[gradle-1.12] 87/211: Imported Upstream version 1.5

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


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

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

commit 70461f232b7903f8f4b3e67fd57c668f53a64187
Author: Emmanuel Bourg <ebourg at apache.org>
Date:   Thu Oct 3 00:54:54 2013 +0200

    Imported Upstream version 1.5
---
 .../main/groovy/org/gradle/build/BuildTypes.groovy |  16 +-
 .../docs/dsl/docbook/JavadocLinkConverter.java     |   8 +
 .../build/docs/dsl/docbook/LinkRenderer.java       |  15 +
 .../build/docs/dsl/links/ClassLinkMetaData.java    |  20 +
 .../docs/dsl/source/SourceMetaDataVisitor.java     |  22 +-
 .../build/docs/dsl/source/model/ClassMetaData.java |  38 +-
 .../dsl/source/model/EnumConstantMetaData.java     |  43 ++
 .../groovy/org/gradle/plugins/jsoup/Jsoup.groovy   |  59 --
 .../gradle/plugins/jsoup/JsoupCopyExtension.groovy |  84 +++
 .../gradle/plugins/jsoup/JsoupFilterReader.groovy  |  56 ++
 .../org/gradle/plugins/jsoup/JsoupPlugin.groovy    |   9 +-
 .../plugins/jsoup/JsoupTransformTarget.groovy      |  32 +
 .../dsl/docbook/JavadocLinkConverterTest.groovy    |  21 +-
 .../dsl/source/ExtractDslMetaDataTaskTest.groovy   |  17 +-
 config/checkstyle/checkstyle.xml                   |   6 +-
 gradle/buildReceipt.gradle                         |  10 +-
 gradle/dependencies.gradle                         |   5 +-
 gradle/versioning.gradle                           |   2 -
 gradle/wrapper/gradle-wrapper.properties           |   4 +-
 .../plugins/announce/internal/NotifySend.groovy    |   1 +
 .../java/org/gradle/api/internal/IoActions.java    |  16 +-
 .../org/gradle/internal/CompositeStoppable.java    |  24 -
 .../java/org/gradle/internal/SystemProperties.java |  10 +-
 .../main/java/org/gradle/util/CollectionUtils.java |  14 +-
 .../org/gradle/api/internal/ActionsTest.groovy     |  13 +
 .../org/gradle/api/internal/IoActionsTest.groovy   |  20 +-
 .../org/gradle/util/CollectionUtilsTest.groovy     |  15 +
 .../build-comparison/build-comparison.gradle       |   2 +-
 .../gradle/BuildComparisonIntegrationSpec.groovy   |   6 +-
 ...Pre12CompareGradleBuildsCrossVersionSpec.groovy |   4 +-
 .../gradle/internal/GradleBuildComparison.java     |   2 +-
 .../quality/PmdPluginIntegrationTest.groovy        |  79 +-
 .../quality/PmdPluginVersionIntegrationTest.groovy |  62 ++
 .../org/gradle/api/plugins/quality/FindBugs.groovy |   8 +
 .../org/gradle/api/plugins/quality/Pmd.groovy      |  60 +-
 .../gradle/api/plugins/quality/PmdExtension.groovy |  15 +-
 .../gradle/api/plugins/quality/PmdPlugin.groovy    |  33 +-
 .../org/gradle/api/plugins/quality/TargetJdk.java  |  71 ++
 .../quality/internal/findbugs/FindBugsSpec.java    |  10 +-
 .../internal/findbugs/FindBugsSpecBuilder.java     |  10 +-
 .../internal/findbugs/FindBugsWorkerManager.groovy |   1 +
 .../api/plugins/quality/PmdPluginTest.groovy       |  30 +-
 .../api/plugins/quality/TargetJdkSpec.groovy       | 103 +++
 .../ArtifactDependenciesIntegrationTest.groovy     |  16 +-
 .../ArtifactOnlyResolutionIntegrationTest.groovy   |   2 +-
 .../resolve/CacheResolveIntegrationTest.groovy     |   4 +-
 .../DependenciesResolveIntegrationTest.java        |  46 --
 .../DependencyResolveRulesIntegrationTest.groovy   | 180 ++++-
 .../resolve/ForcedModulesIntegrationTest.groovy    | 320 ++++++++
 .../ProjectDependenciesIntegrationTest.groovy      |  95 +++
 ...ResolutionStrategySamplesIntegrationTest.groovy |  24 +-
 ...VersionConflictResolutionIntegrationTest.groovy | 556 ++++----------
 ...AliasedArtifactResolutionIntegrationTest.groovy |   2 +-
 .../M3CacheReuseCrossVersionIntegrationTest.groovy |   2 +-
 .../MavenM2CacheReuseIntegrationTest.groovy        |   5 +-
 .../ResolutionOverrideIntegrationTest.groovy       |   5 +-
 .../custom/IvySFtpResolverIntegrationTest.groovy   |   2 +-
 .../custom/IvyUrlResolverIntegrationTest.groovy    |   2 +-
 .../AbstractHttpsRepoResolveIntegrationTest.groovy |   2 +-
 ...ationDependencyResolutionIntegrationTest.groovy |  14 +-
 .../IvyBrokenRemoteResolveIntegrationTest.groovy   |   9 +-
 .../ivy/IvyDescriptorResolveIntegrationTest.groovy |  91 +++
 .../ivy/IvyHttpRepoResolveIntegrationTest.groovy   |  70 +-
 .../resolve/ivy/IvyResolveIntegrationTest.groovy   | 161 +++-
 .../MavenHttpRepoResolveIntegrationTest.groovy     |   2 +-
 .../MavenLocalRepoResolveIntegrationTest.groovy    |  21 +-
 .../artifacts/ArtifactDependencyResolver.java      |   5 +-
 .../internal/artifacts/ConfigurationResolver.java  |  26 +
 .../artifacts/DefaultDependencyFactory.java        |  13 +-
 .../DefaultDependencyManagementServices.java       | 124 +--
 .../artifacts/DefaultProjectDependencyFactory.java |  47 ++
 .../configurations/DefaultConfiguration.java       |  11 +-
 .../DefaultConfigurationContainer.java             |  14 +-
 .../api/internal/artifacts/dsl/ArtifactFile.java   |  78 ++
 .../dsl/DefaultPublishArtifactFactory.java         | 140 ----
 .../artifacts/dsl/ForcedModuleNotationParser.java  |  97 ---
 .../dsl/ModuleVersionSelectorParsers.java          |  93 +++
 .../dsl/PublishArtifactNotationParserFactory.java  | 100 +++
 .../BuildableModuleVersionResolveResult.java       |  14 +-
 .../CacheLockingArtifactDependencyResolver.java    |   7 +-
 ...DefaultBuildableModuleVersionResolveResult.java |  43 +-
 .../ivyservice/DefaultConfigurationResolver.java   |  44 ++
 .../DefaultDependencyResolveDetails.java           |   6 +
 .../ivyservice/DefaultLenientConfiguration.java    |   4 +
 .../ivyservice/DefaultResolvedConfiguration.java   |  15 +-
 .../ivyservice/DefaultSettingsConverter.java       |   5 +-
 .../ivyservice/DefaultUnresolvedDependency.java    |   9 +-
 .../ivyservice/DependencyToModuleResolver.java     |   4 +-
 .../DependencyToModuleVersionIdResolver.java       |   4 +-
 .../ErrorHandlingArtifactDependencyResolver.java   |   6 +-
 .../ivyservice/IvyBackedArtifactPublisher.java     |  10 +-
 .../ivyservice/IvyXmlModuleDescriptorWriter.java   |  12 +-
 .../ivyservice/ModuleVersionNotFoundException.java |   4 +
 .../ivyservice/ModuleVersionResolveException.java  |   4 +-
 .../ivyservice/ModuleVersionResolveResult.java     |   6 +-
 .../SelfResolvingDependencyResolver.java           |   6 +-
 .../artifacts/ivyservice/SettingsConverter.java    |   2 +-
 ...cuitEmptyConfigsArtifactDependencyResolver.java |   6 +-
 .../VersionForcingDependencyToModuleResolver.java  |  17 +-
 .../clientmodule/ClientModuleResolver.java         |  12 +-
 .../DefaultCachedModuleResolution.java             |   5 +-
 .../ForceChangeDependencyDescriptor.java           |  40 -
 .../AbstractDependencyResolverAdapter.java         |  11 +-
 .../BuildableModuleVersionDescriptor.java          |  65 --
 .../ivyresolve/BuildableModuleVersionMetaData.java |  78 ++
 .../CacheLockingModuleVersionRepository.java       |  11 +-
 .../ivyresolve/CachingModuleVersionRepository.java |  46 +-
 .../DefaultBuildableModuleVersionDescriptor.java   | 105 ---
 .../DefaultBuildableModuleVersionMetaData.java     | 137 ++++
 .../ivyresolve/DefaultDependencyMetaData.java      |  88 +++
 .../ivyservice/ivyresolve/DependencyMetaData.java  |  43 ++
 .../ExternalResourceResolverAdapter.java           |  14 +-
 .../IvyAwareModuleVersionRepository.java           |  28 +
 .../ivyresolve/IvyDependencyResolverAdapter.java   |   9 +-
 .../IvyDynamicResolveModuleVersionRepository.java  |  65 ++
 .../ivyresolve/LazyDependencyToModuleResolver.java |  95 +--
 .../LocalAwareModuleVersionRepository.java         |   8 +-
 .../ivyresolve/LocalModuleVersionRepository.java   |  11 +-
 .../ivyresolve/LoopbackDependencyResolver.java     |  21 +-
 .../ivyresolve/ModuleVersionDescriptor.java        |  28 -
 .../ivyresolve/ModuleVersionMetaData.java          |  34 +
 .../ivyresolve/ModuleVersionRepository.java        |   6 +-
 .../ivyservice/ivyresolve/ResolveIvyFactory.java   |  48 +-
 .../ivyresolve/RestrictedDependencyResolver.java   |   2 +-
 .../StartParameterResolutionOverride.java          |   7 +-
 .../ivyservice/ivyresolve/UserResolverChain.java   |  57 +-
 .../parser/DisconnectedParserSettings.java         | 110 +++
 .../parser/GradlePomModuleDescriptorBuilder.java   |  18 +-
 .../parser/GradlePomModuleDescriptorParser.java    |  44 +-
 .../parser/IvyXmlModuleDescriptorParser.java       | 755 ++++++-------------
 .../modulecache/DefaultModuleDescriptorCache.java  |   1 -
 .../PublishModuleDescriptorConverter.java          |   9 +
 .../ResolveModuleDescriptorConverter.java          |  13 +
 ...bstractDependencyDescriptorFactoryInternal.java |  94 ---
 .../AbstractIvyDependencyDescriptorFactory.java    |  80 ++
 .../ClientModuleDependencyDescriptorFactory.java   |  65 --
 ...ClientModuleIvyDependencyDescriptorFactory.java |  65 ++
 ...ultDependenciesToModuleDescriptorConverter.java |   2 +-
 .../DefaultDependencyDescriptorFactory.java        |  51 ++
 .../dependencies/DependencyDescriptorFactory.java  |  13 +-
 .../DependencyDescriptorFactoryDelegate.java       |  63 --
 .../DependencyDescriptorFactoryInternal.java       |  25 -
 .../ExternalModuleDependencyDescriptorFactory.java |  57 --
 ...ternalModuleIvyDependencyDescriptorFactory.java |  57 ++
 .../IvyDependencyDescriptorFactory.java            |  25 +
 .../ProjectDependencyDescriptorFactory.java        |  52 --
 .../ProjectIvyDependencyDescriptorFactory.java     |  52 ++
 .../ReflectiveDependencyDescriptorFactory.java     |  82 ++
 .../DefaultProjectModuleRegistry.java              |   9 +-
 .../projectmodule/ProjectDependencyResolver.java   |  18 +-
 .../projectmodule/ProjectModuleRegistry.java       |   4 +-
 .../DefaultResolutionStrategy.java                 |  21 +-
 .../resolveengine/DefaultDependencyResolver.java   |  21 +-
 .../resolveengine/DependencyGraphBuilder.java      | 157 ++--
 .../resolveengine/ModuleRevisionResolveState.java  |   6 +
 .../resolveengine/ModuleVersionSpec.java           |   4 +-
 .../VersionSelectionReasonResolver.java            |  39 +
 .../result/VersionSelectionReasons.java            |  18 +-
 .../repositories/AbstractArtifactRepository.java   |  42 ++
 .../repositories/DefaultBaseRepositoryFactory.java |   2 +-
 .../DefaultFlatDirArtifactRepository.java          |  14 +-
 .../repositories/DefaultIvyArtifactRepository.java |  49 +-
 .../DefaultMavenArtifactRepository.java            |  23 +-
 .../FixedResolverArtifactRepository.java           |  57 ++
 .../repositories/LegacyDependencyResolver.java     | 284 +++++++
 .../repositories/LegacyMavenResolver.java          |  62 ++
 .../repositories/ResolutionAwareRepository.java    |  23 +
 .../AbstractRepositoryCacheManager.java            |   3 +-
 .../resolver/ExternalResourceResolver.java         |   8 +-
 .../repositories/resolver/MavenResolver.java       |  10 +-
 .../notations/ClientModuleNotationParser.java      |  48 --
 .../ClientModuleNotationParserFactory.java         |  43 ++
 .../notations/DependencyNotationParser.java        |   5 +-
 .../notations/DependencyProjectNotationParser.java |  16 +-
 .../notations/ProjectDependencyFactory.java        |  46 +-
 .../DefaultDependencyManagementServicesTest.groovy |   2 +
 .../DefaultModuleVersionSelectorTest.groovy        |  59 ++
 .../DefaultConfigurationContainerSpec.groovy       |  23 +-
 .../DefaultConfigurationContainerTest.groovy       |   6 +-
 .../configurations/DefaultConfigurationSpec.groovy |  22 +-
 .../configurations/DefaultConfigurationTest.java   |  66 +-
 .../internal/artifacts/dsl/ArtifactFileTest.groovy |  77 ++
 .../dsl/DefaultPublishArtifactFactoryTest.groovy   | 251 ------
 .../dsl/ForcedModuleNotationParserSpec.groovy      | 126 ----
 .../dsl/ModuleVersionSelectorParsersTest.groovy    | 155 ++++
 ...PublishArtifactNotationParserFactoryTest.groovy | 113 +++
 ...cheLockingArtifactDependencyResolverTest.groovy |   6 +-
 ...tBuildableModuleVersionResolveResultTest.groovy |  44 +-
 .../DefaultDependencyResolveDetailsSpec.groovy     |  34 +
 .../ivyservice/DefaultSettingsConverterTest.groovy |  38 +-
 .../DefaultUnresolvedDependencySpec.groovy         |   4 +-
 ...orHandlingArtifactDependencyResolverTest.groovy |  14 +-
 .../ivyservice/IvyBackedArtifactPublisherTest.java |  25 +-
 .../IvyXmlModuleDescriptorWriterTest.groovy        |  28 -
 .../SelfResolvingDependencyResolverTest.groovy     |  14 +-
 ...ptyConfigsArtifactDependencyResolverSpec.groovy |   8 +-
 ...ionForcingDependencyToModuleResolverSpec.groovy |   9 +-
 .../clientmodule/ClientModuleResolverTest.groovy   |  26 +-
 ...aultBuildableModuleVersionDescriptorTest.groovy | 166 ----
 ...efaultBuildableModuleVersionMetaDataTest.groovy | 244 ++++++
 .../DefaultDependencyMetaDataTest.groovy           |  77 ++
 .../DependencyResolverIdentifierTest.groovy        |  11 +-
 ...ynamicResolveModuleVersionRepositoryTest.groovy |  65 ++
 .../LazyDependencyToModuleResolverTest.groovy      |  15 +-
 .../ivyresolve/UserResolverChainTest.groovy        | 131 ++--
 .../parser/IvyXmlModuleDescriptorParserTest.groovy |  34 -
 .../ResolveModuleDescriptorConverterTest.groovy    |   3 +
 ...actDependencyDescriptorFactoryInternalTest.java |  15 +-
 ...lientModuleDependencyDescriptorFactoryTest.java |  11 +-
 ...ependenciesToModuleDescriptorConverterTest.java |  13 +-
 .../DefaultDependencyDescriptorFactoryTest.groovy  |  61 ++
 ...ModuleDescriptorFactoryForClientModuleTest.java |  16 +-
 .../DependencyDescriptorFactoryDelegateTest.java   |  67 --
 ...ernalModuleDependencyDescriptorFactoryTest.java |  12 +-
 .../ProjectDependencyDescriptorFactoryTest.java    | 129 ++--
 ...eflectiveDependencyDescriptorFactoryTest.groovy |  93 +++
 .../ProjectDependencyResolverTest.groovy           |  35 +-
 .../DefaultResolutionStrategySpec.groovy           |  42 +-
 .../DependencyGraphBuilderTest.groovy              |  67 +-
 .../VersionSelectionReasonResolverTest.groovy      |  47 ++
 .../result/VersionSelectionReasonsTest.groovy      |  44 ++
 .../DefaultFlatDirArtifactRepositoryTest.groovy    |   4 +-
 .../DefaultIvyArtifactRepositoryTest.groovy        |  76 +-
 .../DefaultMavenArtifactRepositoryTest.groovy      |  35 +-
 .../notations/DependencyNotationParserTest.groovy  |   2 +-
 .../notations/ProjectDependencyFactoryTest.groovy  |  29 +-
 subprojects/core/core.gradle                       |   3 +-
 .../ConfigurationOnDemandIntegrationTest.groovy    | 125 ++-
 .../api/ProjectConfigurationIntegrationTest.groovy |  38 +
 .../gradle/api/tasks/ArchiveIntegrationTest.groovy |  77 +-
 .../api/tasks/CopyTaskIntegrationTest.groovy       |   2 +-
 .../api/tasks/FileTreeCopyIntegrationTest.groovy   |   2 +-
 .../scripts/StatementLabelsIntegrationTest.groovy  |   1 -
 .../src/main/groovy/org/gradle/StartParameter.java | 107 +--
 ...ExtensiblePolymorphicDomainObjectContainer.java |  37 +
 .../org/gradle/api/NamedDomainObjectContainer.java |   9 +
 .../api/PolymorphicDomainObjectContainer.java      |  59 ++
 .../src/main/groovy/org/gradle/api/Project.java    |  28 +-
 .../src/main/groovy/org/gradle/api/Script.java     |  18 +-
 .../api/artifacts/ArtifactRepositoryContainer.java |   2 +
 .../api/artifacts/ConfigurablePublishArtifact.java |   2 +-
 .../api/artifacts/DependencyResolveDetails.java    |  22 +-
 .../gradle/api/artifacts/ResolutionStrategy.java   |  18 +-
 .../api/artifacts/dsl/RepositoryHandler.java       |  10 +-
 .../FlatDirectoryArtifactRepository.java           |   6 +-
 .../repositories/IvyArtifactRepository.java        |  13 +-
 .../IvyArtifactRepositoryMetaDataProvider.java     |  37 +
 .../repositories/MavenArtifactRepository.java      |   6 +-
 .../gradle/api/execution/TaskExecutionAdapter.java |  32 +
 .../api/file/ConfigurableFileCollection.java       |  12 +-
 .../org/gradle/api/file/ConfigurableFileTree.java  |   8 +-
 .../org/gradle/api/file/CopyProcessingSpec.java    |   2 +-
 .../groovy/org/gradle/api/file/CopySourceSpec.java |   4 +-
 .../main/groovy/org/gradle/api/file/CopySpec.java  |   2 +-
 .../org/gradle/api/file/SourceDirectorySet.java    |   8 +-
 .../AbstractNamedDomainObjectContainer.java        |   8 +
 .../AbstractPolymorphicDomainObjectContainer.java  | 138 ++++
 .../org/gradle/api/internal/ConfigureDelegate.java |  56 +-
 .../gradle/api/internal/ConventionAwareHelper.java |  29 +-
 .../DefaultPolymorphicDomainObjectContainer.java   |  69 ++
 ...amedDomainObjectContainerConfigureDelegate.java |  16 +-
 ...phicDomainObjectContainerConfigureDelegate.java |  44 ++
 .../api/internal/artifacts/ArtifactPublisher.java  |   4 +-
 .../internal/artifacts/BaseRepositoryFactory.java  |   7 +-
 .../DefaultArtifactRepositoryContainer.java        |   1 +
 .../ProjectDependenciesBuildInstruction.java       |  50 --
 .../artifacts/configurations/ResolverProvider.java |  27 -
 .../dependencies/DefaultProjectDependency.java     |  30 +-
 .../artifacts/dsl/DefaultRepositoryHandler.java    |   6 +-
 .../ivyservice/IvyModuleDescriptorWriter.java      |   5 -
 .../ivyservice/ModuleDescriptorConverter.java      |   6 +
 .../repositories/AbstractArtifactRepository.java   |  42 --
 .../repositories/ArtifactRepositoryInternal.java   |   5 +-
 .../FixedResolverArtifactRepository.java           |  44 --
 .../repositories/PublicationAwareRepository.java   |  23 +
 .../component/SoftwareComponentInternal.java       |   8 +-
 .../org/gradle/api/internal/component/Usage.java   |  28 +
 .../api/internal/file/AbstractFileResolver.java    |  16 +
 .../org/gradle/api/internal/file/FileResolver.java |   3 +
 .../api/internal/file/archive/TarCopyAction.java   |  23 -
 .../internal/file/archive/TarCopySpecVisitor.java  |   5 +-
 .../api/internal/file/archive/ZipCopyAction.java   |  24 +
 .../internal/file/archive/ZipCopySpecVisitor.java  |   6 +-
 .../compression/ArchiveOutputStreamFactory.java    |  33 +
 .../file/archive/compression/Bzip2Archiver.java    |   8 +-
 .../file/archive/compression/Compressor.java       |  33 -
 .../file/archive/compression/GzipArchiver.java     |   8 +-
 .../file/archive/compression/SimpleCompressor.java |   4 +-
 .../internal/file/copy/AbstractZipCompressor.java  |  37 +
 .../api/internal/file/copy/ArchiveCopyAction.java  |   3 +
 .../api/internal/file/copy/ZipCompressor.java      |  26 +
 .../internal/file/copy/ZipDeflatedCompressor.java  |  31 +
 .../internal/file/copy/ZipStoredCompressor.java    |  28 +
 .../gradle/api/internal/html/SimpleHtmlWriter.java |  41 +
 .../notations/api/TopLevelNotationParser.java      |  24 -
 .../notations/parsers/CompositeNotationParser.java |   5 -
 .../parsers/ErrorHandlingNotationParser.java       |  16 +-
 .../notations/parsers/MapNotationParser.java       |   2 +
 .../notations/parsers/NormalizedTimeUnit.java      |  45 ++
 .../notations/parsers/TimeUnitsParser.java         |  46 ++
 .../api/internal/plugins/DefaultConvention.java    |   5 +
 .../api/internal/plugins/ExtensionsStorage.java    | 147 +++-
 .../api/internal/project/AbstractProject.java      |   8 -
 .../project/DefaultProjectAccessListener.java      |  33 +
 .../project/GradleInternalServiceRegistry.java     |  17 +-
 .../api/internal/project/ProjectInternal.java      |   4 +-
 .../project/ProjectInternalServiceRegistry.java    |   6 +-
 .../internal/project/TaskExecutionServices.java    |   3 +-
 .../project/TopLevelBuildServiceRegistry.java      |   9 +-
 .../api/internal/tasks/DefaultTaskContainer.java   |  17 +-
 .../tasks/DefaultTaskContainerFactory.java         |   9 +-
 .../api/internal/xml/SimpleMarkupWriter.java       | 415 ++++++++++
 .../gradle/api/internal/xml/SimpleXmlWriter.java   | 395 +---------
 .../gradle/api/internal/xml/XmlTransformer.java    |  27 +-
 .../groovy/org/gradle/api/invocation/Gradle.java   |  89 ++-
 .../gradle/api/plugins/DeferredConfigurable.java   |  32 +
 .../org/gradle/api/plugins/ExtensionContainer.java |  11 +
 .../api/plugins/ObjectConfigurationAction.java     |   2 +-
 .../org/gradle/api/resources/ResourceHandler.java  |   8 +-
 .../main/groovy/org/gradle/api/tasks/Delete.java   |   2 +-
 .../groovy/org/gradle/api/tasks/SourceTask.java    |   4 +-
 .../groovy/org/gradle/api/tasks/TaskInputs.java    |  12 +-
 .../groovy/org/gradle/api/tasks/TaskOutputs.java   |   6 +-
 .../groovy/org/gradle/api/tasks/TaskState.java     |   4 +
 .../main/groovy/org/gradle/api/tasks/Upload.java   |  15 +-
 .../api/tasks/bundling/AbstractArchiveTask.java    |   4 +-
 .../groovy/org/gradle/api/tasks/bundling/Tar.java  |  11 +-
 .../groovy/org/gradle/api/tasks/bundling/Zip.java  |  52 +-
 .../api/tasks/bundling/ZipEntryCompression.java    |  27 +
 .../configuration/DefaultBuildConfigurer.java      |  24 +-
 .../configuration/ImplicitTasksConfigurer.java     |   7 +-
 .../configuration/LifecycleProjectEvaluator.java   |   3 +-
 .../ProjectDependencies2TaskResolver.java          |   9 +-
 .../configuration/ProjectEvaluationConfigurer.java |  25 -
 .../execution/OnlyWhenConfigureOnDemand.java       |  39 -
 .../gradle/execution/ProjectEvaluatingAction.java  |  13 +-
 .../gradle/execution/ProjectFinderByTaskPath.java  |  71 --
 .../gradle/execution/TaskPathProjectEvaluator.java |  18 +-
 .../groovy/org/gradle/execution/TaskSelector.java  |  27 +-
 .../taskgraph/DefaultTaskExecutionPlan.java        |  40 +
 .../taskgraph/ParallelTaskPlanExecutor.java        |  46 +-
 .../execution/taskgraph/TaskExecutionPlan.java     |   3 +
 .../taskgraph/TaskPlanExecutorFactory.java         |  16 +-
 .../taskpath/ProjectFinderByTaskPath.java          |  61 ++
 .../execution/taskpath/ResolvedTaskPath.java       |  57 ++
 .../execution/taskpath/TaskPathResolver.java       |  60 ++
 .../initialization/BuildLayoutParameters.java      |  56 ++
 .../gradle/initialization/BuildSourceBuilder.java  |  14 +-
 .../DefaultCommandLineConverter.java               |  33 +-
 .../initialization/DefaultGradleLauncher.java      |   3 +-
 .../DefaultGradleLauncherFactory.java              |   7 +-
 .../initialization/DependencyResolutionLogger.java |  32 +-
 .../initialization/LayoutCommandLineConverter.java |  62 ++
 .../initialization/ProjectAccessListener.java      |  29 +
 .../org/gradle/listener/ActionBroadcast.java       |   6 +
 .../org/gradle/process/ProcessForkOptions.java     |   4 +-
 .../gradle/process/internal/DefaultExecHandle.java |   1 -
 .../gradle/process/internal/ExecHandleRunner.java  |   1 +
 .../org/gradle/profile/EvalutationOperation.java   |  31 +
 .../org/gradle/profile/HTMLProfileReport.groovy    |  42 --
 .../org/gradle/profile/ProfileReportRenderer.java  | 166 +++-
 .../groovy/org/gradle/profile/ProjectProfile.java  |   7 +-
 .../org/gradle/reporting/CodePanelRenderer.java    |  14 +-
 .../org/gradle/reporting/DomReportRenderer.java    |  55 --
 .../org/gradle/reporting/HtmlReportRenderer.java   |  42 +-
 .../org/gradle/reporting/ReportRenderer.java       |  26 +
 .../org/gradle/reporting/TabbedPageRenderer.java   |  66 +-
 .../groovy/org/gradle/reporting/TabsRenderer.java  |  47 +-
 .../gradle/reporting/TextDomReportRenderer.java    |  50 --
 .../org/gradle/reporting/TextReportRenderer.java   |   2 +-
 .../groovy/org/gradle/util/DeprecationLogger.java  | 218 +-----
 .../org/gradle/util/SingleMessageLogger.java       | 249 ++++++
 .../main/groovy/org/gradle/util/VersionNumber.java |   2 +
 .../org/gradle/configuration/default-imports.txt   |   2 +
 .../org/gradle/profile/ProfileTemplate.html        | 115 ---
 .../groovy/org/gradle/StartParameterTest.groovy    | 325 +++++---
 .../AbstractNamedDomainObjectContainerTest.groovy  |   9 +
 .../api/internal/ConfigureByMapActionTest.groovy   |  12 +
 ...tPolymorphicDomainObjectContainerDslTest.groovy | 110 +++
 ...aultPolymorphicDomainObjectContainerTest.groovy | 161 ++++
 .../DefaultArtifactRepositoryContainerTest.groovy  |  36 +-
 .../ModuleVersionSelectorStrictSpecTest.groovy     |  44 --
 .../ProjectDependenciesBuildInstructionTest.java   |  58 --
 .../AbstractModuleDependencySpec.groovy            | 101 +++
 .../dependencies/AbstractModuleDependencyTest.java |  15 +-
 .../dependencies/DefaultClientModuleTest.java      |  10 +-
 .../DefaultExternalModuleDependencyTest.java       |   5 -
 .../DefaultProjectDependencyTest.groovy            | 190 +++++
 .../dependencies/DefaultProjectDependencyTest.java | 260 -------
 .../dsl/DefaultRepositoryHandlerTest.groovy        |  28 +-
 .../file/archive/TarCopySpecVisitorTest.java       |   7 +-
 .../file/archive/ZipCopySpecVisitorTest.java       |  35 +-
 .../api/internal/html/SimpleHtmlWriterTest.groovy  |  40 +
 .../parsers/ErrorHandlingNotationParserTest.groovy |   8 +-
 .../notations/parsers/TimeUnitsParserTest.groovy   |  54 ++
 .../internal/plugins/ExtensionsStorageTest.groovy  | 147 ++++
 .../api/internal/project/DefaultProjectTest.groovy |  44 +-
 .../GradleInternalServiceRegistryTest.groovy       |   5 +-
 .../internal/project/NewDefaultProjectTest.groovy  |  69 ++
 .../ProjectInternalServiceRegistryTest.java        |   3 +
 .../internal/tasks/DefaultTaskContainerTest.groovy | 309 ++++++++
 .../internal/tasks/DefaultTaskContainerTest.java   | 337 ---------
 .../api/internal/xml/XmlTransformerTest.groovy     |  62 +-
 .../DefaultBuildConfigurerTest.groovy              |  30 +-
 .../ImplicitTasksConfigurerTest.groovy             |   8 -
 .../LifecycleProjectEvaluatorTest.groovy           |  90 +++
 .../LifecycleProjectEvaluatorTest.java             | 127 ----
 .../ProjectDependencies2TaskResolverTest.groovy    |  34 +-
 .../execution/OnlyWhenConfigureOnDemandTest.groovy |  63 --
 .../execution/ProjectEvaluatingActionTest.groovy   |   3 +-
 .../execution/ProjectFinderByTaskPathTest.groovy   |  73 --
 .../execution/TaskPathProjectEvaluatorTest.groovy  |  24 +-
 .../taskgraph/DefaultTaskExecutionPlanTest.groovy  |   8 +-
 .../taskgraph/ParallelTaskExecutionPlanTest.groovy |  26 +
 .../taskgraph/TaskPlanExecutorFactoryTest.groovy   |   6 +-
 .../taskpath/ProjectFinderByTaskPathTest.groovy    |  64 ++
 .../execution/taskpath/ResolvedTaskPathTest.groovy |  33 +
 .../execution/taskpath/TaskPathResolverTest.groovy |  98 +++
 .../initialization/BuildSourceBuilderTest.groovy   |   6 +-
 .../DefaultCommandLineConverterTest.java           |   9 +-
 .../DependencyResolutionLoggerTest.groovy          |  56 +-
 .../LayoutCommandLineConverterTest.groovy          |  45 ++
 .../org/gradle/listener/ListenerBroadcastTest.java |  51 --
 .../gradle/reporting/HtmlReportRendererTest.groovy |  18 +-
 .../org/gradle/reporting/TabsRendererTest.groovy   |  30 +-
 .../reporting/TextDomReportRendererTest.groovy     |  47 --
 .../org/gradle/util/DeprecationLoggerTest.groovy   |  83 --
 .../org/gradle/util/SingleMessageLoggerTest.groovy |  83 ++
 .../groovy/org/gradle/util/HelperUtil.groovy       |  38 +-
 .../groovy/org/gradle/util/Matchers.java           |  13 +-
 .../plugins/cpp/CppSamplesIntegrationTest.groovy   |   6 +-
 ...pendencyInsightReportTaskIntegrationTest.groovy |   4 +-
 .../DependencyReportTaskIntegrationTest.groovy     |   8 +-
 .../nodes/AbstractRenderableDependencyResult.java  |   5 +-
 .../AbstractRenderableDependencyResultSpec.groovy  |  11 +-
 .../nodes/RenderableDependencyResultTest.groovy    |   4 +-
 ...RenderableUnresolvedDependencyResultTest.groovy |   2 +-
 subprojects/docs/docs.gradle                       |  24 +-
 subprojects/docs/release-notes-transform.gradle    | 222 ------
 subprojects/docs/src/docs/css/release-notes.css    |  97 ++-
 subprojects/docs/src/docs/dsl/dsl.xml              |  21 +
 ....gradle.api.artifacts.dsl.RepositoryHandler.xml |  56 ++
 ...rtifacts.repositories.IvyArtifactRepository.xml |  53 ++
 ...ories.IvyArtifactRepositoryMetaDataProvider.xml |  41 +
 ...ifacts.repositories.MavenArtifactRepository.xml |  47 ++
 ...dle.api.distribution.DistributionContainer.xml} |   0
 ...rg.gradle.api.plugins.DistributionExtension.xml |  24 -
 ...pi.plugins.MavenRepositoryHandlerConvention.xml |  44 ++
 .../org.gradle.api.plugins.quality.FindBugs.xml    |   6 +-
 .../dsl/org.gradle.api.plugins.quality.Pmd.xml     |   4 +
 ...org.gradle.api.plugins.quality.PmdExtension.xml |   4 +
 ...org.gradle.api.publish.PublicationContainer.xml |   3 +
 .../dsl/org.gradle.api.publish.ivy.IvyArtifact.xml |  59 ++
 .../org.gradle.api.publish.ivy.IvyArtifactSet.xml  |  41 +
 .../org.gradle.api.publish.maven.MavenArtifact.xml |  34 +
 ...g.gradle.api.publish.maven.MavenArtifactSet.xml |  41 +
 ...g.gradle.api.publish.maven.MavenPublication.xml |  12 +
 ...le.api.publish.maven.tasks.GenerateMavenPom.xml |  40 +
 ...gradle.api.reporting.GenerateBuildDashboard.xml |  50 ++
 .../docs/dsl/org.gradle.api.tasks.bundling.Zip.xml |   6 +-
 ...g.gradle.plugins.ide.idea.model.IdeaProject.xml |   5 +
 subprojects/docs/src/docs/dsl/plugins.xml          |   5 +-
 .../docs/src/docs/release/content/script.js        | 115 ++-
 subprojects/docs/src/docs/release/notes.md         | 731 +++++++-----------
 .../docs/src/docs/userguide/artifactMngmt.xml      |   6 +-
 .../docs/src/docs/userguide/bootstrapPlugin.xml    |  20 +-
 .../docs/userguide/buildAnnouncementsPlugin.xml    |   2 +-
 .../src/docs/userguide/buildDashboardPlugin.xml    |  76 ++
 .../docs/src/docs/userguide/buildEnvironment.xml   |  12 +
 .../docs/src/docs/userguide/commandLine.xml        |  14 +-
 .../docs/src/docs/userguide/comparingBuilds.xml    |   2 +-
 subprojects/docs/src/docs/userguide/depMngmt.xml   | 837 ++++++++++++---------
 .../docs/src/docs/userguide/distributionPlugin.xml | 123 +++
 .../docs/src/docs/userguide/featureLifecycle.xml   |  15 +-
 .../docs/src/docs/userguide/gradleDaemon.xml       |   2 +-
 .../docs/src/docs/userguide/groovyPlugin.xml       |   2 +-
 .../docs/src/docs/userguide/installation.xml       |   8 -
 .../userguide/javaLibraryDistributionPlugin.xml    |  16 +-
 subprojects/docs/src/docs/userguide/javaPlugin.xml |  20 +-
 .../docs/src/docs/userguide/mavenPlugin.xml        |   2 +-
 .../docs/src/docs/userguide/multiproject.xml       |  64 +-
 .../docs/src/docs/userguide/organizeBuildLogic.xml |   2 +-
 subprojects/docs/src/docs/userguide/overview.xml   |   4 +-
 .../docs/src/docs/userguide/publishingIvy.xml      | 291 ++++---
 .../docs/src/docs/userguide/publishingMaven.xml    | 239 ++++--
 .../docs/src/docs/userguide/scalaPlugin.xml        |  16 +
 .../docs/src/docs/userguide/signingPlugin.xml      |   2 +-
 .../docs/src/docs/userguide/sonarPlugin.xml        |  90 ++-
 .../docs/src/docs/userguide/sonarRunnerPlugin.xml  | 320 ++++++++
 .../docs/src/docs/userguide/standardPlugins.xml    |  47 +-
 subprojects/docs/src/docs/userguide/userguide.xml  |   3 +
 .../docs/src/docs/userguide/workingWithFiles.xml   |   2 +-
 .../docs/src/samples/buildDashboard/build.gradle   |  30 +
 .../buildDashboard/config/codenarc/codenarc.xml    |  20 +
 .../docs/src/samples/buildDashboard/readme.xml     |  18 +
 .../groovy/org/gradle/sample/GroovyPerson.groovy   |   5 +
 .../groovy/org/gradle/sample/PersonTest.groovy     |  13 +
 .../docs/src/samples/dependencies/build.gradle     | 178 -----
 .../repo/sea.fish/ivy-billfish-1.0.xml             |  13 -
 .../dependencies/repo/sea.fish/ivy-shark-1.0.xml   |  15 -
 .../dependencies/repo/sea.fish/ivy-tuna-1.0.xml    |  15 -
 .../dependencies/repo/sea.mammals/ivy-orca-1.0.xml |  13 -
 .../docs/src/samples/dependencies/settings.gradle  |   1 -
 .../descriptor-customization/build.gradle          |  30 +
 .../ivy-publish/java-multi-project/build.gradle    |  61 ++
 .../ivy-publish/java-multi-project/output-ivy.xml  |  21 +
 .../ivy-publish/java-multi-project/settings.gradle |   2 +
 .../samples/ivy-publish/quickstart/build.gradle    |  25 +
 .../docs/src/samples/ivypublish-new/build.gradle   |  65 --
 .../docs/src/samples/ivypublish-new/output-ivy.xml |  21 -
 .../src/samples/ivypublish-new/settings.gradle     |  18 -
 .../src/main/java/org/gradle/SomeClass.java        |   4 -
 .../samples/maven-publish/javaProject/build.gradle |  40 +
 .../javaProject}/subproject/build.gradle           |   0
 .../src/main/java/org/gradle/shared/Person.java    |   0
 .../maven-publish/pomCustomization/build.gradle    |  30 +
 .../samples/maven-publish/quickstart/build.gradle  |  25 +
 .../quickstart}/src/main/java/org/MyClass.java     |   0
 .../src/samples/maven/publish-new/build.gradle     |  39 -
 .../docs/src/samples/sonar/advanced/build.gradle   |  18 +-
 .../src/samples/sonarRunner/advanced/build.gradle  |  24 +
 .../samples/sonarRunner/multiProject/build.gradle  |  43 ++
 .../sonarRunner/multiProject/settings.gradle       |   1 +
 .../samples/sonarRunner/quickstart/build.gradle    |  26 +
 .../src/main/java/org/gradle/Person.java           |  16 +
 .../src/test/java/org/gradle/PersonTest.java       |  12 +
 .../artifacts/defineRepository/build.gradle        |  38 +-
 .../artifacts/resolutionStrategy/build.gradle      |  20 +-
 .../samples/userguide/distribution/build.gradle    |  51 ++
 .../userguide/distribution/custom/custom.txt       |   0
 .../src/samples/userguide/distribution/readme.xml  |   3 +
 .../userguide/javaLibraryDistribution/build.gradle |  20 +-
 .../publishingIvyGenerateDescriptor.out            |   5 +
 .../publishingIvyPublishLifecycle.out              |  13 +-
 .../userguideOutput/publishingIvyPublishSingle.out |  13 +-
 .../userguideOutput/publishingMavenGeneratePom.out |   5 +
 .../publishingMavenPublishLocal.out                |   3 +-
 .../publishingMavenPublishMinimal.out              |   3 +-
 .../releasenotes/FunctionalReleaseNotesTest.groovy |   2 +-
 .../docs/src/transforms/release-notes.gradle       | 216 ++++++
 .../ear/descriptor/DeploymentDescriptor.java       |   4 +-
 .../internal/DefaultDeploymentDescriptor.groovy    |  11 +-
 .../DefaultDeploymentDescriptorTest.groovy         |  20 +-
 .../eclipse/AbstractEclipseIntegrationTest.groovy  |   3 +-
 .../eclipse/EclipseClasspathIntegrationTest.groovy |   2 +-
 ...ClasspathRemoteResolutionIntegrationTest.groovy |   2 +-
 .../EclipseClasspathResolveIntegrationTest.groovy  |   2 +-
 .../ide/eclipse/EclipseIntegrationTest.groovy      |   2 +-
 .../EclipseMultiModuleIntegrationTest.groovy       |   4 +-
 .../eclipse/EclipseProjectIntegrationTest.groovy   |   2 +-
 .../eclipse/EclipseWtpModelIntegrationTest.groovy  |  54 +-
 .../idea/ConfigurationHooksIntegrationTest.groovy  |   2 +-
 .../plugins/ide/idea/IdeaIntegrationTest.groovy    |  17 +-
 .../ide/idea/IdeaModuleIntegrationTest.groovy      |   2 +-
 .../ide/idea/IdeaMultiModuleIntegrationTest.groovy |   2 +-
 .../ide/idea/IdeaProjectIntegrationTest.groovy     |   2 +-
 .../build.gradle                                   |   3 +
 .../expectedFiles/project1/project1.iml.xml        |  32 +
 .../expectedFiles/project2/project2.iml.xml        |  32 +
 .../expectedFiles/project3/project3.iml.xml        |  32 +
 .../expectedFiles/root.ipr.xml                     | 123 +++
 .../project1/build.gradle                          |   9 +
 .../project2/build.gradle                          |   9 +
 .../project3/build.gradle                          |   9 +
 .../settings.gradle                                |   3 +
 .../model/internal/WtpComponentFactory.groovy      |  19 +-
 .../org/gradle/plugins/ide/idea/IdeaPlugin.groovy  |  12 +-
 .../ide/idea/internal/IdeaScalaConfigurer.groovy   | 129 ++++
 .../plugins/ide/idea/model/IdeaModule.groovy       |   5 +-
 .../plugins/ide/idea/model/IdeaProject.groovy      |  14 +-
 .../gradle/plugins/ide/idea/model/Module.groovy    |   2 +-
 .../plugins/ide/idea/model/ModuleLibrary.groovy    |  21 +-
 .../org/gradle/plugins/ide/idea/model/Path.groovy  |   6 +-
 .../gradle/plugins/ide/idea/model/Project.groovy   |  55 +-
 .../plugins/ide/idea/model/ProjectLibrary.groovy   | 105 +++
 .../ide/internal/IdeDependenciesExtractor.groovy   |  79 +-
 .../internal/JavadocAndSourcesDownloader.groovy    | 125 +++
 .../internal/provider/BuildModelAction.java        |  11 +
 .../internal/provider/IdeaModelBuilder.java        |   1 -
 .../ide/idea/model/ProjectLibraryTest.groovy       |  86 +++
 .../plugins/ide/idea/model/ProjectTest.groovy      |  16 +-
 .../plugins/ide/idea/model/customProject.xml       |  11 +
 subprojects/integ-test/integ-test.gradle           |   2 +-
 .../integtests/CacheProjectIntegrationTest.groovy  |   2 +-
 .../integtests/CharacterEncodingIntegTest.groovy   |   3 -
 .../integtests/CommandLineIntegrationTest.groovy   |   6 +-
 .../gradle/integtests/ExecIntegrationTest.groovy   |   4 +-
 .../IncrementalBuildIntegrationTest.groovy         |   2 +-
 .../IncrementalTestIntegrationTest.groovy          |  10 +-
 .../InitScriptExecutionIntegrationTest.groovy      |  24 +-
 .../integtests/JavaProjectIntegrationTest.groovy   |   3 +
 .../MultiProjectDependencyIntegrationTest.groovy   |   5 +-
 .../OsgiProjectSampleIntegrationTest.groovy        |   2 +-
 .../ParallelProjectExecutionIntegrationTest.groovy |   1 +
 .../integtests/ProfilingIntegrationTest.groovy     |  23 +-
 .../integtests/ProjectLayoutIntegrationTest.groovy |   6 +-
 .../integtests/WaterProjectIntegrationTest.groovy  |   2 +-
 .../BuildEnvironmentIntegrationTest.groovy         |   4 +-
 .../logging/LoggerIsEnabledIntegrationTest.groovy  |   2 +-
 .../logging/LoggingIntegrationTest.groovy          |  12 +-
 .../ivy/IvyHttpPublishIntegrationTest.groovy       |   2 +-
 .../IvyJavaProjectPublishIntegrationTest.groovy    |   8 +-
 .../ivy/IvySFtpPublishIntegrationTest.groovy       |   2 +-
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |   2 +-
 .../MavenJavaProjectPublishIntegrationTest.groovy  |   4 +-
 .../MavenMultiProjectPublishIntegrationTest.groovy |   8 +-
 .../MavenPublishIgnoresMavenSettingsTest.groovy    |  68 ++
 .../maven/MavenPublishIntegrationTest.groovy       |   2 +-
 ...MavenPublishRespectsPomConfigurationTest.groovy |   2 +-
 ...SamplesMavenPomGenerationIntegrationTest.groovy |   2 +-
 .../SamplesMavenQuickstartIntegrationTest.groovy   |   2 +-
 .../samples/SamplesAnnounceIntegrationTest.groovy  |   2 +-
 .../samples/SamplesAntlrIntegrationTest.groovy     |   6 +-
 .../SamplesApplicationIntegrationTest.groovy       |   2 +-
 ...sClientModuleDependenciesIntegrationTest.groovy |   2 +-
 .../SamplesCodeQualityIntegrationTest.groovy       |   4 +-
 ...amplesCustomBuildLanguageIntegrationTest.groovy |   2 +-
 .../SamplesCustomPluginIntegrationTest.groovy      |   6 +-
 ...lesExcludesAndClassifiersIntegrationTest.groovy |   2 +-
 ...lesGroovyCustomizedLayoutIntegrationTest.groovy |   6 +-
 ...SamplesGroovyMultiProjectIntegrationTest.groovy |   2 +-
 .../SamplesGroovyQuickstartIntegrationTest.groovy  |   6 +-
 .../SamplesJavaApiAndImplIntegrationTest.groovy    |   8 +-
 .../samples/SamplesJavaBaseIntegrationTest.groovy  |   6 +-
 ...mplesJavaCustomizedLayoutIntegrationTest.groovy |   6 +-
 .../SamplesJavaMultiProjectIntegrationTest.groovy  |  10 +-
 .../SamplesJavaOnlyIfIntegrationTest.groovy        |   2 +-
 ...esJavaProjectWithIntTestsIntegrationTest.groovy |   6 +-
 .../SamplesJavaQuickstartIntegrationTest.groovy    |   6 +-
 ...SamplesMixedJavaAndGroovyIntegrationTest.groovy |   6 +-
 .../SamplesMixedJavaAndScalaIntegrationTest.groovy |   6 +-
 ...mplesMultiProjectBuildSrcIntegrationTest.groovy |   2 +-
 .../SamplesRepositoriesIntegrationTest.groovy      |   2 +-
 ...plesScalaCustomizedLayoutIntegrationTest.groovy |   6 +-
 .../SamplesScalaQuickstartIntegrationTest.groovy   |   6 +-
 .../samples/SamplesScalaZincIntegrationTest.groovy |   2 +-
 .../SamplesWebProjectIntegrationTest.groovy        |   2 +-
 .../SamplesWebQuickstartIntegrationTest.groovy     |   2 +-
 .../AbstractDependencyResolutionTest.groovy        |   9 +-
 .../fixtures/AbstractIntegrationSpec.groovy        |  13 -
 .../fixtures/AutoTestedSamplesUtil.groovy          |   9 +-
 .../org/gradle/integtests/fixtures/Sample.java     |  11 +-
 .../gradle/integtests/fixtures/TestResources.java  |  12 +-
 .../fixtures/UserGuideSamplesRunner.groovy         |   2 +-
 .../fixtures/executer/AbstractGradleExecuter.java  | 209 +++--
 .../executer/BuildServerGradleDistribution.groovy  |  41 +
 .../fixtures/executer/DaemonGradleExecuter.java    |   4 +-
 .../executer/DefaultGradleDistribution.java        |   4 +
 .../executer/DependencyResolutionFailure.groovy    |   9 +-
 .../executer/DownloadableGradleDistribution.groovy |  75 ++
 .../fixtures/executer/ExecutionFailure.java        |   2 +-
 .../fixtures/executer/ExecutionResult.java         |   5 -
 .../fixtures/executer/ForkingGradleExecuter.java   |  73 +-
 .../fixtures/executer/ForkingGradleHandle.java     |  10 +-
 .../fixtures/executer/GradleDistribution.java      |   5 +
 .../fixtures/executer/GradleExecuter.java          |  54 +-
 .../fixtures/executer/InProcessGradleExecuter.java |  48 +-
 .../executer/InitScriptExecuterFixture.groovy      |  58 ++
 .../executer/IntegrationTestBuildContext.java      |   3 +
 .../fixtures/executer/OutputScraper.groovy         |  64 --
 .../executer/OutputScrapingExecutionFailure.java   |   4 +-
 .../executer/OutputScrapingExecutionResult.java    |   6 -
 .../executer/ParallelForkingGradleExecuter.java    |   5 +-
 .../executer/ParallelForkingGradleHandle.java      |  14 +-
 .../executer/ProgressLoggingFixture.groovy         |  87 ++-
 .../executer/ProjectLifecycleFixture.groovy        |  57 ++
 .../executer/ReleasedGradleDistribution.groovy     |  57 +-
 .../gradle/test/fixtures/ivy/IvyDescriptor.groovy  |  70 +-
 .../test/fixtures/ivy/IvyDescriptorArtifact.groovy |  21 +
 .../fixtures/ivy/IvyDescriptorDependency.groovy    |  29 +
 .../IvyDescriptorDependencyConfiguration.groovy    |  32 -
 .../gradle/test/fixtures/ivy/IvyFileModule.groovy  |  76 +-
 .../gradle/test/fixtures/ivy/IvyHttpModule.groovy  |   5 +
 .../org/gradle/test/fixtures/ivy/IvyModule.java    |   2 +
 .../test/fixtures/maven/M2Installation.groovy      |   5 +-
 .../test/fixtures/maven/MavenDependency.groovy     |  30 +
 .../test/fixtures/maven/MavenFileModule.groovy     |  54 +-
 .../org/gradle/test/fixtures/maven/MavenPom.groovy |  11 +-
 .../gradle/test/fixtures/maven/MavenScope.groovy   |  29 +-
 .../gradle/test/fixtures/publish/Identifier.java   | 101 +++
 .../fixtures/executer/OutputScraperTest.groovy     | 100 ---
 .../internal-testing/internal-testing.gradle       |   1 +
 .../fixtures/DefaultTestExecutionResult.groovy     | 186 ++---
 .../fixtures/HtmlTestExecutionResult.groovy        | 146 ++++
 .../fixtures/JUnitXmlTestExecutionResult.groovy    | 184 +++++
 .../file/TestDirectoryProviderFinder.groovy        |  35 -
 .../main/groovy/org/gradle/util/RuleHelper.java    |  57 --
 .../groovy/org/gradle/util/TestPrecondition.groovy |   4 +
 .../api/AutoTestedSamplesIvyIntegrationTest.groovy |  29 +
 .../publish/ivy/AbstractIvyPublishIntegTest.groovy |  67 ++
 .../ivy/AutoTestedSamplesIvyIntegrationTest.groovy |  29 -
 .../ivy/IvyCustomPublishIntegrationTest.groovy     | 111 ---
 .../ivy/IvyEarProjectPublishIntegrationTest.groovy |  64 --
 .../ivy/IvyHttpPublishIntegrationTest.groovy       | 349 ---------
 .../IvyJavaProjectPublishIntegrationTest.groovy    |  62 --
 .../ivy/IvyLocalPublishIntegrationTest.groovy      | 177 -----
 ...IvyPublishArtifactCustomisationIntegTest.groovy | 321 ++++++++
 .../publish/ivy/IvyPublishBasicIntegTest.groovy    | 158 ++++
 .../IvyPublishCrossVersionIntegrationTest.groovy   | 128 ++++
 ...yPublishDescriptorCustomisationIntegTest.groovy | 147 ++++
 ...vyPublishDescriptorModificationIntegTest.groovy | 103 ---
 .../api/publish/ivy/IvyPublishEarIntegTest.groovy  |  82 ++
 .../api/publish/ivy/IvyPublishHttpIntegTest.groovy | 378 ++++++++++
 .../IvyPublishIdentifierValidationIntegTest.groovy | 155 ++++
 .../publish/ivy/IvyPublishIssuesIntegTest.groovy   |  64 ++
 .../api/publish/ivy/IvyPublishJavaIntegTest.groovy | 150 ++++
 .../ivy/IvyPublishMultiProjectIntegTest.groovy     | 157 ++++
 .../IvyPublishMultipleReposIntegrationTest.groovy  |  87 ---
 .../IvyPublishMultipleRepositoriesIntegTest.groovy |  88 +++
 .../publish/ivy/IvyPublishPluginIntegTest.groovy   |  32 -
 .../api/publish/ivy/IvyPublishWarIntegTest.groovy  |  78 ++
 .../IvySingleProjectPublishIntegrationTest.groovy  |  76 --
 .../ivy/IvyWarProjectPublishIntegrationTest.groovy |  60 --
 .../ivy/SamplesIvyPublishIntegrationTest.groovy    |  74 +-
 .../ivy/plugins/IvyPublishPluginIntegTest.groovy   |  32 +
 .../ivy/InvalidIvyPublicationException.java        |  40 +
 .../org/gradle/api/publish/ivy/IvyArtifact.java    | 105 +++
 .../org/gradle/api/publish/ivy/IvyArtifactSet.java |  62 ++
 .../gradle/api/publish/ivy/IvyConfiguration.java   |  48 ++
 .../api/publish/ivy/IvyConfigurationContainer.java |  42 ++
 .../org/gradle/api/publish/ivy/IvyDependency.java  |  36 +
 .../api/publish/ivy/IvyModuleDescriptor.java       |   2 +-
 .../org/gradle/api/publish/ivy/IvyPublication.java | 269 +++++--
 .../ivy/internal/DefaultIvyModuleDescriptor.java   |  53 --
 .../ivy/internal/DefaultIvyPublication.java        | 130 ----
 .../ivy/internal/IvyModuleDescriptorInternal.java  |  35 -
 .../ivy/internal/IvyNormalizedPublication.java     |  49 --
 .../ivy/internal/IvyPublicationInternal.java       |  40 -
 .../api/publish/ivy/internal/IvyPublisher.java     |  40 -
 .../ivy/internal/artifact/DefaultIvyArtifact.java  |  96 +++
 .../internal/artifact/DefaultIvyArtifactSet.java   |  90 +++
 .../artifact/IvyArtifactNotationParserFactory.java | 147 ++++
 .../internal/dependency/DefaultIvyDependency.java  |  37 +
 .../dependency/DefaultIvyDependencySet.java        |  25 +
 .../internal/dependency/IvyDependencyInternal.java |  24 +
 ...tionDynamicDescriptorGenerationTaskCreator.java |  73 ++
 .../plugins/IvyPublishDynamicTaskCreator.java      |  90 +++
 .../publication/DefaultIvyConfiguration.java       |  43 ++
 .../DefaultIvyConfigurationContainer.java          |  34 +
 .../publication/DefaultIvyModuleDescriptor.java    |  71 ++
 .../publication/DefaultIvyPublication.java         | 156 ++++
 .../publication/DefaultIvyPublicationIdentity.java |  55 ++
 .../publication/IvyModuleDescriptorInternal.java   |  44 ++
 .../publication/IvyPublicationInternal.java        |  40 +
 .../publisher/DependencyResolverIvyPublisher.java  |  70 ++
 .../publisher/IvyDescriptorFileGenerator.java      | 213 ++++++
 .../publisher/IvyNormalizedPublication.java        |  53 ++
 .../internal/publisher/IvyPublicationIdentity.java |  31 +
 .../ivy/internal/publisher/IvyPublisher.java       |  23 +
 .../internal/publisher/ValidatingIvyPublisher.java | 162 ++++
 .../api/publish/ivy/plugins/IvyPublishPlugin.java  |  91 ++-
 .../publish/ivy/tasks/GenerateIvyDescriptor.java   | 100 +--
 .../publish/ivy/tasks/PublishToIvyRepository.java  |  23 +-
 ...tionDynamicDescriptorGenerationTaskCreator.java |  83 --
 .../internal/IvyPublishDynamicTaskCreator.java     |  90 ---
 .../ivy/internal/DefaultIvyPublicationTest.groovy  | 114 ---
 .../IvyArtifactNotationParserFactoryTest.groovy    | 176 +++++
 ...namicDescriptorGenerationTaskCreatorTest.groovy |  88 +++
 .../IvyPublishDynamicTaskCreatorTest.groovy        | 104 +++
 .../publication/DefaultIvyPublicationTest.groovy   | 247 ++++++
 .../IvyDescriptorFileGeneratorTest.groovy          | 265 +++++++
 .../publisher/ValidatingIvyPublisherTest.groovy    | 259 +++++++
 .../ivy/plugins/IvyPublishPluginTest.groovy        |  65 +-
 .../ivy/tasks/PublishToIvyRepositoryTest.groovy    |   4 +-
 ...namicDescriptorGenerationTaskCreatorTest.groovy |  89 ---
 .../IvyPublishDynamicTaskCreatorTest.groovy        | 105 ---
 .../ConfigurationOnDemandIntegrationTest.groovy    |  52 ++
 ...EnablingParallelExecutionIntegrationTest.groovy |  49 ++
 .../daemon/DaemonFeedbackIntegrationSpec.groovy    |   2 +-
 .../launcher/daemon/DaemonLifecycleSpec.groovy     |   1 -
 .../daemon/SingleUseDaemonIntegrationTest.groovy   |   4 +-
 .../daemon/StoppingDaemonIntegrationSpec.groovy    |   2 +-
 .../gradle/launcher/cli/BuildActionsFactory.java   |  29 +-
 .../daemon/client/DaemonClientConnection.java      |   1 -
 .../daemon/configuration/DaemonParameters.java     | 110 +--
 .../daemon/configuration/GradleProperties.java     | 201 +++++
 .../configuration/GradlePropertiesConfigurer.java  |  48 ++
 .../daemon/server/exec/DaemonCommandExecution.java |  12 -
 .../server/exec/EstablishBuildEnvironment.java     |   4 +-
 .../internal/provider/ConfiguringBuildAction.java  |   7 +-
 .../DaemonGradleLauncherActionExecuter.java        |   5 +-
 .../internal/provider/DefaultConnection.java       |  28 +-
 .../provider/connection/BuildLogLevelMixIn.java    |   5 +-
 .../launcher/cli/BuildActionsFactoryTest.groovy    | 110 +--
 .../configuration/DaemonParametersTest.groovy      | 242 ++----
 .../GradlePropertiesConfigurerTest.groovy          |  77 ++
 .../configuration/GradlePropertiesTest.groovy      | 218 ++++++
 .../provider/ConfiguringBuildActionTest.groovy     |  12 +
 .../connection/BuildLogLevelMixInTest.groovy       |  70 ++
 subprojects/maven/maven.gradle                     |   2 +
 .../maven/MavenConversionIntegrationTest.groovy    |   8 +-
 .../maven/AbstractMavenPublishIntegTest.groovy     | 107 +++
 ...venPublishArtifactCustomisationIntegTest.groovy | 228 ++++++
 .../maven/MavenPublishBasicIntegTest.groovy        | 159 +++-
 .../MavenPublishCrossVersionIntegrationTest.groovy |  37 +-
 .../publish/maven/MavenPublishEarIntegTest.groovy  |  72 ++
 .../publish/maven/MavenPublishHttpIntegTest.groovy |   7 +-
 ...avenPublishIdentifierValidationIntegTest.groovy | 149 ++++
 .../maven/MavenPublishIssuesIntegTest.groovy       | 111 +++
 .../publish/maven/MavenPublishJavaIntegTest.groovy | 114 ++-
 .../maven/MavenPublishMultiProjectIntegTest.groovy | 129 ++--
 .../MavenPublishPomCustomisationIntegTest.groovy   |  94 ++-
 .../maven/MavenPublishWarProjectIntegTest.groovy   | 144 ++++
 .../SamplesMavenPublishIntegrationTest.groovy      |  72 +-
 .../gradle/api/plugins/MavenPluginConvention.java  |   2 +-
 .../plugins/MavenRepositoryHandlerConvention.java  |  39 +-
 .../api/plugins/maven/ConvertMaven2Gradle.groovy   |  23 +-
 .../maven/internal/MavenProjectXmlWriter.java      |  11 +-
 .../maven/internal/DefaultMavenPom.java            |   2 +-
 .../maven/internal/ant/AbstractMavenResolver.java  |  47 +-
 .../maven/internal/ant/CustomDeployTask.java       |  12 -
 .../ant/DefaultPomDependenciesConverter.java       |   6 +-
 .../internal/ant/NoInstallDeployTaskFactory.java   |   6 +
 .../maven/InvalidMavenPublicationException.java    |  40 +
 .../gradle/api/publish/maven/MavenArtifact.java    |  65 ++
 .../gradle/api/publish/maven/MavenArtifactSet.java |  62 ++
 .../org/gradle/api/publish/maven/MavenPom.java     |   7 +-
 .../gradle/api/publish/maven/MavenPublication.java | 187 ++++-
 .../publish/maven/internal/DefaultMavenPom.java    |  36 -
 .../maven/internal/DefaultMavenPublication.java    |  68 --
 .../maven/internal/MavenDeployerConfigurer.java    |  55 --
 .../maven/internal/MavenNormalizedPublication.java |  65 --
 .../publish/maven/internal/MavenPomInternal.java   |  27 -
 .../maven/internal/MavenProjectIdentity.java       |  29 -
 .../MavenProjectIdentityModuleAdapter.java         |  44 --
 .../maven/internal/MavenPublicationInternal.java   |  34 -
 .../internal/MavenPublishDynamicTaskCreator.java   |  85 ---
 .../MavenPublishLocalDynamicTaskCreator.java       |  64 --
 .../api/publish/maven/internal/MavenPublisher.java | 111 ---
 .../internal/ModuleBackedMavenProjectIdentity.java |  44 --
 .../internal/artifact/DefaultMavenArtifact.java    |  66 ++
 .../internal/artifact/DefaultMavenArtifactSet.java |  89 +++
 .../MavenArtifactNotationParserFactory.java        | 137 ++++
 .../internal/plugins/GeneratePomTaskCreator.java   |  71 ++
 .../plugins/MavenPublishDynamicTaskCreator.java    |  86 +++
 .../MavenPublishLocalDynamicTaskCreator.java       |  65 ++
 .../internal/publication/DefaultMavenPom.java      |  56 ++
 .../publication/DefaultMavenProjectIdentity.java   |  55 ++
 .../publication/DefaultMavenPublication.java       | 145 ++++
 .../internal/publication/MavenPomInternal.java     |  36 +
 .../publication/MavenPublicationInternal.java      |  45 ++
 .../publisher/AntTaskBackedMavenPublisher.java     | 127 ++++
 .../publisher/MavenDeployerConfigurer.java         |  55 ++
 .../publisher/MavenNormalizedPublication.java      |  53 ++
 .../internal/publisher/MavenProjectIdentity.java   |  33 +
 .../maven/internal/publisher/MavenPublisher.java   |  22 +
 .../publisher/StaticLockingMavenPublisher.java     |  43 ++
 .../publisher/ValidatingMavenPublisher.java        | 156 ++++
 .../internal/tasks/MavenPomFileGenerator.java      | 124 +++
 .../publish/maven/plugins/MavenPublishPlugin.java  | 102 +--
 .../api/publish/maven/tasks/GenerateMavenPom.java  | 114 +++
 .../publish/maven/tasks/PublishToMavenLocal.java   |   7 +-
 .../maven/tasks/PublishToMavenRepository.java      |  50 +-
 .../internal/MavenProjectXmlWriterTest.groovy      |  34 +
 ...ectDependencyArtifactIdExtractorHackTest.groovy |   2 +-
 .../ModuleBackedMavenProjectIdentityTest.groovy    |  57 --
 .../MavenArtifactNotationParserFactoryTest.groovy  | 162 ++++
 .../publication/DefaultMavenPublicationTest.groovy | 233 ++++++
 .../publisher/ValidatingMavenPublisherTest.groovy  | 236 ++++++
 .../tasks/MavenPomFileGeneratorTest.groovy         | 204 +++++
 .../maven/plugins/MavenPublishPluginTest.groovy    |  94 +--
 .../inet/TcpConnectorConcurrencyTest.groovy        |  85 ---
 .../jna/AbstractProcessEnvironment.java            |  92 ---
 .../gradle/internal/nativeplatform/jna/LibC.java   |   1 -
 .../jna/LibCBackedProcessEnvironment.java          |   1 +
 .../jna/WindowsProcessEnvironment.java             |   1 +
 .../AbstractProcessEnvironment.java                |  92 +++
 .../NativePlatformBackedProcessEnvironment.java    |  51 ++
 .../nativeplatform/services/NativeServices.java    |  40 +-
 .../jna/ProcessEnvironmentTest.groovy              |  79 --
 .../ProcessEnvironmentTest.groovy                  |  79 ++
 ...CrossVersionCompatibilityIntegrationTest.groovy |   2 +-
 .../integtests/openapi/GradleRunnerTest.groovy     |   2 +-
 .../gradle/integtests/openapi/OpenApiFixture.java  |   8 +-
 .../gradle/integtests/openapi/OpenApiUiTest.groovy |   4 +-
 .../integtests/openapi/OutputUILordTest.groovy     |   4 +-
 subprojects/performance/performance.gradle         |  11 +-
 .../performance/CleanBuildPerformanceTest.groovy   |  25 +-
 .../DependencyReportPerformanceTest.groovy         |  26 +-
 .../DependencyResolutionStressTest.groovy          |   2 +-
 .../IdeIntegrationPerformanceTest.groovy           |  55 +-
 .../TestExecutionPerformanceTest.groovy            |  30 +-
 .../UpToDateBuildPerformanceTest.groovy            |  24 +-
 .../fixture/AbstractPerformanceTest.groovy         |  27 +
 .../gradle/performance/fixture/DataReporter.groovy |  21 +
 .../fixture/PerformanceTestRunner.groovy           |  31 +-
 .../fixture/TextFileDataReporter.groovy            |  30 +
 subprojects/plugins/plugins.gradle                 |   2 +-
 .../api/plugins/BasePluginIntegrationTest.groovy   |   2 +-
 .../DistributionPluginIntegrationTest.groovy       | 351 +++++++++
 .../JavaLibraryDistributionIntegrationTest.groovy  | 156 ++--
 .../ParallelCompilerDaemonIntegrationTest.groovy   |   2 +-
 ...ntInProcessGroovyCompilerIntegrationTest.groovy |   2 +-
 .../ApiGroovyCompilerIntegrationSpec.groovy        |   4 +-
 .../BasicGroovyCompilerIntegrationSpec.groovy      |   7 +-
 .../IncrementalGroovyCompileIntegrationTest.groovy |   2 +-
 .../IncrementalJavaCompileIntegrationTest.groovy   |   2 +-
 .../gradle/javadoc/JavadocIntegrationTest.groovy   |   2 +-
 .../testing/TestEnvironmentIntegrationTest.groovy  |  12 +-
 .../TestOutputListenerIntegrationTest.groovy       |   8 +-
 .../testing/TestReportIntegrationTest.groovy       |  17 +-
 .../gradle/testing/TestingIntegrationTest.groovy   |   4 +-
 .../junit/JUnitCrossVersionIntegrationSpec.groovy  |   8 +-
 .../testing/junit/JUnitIntegrationTest.groovy      |  50 +-
 .../junit/JUnitLoggingIntegrationTest.groovy       |   8 +-
 .../testng/SampleTestNGIntegrationTest.groovy      |   6 +-
 .../testing/testng/TestNGIntegrationTest.groovy    |  29 +-
 .../testng/TestNGLoggingIntegrationTest.groovy     |   4 +-
 ...NGProducesJUnitXmlResultsIntegrationTest.groovy | 184 -----
 .../TestNGProducesOldReportsIntegrationTest.groovy |   8 +-
 ...tNGXmlResultAndHtmlReportIntegrationTest.groovy | 199 +++++
 .../src/test/java/org/gradle/Junit4Test.java       |   9 +
 .../src/test/java/org/gradle/NoTest.java           |  22 +
 .../supportsJunit3Suites/build.gradle              |  26 +
 .../src/test/java/org/gradle/SomeSuite.java        |  45 ++
 .../src/test/java/org/gradle/SomeTest1.java        |  24 +
 .../src/test/java/org/gradle/SomeTest2.java        |  24 +
 .../build.gradle                                   |   7 +
 .../test/java/org/gradle/SomeOtherTestSuite.java   |  25 +
 .../src/test/java/org/gradle/SomeTest.java         |  23 +
 .../src/test/java/org/gradle/SomeTestSuite.java    |  24 +
 .../supportsTestFactory/build.gradle               |  31 +
 .../test/java/org/gradle/factory/FactoryTest.java  |  33 +
 .../java/org/gradle/factory/TestNGFactory.java     |  29 +
 .../org/gradle/api/distribution/Distribution.java  |  55 ++
 .../api/distribution/DistributionContainer.java    |  26 +
 .../distribution/internal/DefaultDistribution.java |  59 ++
 .../internal/DefaultDistributionContainer.java     |  40 +
 .../org/gradle/api/distribution/package-info.java  |  20 +
 .../distribution/plugins/DistributionPlugin.groovy | 133 ++++
 .../org/gradle/api/internal/java/JavaLibrary.java  |  37 +-
 .../gradle/api/internal/java/WebApplication.java   |  57 ++
 .../internal/tasks/DefaultBinariesContainer.java   |  27 +
 .../tasks/DefaultClassDirectoryBinary.java         | 109 +++
 .../api/internal/tasks/DefaultClasspath.java       |  38 +
 .../internal/tasks/DefaultFunctionalSourceSet.java |  34 +
 .../api/internal/tasks/DefaultJavaSourceSet.java   |  72 ++
 .../internal/tasks/DefaultJvmBinaryContainer.java  |  36 +
 .../internal/tasks/DefaultProjectSourceSet.java    |  32 +
 .../api/internal/tasks/DefaultResourceSet.java     |  53 ++
 .../internal/tasks/SourceSetCompileClasspath.java  |  35 +
 .../compile/daemon/CompilerDaemonManager.java      |   5 +-
 .../tasks/testing/DecoratingTestDescriptor.java    |  12 +-
 .../junit/IgnoredTestDescriptorProvider.java       |  42 ++
 .../tasks/testing/junit/JUnitTestEventAdapter.java |  34 +-
 .../testing/junit/report/ClassPageRenderer.java    | 117 ++-
 .../testing/junit/report/DefaultTestReport.java    |   1 +
 .../testing/junit/report/OverviewPageRenderer.java | 107 +--
 .../testing/junit/report/PackagePageRenderer.java  |  68 +-
 .../tasks/testing/junit/report/PageRenderer.java   | 145 ++--
 .../junit/result/AbstractTestResultProvider.java   |  70 --
 .../junit/result/AggregateTestResultsProvider.java |   4 +
 .../BinaryResultBackedTestResultsProvider.java     |  21 +-
 .../testing/junit/result/CachingFileWriter.java    |  12 +-
 .../testing/junit/result/TestOutputSerializer.java |  90 +++
 .../junit/result/TestReportDataCollector.java      |  38 +-
 .../testing/junit/result/TestResultsProvider.java  |   2 +
 .../internal/tasks/testing/testng/TestNGSpec.java  |  97 +++
 .../testing/testng/TestNGTestClassProcessor.java   |  21 +-
 .../tasks/testing/testng/TestNGTestFramework.java  |  12 +-
 .../testing/testng/TestNGTestMethodDetecter.java   |   1 +
 .../org/gradle/api/java/archives/Manifest.java     |   2 +-
 .../api/java/archives/ManifestMergeSpec.java       |   2 +-
 .../java/archives/internal/DefaultManifest.java    |   2 +-
 .../api/plugins/DistributionExtension.groovy       |  46 --
 .../org/gradle/api/plugins/JavaBasePlugin.java     |  52 +-
 .../org/gradle/api/plugins/JavaLanguagePlugin.java |  76 ++
 .../plugins/JavaLibraryDistributionPlugin.groovy   |  36 +-
 .../groovy/org/gradle/api/plugins/JavaPlugin.java  |  20 +-
 .../org/gradle/api/plugins/JvmLanguagePlugin.java  | 128 ++++
 .../org/gradle/api/plugins/LanguageBasePlugin.java |  60 ++
 .../groovy/org/gradle/api/plugins/WarPlugin.java   |  10 +-
 .../org/gradle/api/tasks/BinariesContainer.java    |  27 +
 .../org/gradle/api/tasks/ClassDirectoryBinary.java |  49 ++
 .../groovy/org/gradle/api/tasks/Classpath.java     |  28 +
 .../org/gradle/api/tasks/FunctionalSourceSet.java  |  27 +
 .../groovy/org/gradle/api/tasks/JavaSourceSet.java |  24 +
 .../org/gradle/api/tasks/JvmBinaryContainer.java   |  27 +
 .../org/gradle/api/tasks/JvmLanguageSourceSet.java |  26 +
 .../org/gradle/api/tasks/LanguageSourceSet.java    |  33 +
 .../org/gradle/api/tasks/ProjectSourceSet.java     |  26 +
 .../groovy/org/gradle/api/tasks/ResourceSet.java   |  24 +
 .../groovy/org/gradle/api/tasks/SourceSet.java     |   4 +-
 .../org/gradle/api/tasks/bundling/War.groovy       |   2 +-
 .../api/tasks/testing/testng/TestNGOptions.groovy  |   4 +-
 .../javadoc/internal/JavadocOptionFileWriter.java  |   2 +-
 .../gradle-plugins/distribution.properties         |  17 +
 .../META-INF/gradle-plugins/java-lang.properties   |   1 +
 .../META-INF/gradle-plugins/jvm-lang.properties    |   1 +
 .../META-INF/gradle-plugins/lang-base.properties   |   1 +
 .../plugins/DistributionPluginTest.groovy          | 136 ++++
 .../junit/JUnitTestClassProcessorTest.groovy       |  61 +-
 .../junit/report/DefaultTestReportTest.groovy      |  76 +-
 .../junit/result/TestOutputSerializerTest.groovy   |  72 ++
 .../result/TestReportDataCollectorSpec.groovy      |  56 +-
 .../testng/TestNGTestClassProcessorTest.groovy     |  13 +-
 .../gradle/api/plugins/JavaBasePluginTest.groovy   |  59 +-
 .../api/plugins/JavaLanguagePluginTest.groovy      |  39 +
 .../JavaLibraryDistributionPluginTest.groovy       |  29 +-
 .../org/gradle/api/plugins/JavaPluginTest.groovy   |  13 +-
 .../api/plugins/JvmLanguagePluginTest.groovy       |  63 ++
 .../api/plugins/LanguageBasePluginTest.groovy      |  51 ++
 subprojects/publish/publish.gradle                 |   3 +
 .../org/gradle/api/publish/Publication.java        |   0
 .../gradle/api/publish/PublicationContainer.java   |  77 ++
 .../gradle/api/publish/PublishingExtension.java    | 111 +++
 .../internal/CompositePublicationFactory.java      |  39 +
 .../internal/DefaultPublicationContainer.java      |  53 ++
 .../internal/DefaultPublishingExtension.java       |  50 ++
 .../internal/GroovyPublicationContainer.groovy     |  35 +
 .../internal/PublicationContainerInternal.java     |  23 +
 .../api/publish/internal/PublicationFactory.java   |  22 +
 .../internal/PublicationFieldValidator.java        |  77 ++
 .../api/publish/internal/PublishOperation.java     |   0
 .../org/gradle/api/publish/package-info.java       |   0
 .../api/publish/plugins/PublishingPlugin.java      |  65 ++
 .../gradle/api/publish/plugins/package-info.java   |   0
 .../gradle/api/publish/PublicationContainer.java   |  46 --
 .../gradle/api/publish/PublishingExtension.java    | 100 ---
 .../api/publish/UnknownPublicationException.java   |  32 -
 .../internal/DefaultPublicationContainer.java      |  36 -
 .../internal/DefaultPublishingExtension.java       |  49 --
 .../api/publish/plugins/PublishingPlugin.java      |  58 --
 .../DefaultPublicationContainerTest.groovy         | 104 ++-
 subprojects/reporting/reporting.gradle             |   4 +
 .../BuildDashboardPluginIntegrationTest.groovy     | 151 ++++
 .../api/reporting/BuildDashboardReports.java       |  32 +
 .../api/reporting/GenerateBuildDashboard.java      | 132 ++++
 .../groovy/org/gradle/api/reporting/Report.java    |   7 +
 .../internal/BuildDashboardGenerator.java          |  97 +++
 .../internal/DefaultBuildDashboardReports.java     |  34 +
 .../api/reporting/internal/SimpleReport.java       |   8 +-
 .../reporting/internal/TaskGeneratedReport.java    |   6 +-
 .../reporting/plugins/BuildDashboardPlugin.groovy  |  58 ++
 .../gradle/api/reporting/plugins/package-info.java |  20 +
 .../gradle-plugins/build-dashboard.properties      |   1 +
 .../reporting/GenerateBuildDashboardSpec.groovy    |  35 +
 .../internal/BuildDashboardGeneratorSpec.groovy    |  95 +++
 .../internal/DefaultReportContainerTest.groovy     |   4 +-
 subprojects/scala/scala.gradle                     |   2 +-
 .../IncrementalScalaCompileIntegrationTest.groovy  |   2 +-
 ...tForkingScalaCompilerJdk6IntegrationTest.groovy |   3 +-
 ...nProcessScalaCompilerJdk6IntegrationTest.groovy |   2 +-
 .../ZincScalaCompilerJdk6IntegrationTest.groovy    |   4 +-
 .../scala/test/ScalaTestIntegrationTest.groovy     |   6 +-
 .../internal/tasks/scala/AntScalaCompiler.groovy   |   8 +-
 .../tasks/scala/jdk6/ZincScalaCompiler.java        |   2 +-
 .../api/plugins/scala/ScalaBasePlugin.groovy       | 111 ++-
 .../api/plugins/scala/ScalaBasePluginTest.groovy   |  67 ++
 .../plugins/signing/SigningIntegrationSpec.groovy  |   3 +-
 .../plugins/signing/SigningSamplesSpec.groovy      |   9 +-
 subprojects/sonar/sonar.gradle                     |  19 +-
 .../plugins/sonar/SonarSmokeIntegrationTest.groovy |  10 +-
 .../runner/SonarRunnerSmokeIntegrationTest.groovy  |  84 +++
 .../SonarSmokeIntegrationTest/shared/build.gradle  |  14 +-
 .../shared/customizedProject/build.gradle          |  14 +
 .../gradle/test/customizedProject/Production1.java |  14 +
 .../org/gradle/test/customizedProject/Test1.java   |  12 +
 .../shared/gradle.properties                       |   1 +
 .../shared/javaProject/build.gradle                |   2 +
 .../org/gradle/test/javaProject/Production1.java   |  14 +
 .../org/gradle/test/javaProject/Production10.java  |  14 +
 .../org/gradle/test/javaProject/Production2.java   |  14 +
 .../org/gradle/test/javaProject/Production3.java   |  14 +
 .../org/gradle/test/javaProject/Production4.java   |  14 +
 .../org/gradle/test/javaProject/Production5.java   |  14 +
 .../org/gradle/test/javaProject/Production6.java   |  14 +
 .../org/gradle/test/javaProject/Production7.java   |  14 +
 .../org/gradle/test/javaProject/Production8.java   |  14 +
 .../org/gradle/test/javaProject/Production9.java   |  14 +
 .../gradle/test/javaProject/productionResource.xml |   6 +
 .../java/org/gradle/test/javaProject/Test1.java    |  12 +
 .../java/org/gradle/test/javaProject/Test10.java   |  12 +
 .../java/org/gradle/test/javaProject/Test2.java    |  12 +
 .../java/org/gradle/test/javaProject/Test3.java    |  12 +
 .../java/org/gradle/test/javaProject/Test4.java    |  12 +
 .../java/org/gradle/test/javaProject/Test5.java    |  12 +
 .../java/org/gradle/test/javaProject/Test6.java    |  12 +
 .../java/org/gradle/test/javaProject/Test7.java    |  12 +
 .../java/org/gradle/test/javaProject/Test8.java    |  12 +
 .../java/org/gradle/test/javaProject/Test9.java    |  12 +
 .../org/gradle/test/javaProject/testResource.xml   |   6 +
 .../nested/nested2/nestedProject/build.gradle      |   1 +
 .../org/gradle/test/nestedProject/Production1.java |  14 +
 .../java/org/gradle/test/nestedProject/Test1.java  |  12 +
 .../shared/settings.gradle                         |   3 +
 .../shared/skippedProject/build.gradle             |   3 +
 .../gradle/test/skippedProject/Production1.java    |  14 +
 .../java/org/gradle/test/skippedProject/Test1.java |  12 +
 .../shared/build.gradle                            |  20 +
 .../shared/customizedProject/build.gradle          |  14 +
 .../gradle/test/customizedProject/Production1.java |  14 +
 .../org/gradle/test/customizedProject/Test1.java   |  12 +
 .../shared/gradle.properties                       |   1 +
 .../shared/groovyProject/build.gradle              |  14 +
 .../test/groovyProject/ProductionGroovy1.groovy    |  13 +
 .../test/groovyProject/ProductionGroovy10.groovy   |  13 +
 .../test/groovyProject/ProductionGroovy2.groovy    |  13 +
 .../test/groovyProject/ProductionGroovy3.groovy    |  13 +
 .../test/groovyProject/ProductionGroovy4.groovy    |  13 +
 .../test/groovyProject/ProductionGroovy5.groovy    |  13 +
 .../test/groovyProject/ProductionGroovy6.groovy    |  13 +
 .../test/groovyProject/ProductionGroovy7.groovy    |  13 +
 .../test/groovyProject/ProductionGroovy8.groovy    |  13 +
 .../test/groovyProject/ProductionGroovy9.groovy    |  13 +
 .../gradle/test/groovyProject/TestGroovy1.groovy   |  12 +
 .../gradle/test/groovyProject/TestGroovy10.groovy  |  12 +
 .../gradle/test/groovyProject/TestGroovy2.groovy   |  12 +
 .../gradle/test/groovyProject/TestGroovy3.groovy   |  12 +
 .../gradle/test/groovyProject/TestGroovy4.groovy   |  12 +
 .../gradle/test/groovyProject/TestGroovy5.groovy   |  12 +
 .../gradle/test/groovyProject/TestGroovy6.groovy   |  12 +
 .../gradle/test/groovyProject/TestGroovy7.groovy   |  12 +
 .../gradle/test/groovyProject/TestGroovy8.groovy   |  12 +
 .../gradle/test/groovyProject/TestGroovy9.groovy   |  12 +
 .../shared/javaProject/build.gradle                |   2 +
 .../org/gradle/test/javaProject/Production1.java   |  14 +
 .../org/gradle/test/javaProject/Production10.java  |  14 +
 .../org/gradle/test/javaProject/Production2.java   |  14 +
 .../org/gradle/test/javaProject/Production3.java   |  14 +
 .../org/gradle/test/javaProject/Production4.java   |  14 +
 .../org/gradle/test/javaProject/Production5.java   |  14 +
 .../org/gradle/test/javaProject/Production6.java   |  14 +
 .../org/gradle/test/javaProject/Production7.java   |  14 +
 .../org/gradle/test/javaProject/Production8.java   |  14 +
 .../org/gradle/test/javaProject/Production9.java   |  14 +
 .../gradle/test/javaProject/productionResource.xml |   6 +
 .../java/org/gradle/test/javaProject/Test1.java    |  12 +
 .../java/org/gradle/test/javaProject/Test10.java   |  12 +
 .../java/org/gradle/test/javaProject/Test2.java    |  12 +
 .../java/org/gradle/test/javaProject/Test3.java    |  12 +
 .../java/org/gradle/test/javaProject/Test4.java    |  12 +
 .../java/org/gradle/test/javaProject/Test5.java    |  12 +
 .../java/org/gradle/test/javaProject/Test6.java    |  12 +
 .../java/org/gradle/test/javaProject/Test7.java    |  12 +
 .../java/org/gradle/test/javaProject/Test8.java    |  12 +
 .../java/org/gradle/test/javaProject/Test9.java    |  12 +
 .../org/gradle/test/javaProject/testResource.xml   |   6 +
 .../nested/nested2/nestedProject/build.gradle      |   1 +
 .../org/gradle/test/nestedProject/Production1.java |  14 +
 .../java/org/gradle/test/nestedProject/Test1.java  |  12 +
 .../shared/settings.gradle                         |   3 +
 .../shared/skippedProject/build.gradle             |   3 +
 .../gradle/test/skippedProject/Production1.java    |  14 +
 .../java/org/gradle/test/skippedProject/Test1.java |  12 +
 .../gradle/api/sonar/runner/SonarProperties.groovy |  56 ++
 .../org/gradle/api/sonar/runner/SonarRunner.groovy |  54 ++
 .../api/sonar/runner/SonarRunnerExtension.groovy   |  78 ++
 .../api/sonar/runner/SonarRunnerPlugin.groovy      | 222 ++++++
 .../gradle-plugins/sonar-runner.properties         |   1 +
 .../api/sonar/runner/SonarPropertiesTest.groovy    |  48 ++
 .../sonar/runner/SonarRunnerExtensionTest.groovy   |  43 ++
 .../api/sonar/runner/SonarRunnerPluginTest.groovy  | 373 +++++++++
 .../SamplesToolingApiIntegrationTest.groovy        |   2 +-
 .../tooling/ToolingApiIntegrationTest.groovy       |   7 +-
 .../tooling/fixture/ConfigurableOperation.groovy   |  24 +-
 .../tooling/fixture/ToolingApiSpecification.groovy |  18 +
 ...ToolingApiBuildExecutionCrossVersionSpec.groovy |  15 +-
 .../m5/ToolingApiModelCrossVersionSpec.groovy      |  15 +-
 .../m8/GradlePropertiesCrossVersionSpec.groovy     |  72 --
 ...adlePropertiesToolingApiCrossVersionSpec.groovy |  72 ++
 .../m8/ToolingApiLoggingCrossVersionSpec.groovy    |  26 +-
 ...singCommandLineArgumentsCrossVersionSpec.groovy |  15 +-
 ...ApiInitScriptCrossVersionIntegrationTest.groovy |   7 +-
 ...ningCommandLineArgumentsCrossVersionSpec.groovy |  55 ++
 ...ApiConfigurationOnDemandCrossVersionSpec.groovy |  66 ++
 .../org/gradle/tooling/LongRunningOperation.java   |   4 +-
 .../tooling/internal/idea/DefaultIdeaModule.java   |  12 -
 .../integtests/LiveOutputIntegrationTest.groovy    |   2 +-
 ...projectProjectAndTaskListIntegrationTest.groovy |   2 +-
 .../gradleplugin/foundation/GradlePluginLord.java  |   3 +-
 .../userinterface/swing/generic/tabs/SetupTab.java |   2 +-
 .../swing/standalone/Application.java              |   3 +-
 version.txt                                        |   2 +-
 1174 files changed, 36248 insertions(+), 15616 deletions(-)

diff --git a/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy b/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
index aa65af4..4490268 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/build/BuildTypes.groovy
@@ -41,21 +41,12 @@ class BuildTypes {
         if (args.first() instanceof Map) {
             properties.putAll(args.remove(0))
         }
-        def tasks = []
-        def configure = {}
+        def tasks = args*.toString()
 
-        args.each {
-            if (it instanceof Closure) {
-                configure = it
-            } else {
-                tasks << it.toString()
-            }
-        }
-
-        register(name, tasks, properties, configure)
+        register(name, tasks, properties)
     }
 
-    private register(name, tasks, projectProperties, configure) {
+    private register(name, tasks, projectProperties) {
         project.task(name) {
             group = "Build Type"
             def abbreviation = name[0] + name[1..-1].replaceAll("[a-z]", "")
@@ -75,7 +66,6 @@ class BuildTypes {
                     }
                     project.properties."$k" = v
                 }
-                project.configure(project.gradle.startParameter, configure)
             }
 
             doFirst {
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverter.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverter.java
index e20e8e1..38c99b3 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverter.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverter.java
@@ -113,6 +113,10 @@ public class JavadocLinkConverter {
             methodSignature = signature.toString();
         }
 
+        if (targetClass.isEnum() && targetClass.getEnumConstant(methodSignature) != null) {
+            return linkRenderer.link(targetClass.getEnumConstant(methodSignature), listener);
+        }
+
         MethodMetaData method = findMethod(methodSignature, targetClass);
         if (method == null) {
             return null;
@@ -165,6 +169,10 @@ public class JavadocLinkConverter {
             return element;
         }
 
+        return createLiteralNode(value);
+    }
+
+    private Node createLiteralNode(String value) {
         Element element = document.createElement("literal");
         element.appendChild(document.createTextNode(value));
         return element;
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/LinkRenderer.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/LinkRenderer.java
index 5b8f2da..8421d29 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/LinkRenderer.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/LinkRenderer.java
@@ -16,6 +16,7 @@
 package org.gradle.build.docs.dsl.docbook;
 
 import org.apache.commons.lang.StringUtils;
+import org.gradle.build.docs.dsl.source.model.EnumConstantMetaData;
 import org.gradle.build.docs.dsl.source.model.MethodMetaData;
 import org.gradle.build.docs.dsl.source.model.TypeMetaData;
 import org.w3c.dom.Document;
@@ -120,4 +121,18 @@ public class LinkRenderer {
             return element;
         }
     }
+
+    public Node link(EnumConstantMetaData enumConstant, GenerationListener listener) {
+        if (model.isKnownType(enumConstant.getOwnerClass().getClassName())) {
+            Element apilink = document.createElement("apilink");
+            apilink.setAttribute("class", enumConstant.getOwnerClass().getClassName());
+            apilink.setAttribute("method", enumConstant.getName());
+            return apilink;
+        } else {
+            listener.warning(String.format("Could not generate link for enum constant %s", enumConstant));
+            Element element = document.createElement("UNKNOWN-ENUM");
+            element.appendChild(document.createTextNode(enumConstant.toString()));
+            return element;
+        }
+    }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/ClassLinkMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/ClassLinkMetaData.java
index a831f42..f310ff5 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/ClassLinkMetaData.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/links/ClassLinkMetaData.java
@@ -16,6 +16,7 @@
 package org.gradle.build.docs.dsl.links;
 
 import org.gradle.build.docs.dsl.source.model.ClassMetaData;
+import org.gradle.build.docs.dsl.source.model.EnumConstantMetaData;
 import org.gradle.build.docs.dsl.source.model.MethodMetaData;
 import org.gradle.build.docs.model.Attachable;
 import org.gradle.build.docs.model.ClassMetaDataRepository;
@@ -42,6 +43,9 @@ public class ClassLinkMetaData implements Serializable, Attachable<ClassLinkMeta
         for (MethodMetaData method : classMetaData.getDeclaredMethods()) {
             addMethod(method, style);
         }
+        for (EnumConstantMetaData enumConstant : classMetaData.getEnumConstants()) {
+            addEnumConstant(enumConstant, style);
+        }
     }
 
     public LinkMetaData getClassLink() {
@@ -101,6 +105,11 @@ public class ClassLinkMetaData implements Serializable, Attachable<ClassLinkMeta
         methods.put(method.getOverrideSignature(), new MethodLinkMetaData(method.getName(), method.getOverrideSignature(), style));
     }
 
+    public void addEnumConstant(EnumConstantMetaData enumConstant, LinkMetaData.Style style) {
+        String name = enumConstant.getName();
+        methods.put(name, new EnumConstantLinkMetaData(name, style));
+    }
+
     public void addBlockMethod(MethodMetaData method) {
         methods.put(method.getOverrideSignature(), new BlockLinkMetaData(method.getName(), method.getOverrideSignature()));
     }
@@ -161,4 +170,15 @@ public class ClassLinkMetaData implements Serializable, Attachable<ClassLinkMeta
             return String.format("%s:%s", className, propertyName);
         }
     }
+
+    private static class EnumConstantLinkMetaData extends MethodLinkMetaData {
+        private EnumConstantLinkMetaData(String name, LinkMetaData.Style style) {
+            super(name, name, style);
+        }
+
+        @Override
+        public String getDisplayName() {
+            return name;
+        }
+    }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java
index 40915fc..b0384aa 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java
@@ -22,6 +22,7 @@ import org.codehaus.groovy.antlr.LineColumn;
 import org.codehaus.groovy.antlr.SourceBuffer;
 import org.codehaus.groovy.antlr.treewalker.VisitorAdapter;
 import org.gradle.build.docs.dsl.source.model.*;
+import org.gradle.build.docs.dsl.source.model.ClassMetaData.MetaType;
 import org.gradle.build.docs.model.ClassMetaDataRepository;
 
 import java.lang.reflect.Modifier;
@@ -78,32 +79,32 @@ public class SourceMetaDataVisitor extends VisitorAdapter {
 
     @Override
     public void visitClassDef(GroovySourceAST t, int visit) {
-        visitTypeDef(t, visit, false);
+        visitTypeDef(t, visit, MetaType.CLASS);
     }
 
     @Override
     public void visitInterfaceDef(GroovySourceAST t, int visit) {
-        visitTypeDef(t, visit, true);
+        visitTypeDef(t, visit, MetaType.INTERFACE);
     }
 
     @Override
     public void visitEnumDef(GroovySourceAST t, int visit) {
-        visitTypeDef(t, visit, false);
+        visitTypeDef(t, visit, MetaType.ENUM);
     }
 
     @Override
     public void visitAnnotationDef(GroovySourceAST t, int visit) {
-        visitTypeDef(t, visit, false);
+        visitTypeDef(t, visit, MetaType.ANNOTATION);
     }
 
-    private void visitTypeDef(GroovySourceAST t, int visit, boolean isInterface) {
+    private void visitTypeDef(GroovySourceAST t, int visit, ClassMetaData.MetaType metaType) {
         if (visit == OPENING_VISIT) {
             ClassMetaData outerClass = getCurrentClass();
             String baseName = extractIdent(t);
             String className = outerClass != null ? outerClass.getClassName() + '.' + baseName
                     : packageName + '.' + baseName;
             String comment = getJavaDocCommentsBeforeNode(t);
-            ClassMetaData currentClass = new ClassMetaData(className, packageName, isInterface, groovy, comment);
+            ClassMetaData currentClass = new ClassMetaData(className, packageName, metaType, groovy, comment);
             if (outerClass != null) {
                 outerClass.addInnerClassName(className);
                 currentClass.setOuterClassName(outerClass.getClassName());
@@ -149,6 +150,15 @@ public class SourceMetaDataVisitor extends VisitorAdapter {
     }
 
     @Override
+    public void visitEnumConstantDef(GroovySourceAST t, int visit) {
+        if (visit == OPENING_VISIT) {
+            String name = extractName(t);
+            getCurrentClass().addEnumConstant(name);
+            skipJavaDocComment(t);
+        }
+    }
+
+    @Override
     public void visitMethodDef(GroovySourceAST t, int visit) {
         if (visit == OPENING_VISIT) {
             maybeAddMethod(t);
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaData.java
index dab28f9..d2d4aff 100644
--- a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaData.java
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/ClassMetaData.java
@@ -32,7 +32,7 @@ public class ClassMetaData extends AbstractLanguageElement implements Serializab
     private final String className;
     private String superClassName;
     private final String packageName;
-    private final boolean isInterface;
+    private final MetaType metaType;
     private final boolean isGroovy;
     private final List<String> imports = new ArrayList<String>();
     private final List<String> interfaceNames = new ArrayList<String>();
@@ -42,17 +42,18 @@ public class ClassMetaData extends AbstractLanguageElement implements Serializab
     private String outerClassName;
     private transient ClassMetaDataRepository<ClassMetaData> metaDataRepository;
     public final HashMap<String, String> constants = new HashMap<String, String>();
+    private final List<EnumConstantMetaData> enumConstants = new ArrayList<EnumConstantMetaData>();
 
-    public ClassMetaData(String className, String packageName, boolean isInterface, boolean isGroovy, String rawClassComment) {
+    public ClassMetaData(String className, String packageName, MetaType metaType, boolean isGroovy, String rawClassComment) {
         super(rawClassComment);
         this.className = className;
         this.packageName = packageName;
-        this.isInterface = isInterface;
+        this.metaType = metaType;
         this.isGroovy = isGroovy;
     }
 
     public ClassMetaData(String className) {
-        this(className, StringUtils.substringBeforeLast(className, "."), false, false, "");
+        this(className, StringUtils.substringBeforeLast(className, "."), MetaType.CLASS, false, "");
     }
 
     @Override
@@ -73,13 +74,17 @@ public class ClassMetaData extends AbstractLanguageElement implements Serializab
     }
 
     public boolean isInterface() {
-        return isInterface;
+        return metaType == MetaType.INTERFACE;
     }
 
     public boolean isGroovy() {
         return isGroovy;
     }
 
+    public boolean isEnum() {
+        return metaType == MetaType.ENUM;
+    }
+
     public String getSuperClassName() {
         return superClassName;
     }
@@ -243,7 +248,24 @@ public class ClassMetaData extends AbstractLanguageElement implements Serializab
     public Map<String, String> getConstants() {
         return constants;
     }
-    
+
+    public void addEnumConstant(String name) {
+        enumConstants.add(new EnumConstantMetaData(name, this));
+    }
+
+    public List<EnumConstantMetaData> getEnumConstants() {
+        return enumConstants;
+    }
+
+    public EnumConstantMetaData getEnumConstant(String name) {
+        for (EnumConstantMetaData enumConstant : enumConstants) {
+            if (enumConstant.getName().equals(name)) {
+                return enumConstant;
+            }
+        }
+        return null;
+    }
+
     public void attach(ClassMetaDataRepository<ClassMetaData> metaDataRepository) {
         this.metaDataRepository = metaDataRepository;
     }
@@ -280,4 +302,8 @@ public class ClassMetaData extends AbstractLanguageElement implements Serializab
             methodMetaData.visitTypes(action);
         }
     }
+
+    public static enum MetaType {
+        CLASS, INTERFACE, ENUM, ANNOTATION
+    }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/EnumConstantMetaData.java b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/EnumConstantMetaData.java
new file mode 100644
index 0000000..7f73440
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/source/model/EnumConstantMetaData.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.build.docs.dsl.source.model;
+
+import java.io.Serializable;
+
+public class EnumConstantMetaData extends AbstractLanguageElement implements Serializable {
+    private final String name;
+    private final ClassMetaData ownerClass;
+
+    public EnumConstantMetaData(String name, ClassMetaData ownerClass) {
+        this.name = name;
+        this.ownerClass = ownerClass;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ClassMetaData getOwnerClass() {
+        return ownerClass;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s.%s()", ownerClass, name);
+    }
+
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/Jsoup.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/Jsoup.groovy
deleted file mode 100644
index 414e29c..0000000
--- a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/Jsoup.groovy
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.plugins.jsoup
-
-import org.gradle.api.tasks.SourceTask
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.OutputFile
-import org.gradle.util.ConfigureUtil
-import org.jsoup.nodes.Document
-
-class Jsoup extends SourceTask {
-    
-    @Input
-    String inputEncoding
-
-    @Input
-    String outputEncoding
-
-    List<Closure> transforms = []
-
-    private destination
-
-    void setDestination(destination) {
-        this.destination = destination
-    }
-
-    @OutputFile
-    File getDestination() {
-        project.file(destination)
-    }
-    
-    @TaskAction 
-    void doTransform() {
-        Document document = org.jsoup.Jsoup.parse(getSource().singleFile.getText(getInputEncoding()))
-        document.outputSettings().charset(getOutputEncoding())
-        getTransforms().each { ConfigureUtil.configure(it, document) }
-        getDestination().setText(document.toString(), getOutputEncoding())
-    }
-
-    void transform(Closure transform) {
-        this.transforms << transform
-    }
-    
-}
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupCopyExtension.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupCopyExtension.groovy
new file mode 100644
index 0000000..6b11854
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupCopyExtension.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.jsoup
+
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.TaskInputs
+import org.gradle.api.Action
+import org.gradle.api.Project
+import org.gradle.api.file.FileCopyDetails
+import org.gradle.api.internal.ClosureBackedAction
+import org.jsoup.nodes.Document
+
+class JsoupCopyExtension {
+
+    public static final List<String> DEFAULT_TRANSFORM_EXTENSIONS = ["html"].asImmutable()
+
+    final Copy task
+
+    JsoupCopyExtension(Copy task) {
+        this.task = task
+    }
+
+    void plugins(Object... plugins) {
+        task.project.files(plugins).each {
+            task.inputs.file(it)
+            task.project.apply from: it, to: this
+        }
+    }
+
+    void transform(Action<JsoupTransformTarget> action) {
+        transform(DEFAULT_TRANSFORM_EXTENSIONS as String[], action)
+    }
+
+    void transform(String[] extensions, Closure action) {
+        transform(extensions, new ClosureBackedAction(action))
+    }
+
+    void transform(String[] extensions, Action<JsoupTransformTarget> action) {
+        task.eachFile { FileCopyDetails fcd ->
+            if (extensions.any { fcd.name.endsWith(".$it") }) {
+                fcd.filter(fileCopyDetails: fcd, action: action, JsoupFilterReader)
+            }
+        }
+    }
+
+    void transformDocument(Action<Document> action) {
+        transformDocument(DEFAULT_TRANSFORM_EXTENSIONS as String[], action)
+    }
+
+    void transformDocument(String[] extensions, Closure action) {
+        transformDocument(extensions, new ClosureBackedAction(action))
+    }
+
+    void transformDocument(String[] extensions, Action<Document> action) {
+        transform(extensions, new Action<JsoupTransformTarget>() {
+            void execute(JsoupTransformTarget target) {
+                action.execute(target.document)
+            }
+        })
+    }
+
+    Project getProject() {
+        task.project
+    }
+
+    TaskInputs getInputs() {
+        task.inputs
+    }
+
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy
new file mode 100644
index 0000000..6c51efb
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupFilterReader.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.jsoup
+
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import org.gradle.api.Action
+import org.gradle.api.file.FileCopyDetails
+
+class JsoupFilterReader extends FilterReader {
+
+    FileCopyDetails fileCopyDetails
+    Action<JsoupTransformTarget> action
+
+    JsoupFilterReader(Reader reader) {
+        super(new DeferringReader(reader));
+        this.in.parent = this
+    }
+
+    private static class DeferringReader extends Reader {
+        private final Reader source
+        private Reader delegate
+        private JsoupFilterReader parent
+
+        DeferringReader(Reader source) {
+            this.source = source
+        }
+
+        int read(char[] cbuf, int off, int len) {
+            if (delegate == null) {
+                Document document = Jsoup.parse(source.text)
+                JsoupTransformTarget target = new JsoupTransformTarget(document, parent.fileCopyDetails)
+                parent.action.execute(target)
+                delegate = new StringReader(document.toString())
+            }
+
+            delegate.read(cbuf, off, len)
+        }
+
+        void close() {}
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupPlugin.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupPlugin.groovy
index 65039d8..896adaf 100644
--- a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupPlugin.groovy
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupPlugin.groovy
@@ -18,16 +18,13 @@ package org.gradle.plugins.jsoup
 
 import org.gradle.api.Project
 import org.gradle.api.Plugin
-import java.nio.charset.Charset
+import org.gradle.api.tasks.Copy
 
 class JsoupPlugin implements Plugin<Project> {
 
     void apply(Project project) {
-        project.tasks.withType(Jsoup) { Jsoup task ->
-            task.conventionMapping.with {
-                inputEncoding = { Charset.defaultCharset().name() }
-                outputEncoding = { task.inputEncoding }
-            }
+        project.tasks.withType(Copy) { Copy task ->
+            task.extensions.create("jsoup", JsoupCopyExtension, task)
         }
     }
 }
diff --git a/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupTransformTarget.groovy b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupTransformTarget.groovy
new file mode 100644
index 0000000..5b2c83f
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/plugins/jsoup/JsoupTransformTarget.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.jsoup
+
+import org.jsoup.nodes.Document
+import org.gradle.api.file.FileCopyDetails
+
+class JsoupTransformTarget {
+
+    final Document document
+    final FileCopyDetails fileCopyDetails
+
+    JsoupTransformTarget(Document document, FileCopyDetails fileCopyDetails) {
+        this.document = document
+        this.fileCopyDetails = fileCopyDetails
+    }
+
+}
\ No newline at end of file
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverterTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverterTest.groovy
index 4141ede..bbdc46a 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverterTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/docbook/JavadocLinkConverterTest.groovy
@@ -15,11 +15,12 @@
  */
 package org.gradle.build.docs.dsl.docbook
 
-import org.gradle.build.docs.dsl.source.TypeNameResolver
 import org.gradle.build.docs.XmlSpecification
+import org.gradle.build.docs.dsl.source.TypeNameResolver
 import org.gradle.build.docs.dsl.source.model.ClassMetaData
-import org.gradle.build.docs.model.ClassMetaDataRepository
+import org.gradle.build.docs.dsl.source.model.EnumConstantMetaData
 import org.gradle.build.docs.dsl.source.model.MethodMetaData
+import org.gradle.build.docs.model.ClassMetaDataRepository
 
 class JavadocLinkConverterTest extends XmlSpecification {
     final LinkRenderer linkRenderer = Mock()
@@ -150,6 +151,22 @@ class JavadocLinkConverterTest extends XmlSpecification {
         format(link) == '<someLinkElement/>'
     }
 
+    def convertsEnumConstantLinkToLiteralValue() {
+        ClassMetaData enumClass = Mock()
+        EnumConstantMetaData enumConstant = new EnumConstantMetaData("SOME_ENUM_VALUE", enumClass)
+
+        when:
+        def link = converter.resolve('SomeName#SOME_ENUM_VALUE', classMetaData, listener)
+
+        then:
+        format(link) == '<someLinkElement/>'
+        _ * nameResolver.resolve('SomeName', classMetaData) >> 'org.gradle.SomeName'
+        _ * repository.find('org.gradle.SomeName') >> enumClass
+        _ * enumClass.enum >> true
+        _ * enumClass.getEnumConstant("SOME_ENUM_VALUE") >> enumConstant
+        _ * linkRenderer.link(enumConstant, listener) >> parse('<someLinkElement/>')
+    }
+
     def convertsValueLinkToLiteralValue() {
         ClassMetaData otherClass = Mock()
 
diff --git a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTaskTest.groovy b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTaskTest.groovy
index 5c43341..0d92bb6 100644
--- a/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTaskTest.groovy
+++ b/buildSrc/src/test/groovy/org/gradle/build/docs/dsl/source/ExtractDslMetaDataTaskTest.groovy
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 package org.gradle.build.docs.dsl.source
-
 import org.gradle.api.Project
 import org.gradle.build.docs.dsl.source.model.ClassMetaData
 import org.gradle.build.docs.model.SimpleClassMetaDataRepository
@@ -46,6 +45,7 @@ class ExtractDslMetaDataTaskTest extends Specification {
         def groovyClass = repository.get('org.gradle.test.GroovyClass')
         groovyClass.groovy
         !groovyClass.isInterface()
+        !groovyClass.enum
         groovyClass.rawCommentText.contains('This is a groovy class.')
         groovyClass.superClassName == 'org.gradle.test.A'
         groovyClass.interfaceNames == ['org.gradle.test.GroovyInterface', 'org.gradle.test.JavaInterface']
@@ -54,6 +54,7 @@ class ExtractDslMetaDataTaskTest extends Specification {
         def groovyInterface = repository.get('org.gradle.test.GroovyInterface')
         groovyInterface.groovy
         groovyInterface.isInterface()
+        !groovyInterface.enum
         groovyInterface.superClassName == null
         groovyInterface.interfaceNames == ['org.gradle.test.Interface1', 'org.gradle.test.Interface2']
         groovyInterface.annotationTypeNames == []
@@ -75,6 +76,7 @@ class ExtractDslMetaDataTaskTest extends Specification {
         def javaClass = repository.get('org.gradle.test.JavaClass')
         !javaClass.groovy
         !javaClass.isInterface()
+        !javaClass.enum
         javaClass.rawCommentText.contains('This is a java class.')
         javaClass.superClassName == 'org.gradle.test.A'
         javaClass.interfaceNames == ['org.gradle.test.GroovyInterface', 'org.gradle.test.JavaInterface']
@@ -83,6 +85,7 @@ class ExtractDslMetaDataTaskTest extends Specification {
         def javaInterface = repository.get('org.gradle.test.JavaInterface')
         !javaInterface.groovy
         javaInterface.isInterface()
+        !javaInterface.enum
         javaInterface.superClassName == null
         javaInterface.interfaceNames == ['org.gradle.test.Interface1', 'org.gradle.test.Interface2']
         javaInterface.annotationTypeNames == []
@@ -438,6 +441,12 @@ class ExtractDslMetaDataTaskTest extends Specification {
         def groovyEnum = repository.get('org.gradle.test.GroovyEnum')
         groovyEnum.groovy
         !groovyEnum.isInterface()
+        groovyEnum.enum
+
+        and:
+        groovyEnum.getEnumConstant("A") != null
+        groovyEnum.getEnumConstant("B") != null
+        groovyEnum.getEnumConstant("C") == null
     }
 
     def handlesEnumTypesInJavaSource() {
@@ -451,6 +460,12 @@ class ExtractDslMetaDataTaskTest extends Specification {
         def javaEnum = repository.get('org.gradle.test.JavaEnum')
         !javaEnum.groovy
         !javaEnum.isInterface()
+        javaEnum.enum
+
+        and:
+        javaEnum.getEnumConstant("A") != null
+        javaEnum.getEnumConstant("B") != null
+        javaEnum.getEnumConstant("C") == null
     }
 
     def handlesAnnotationTypesInGroovySource() {
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index 4c39c9a..6b0b710 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -84,9 +84,11 @@
         <module name="ConstantName"/>
         <module name="LocalFinalVariableName"/>
         <module name="LocalVariableName"/>
-        <module name="MemberName"/>
+        <module name="MemberName">
+            <property name="format" value="^[a-z_][a-zA-Z0-9_]*$"/>
+        </module>
         <module name="MethodName">
-            <property name="format" value="^[a-z][a-zA-Z0-9_]*$"/>
+            <property name="format" value="^[a-z_][a-zA-Z0-9_]*$"/>
         </module>
         <module name="MethodTypeParameterName"/>
         <module name="PackageName">
diff --git a/gradle/buildReceipt.gradle b/gradle/buildReceipt.gradle
index 893ce2e..3139797 100644
--- a/gradle/buildReceipt.gradle
+++ b/gradle/buildReceipt.gradle
@@ -50,6 +50,11 @@ task determineCommitId {
             env.find { it.key.startsWith("BUILD_VCS_NUMBER_Gradle_Master") }?.value
         }
 
+        // For the discovery release builds, this points to the Gradle revision
+        strategies << {
+            env.find { it.key.startsWith("BUILD_VCS_NUMBER_Gradle_release_branch") }?.value
+        }
+
         // If it's a checkout, ask Git for it
         strategies << {
             if (file(".git/HEAD").exists()) {
@@ -107,7 +112,7 @@ task createBuildReceipt(dependsOn: determineCommitId) {
             hostName = "unknown"
         }
         def data = [
-                commitId:  determineCommitId.commitId,
+                commitId: determineCommitId.commitId,
                 versionNumber: version,
                 versionBase: versionBase,
                 isSnapshot: isSnapshot,
@@ -117,7 +122,8 @@ task createBuildReceipt(dependsOn: determineCommitId) {
                 hostname: hostName,
                 javaVersion: System.properties["java.version"],
                 osName: System.properties["os.name"],
-                osVersion: System.properties["os.version"]
+                osVersion: System.properties["os.version"],
+                project: 'gradle'
         ]
 
         receiptFile.parentFile.mkdirs()
diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
old mode 100644
new mode 100755
index f269dbb..0face90
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -89,9 +89,10 @@ libraries += [
         xmlunit: 'xmlunit:xmlunit:1.3',
         nekohtml: 'net.sourceforge.nekohtml:nekohtml:1.9.14',
         xbean: 'org.apache.xbean:xbean-reflect:3.4 at jar', //required by maven3 classes
-        nativePlatform: 'net.rubygrapefruit:native-platform:0.2',
+        nativePlatform: 'net.rubygrapefruit:native-platform:0.3-rc-2',
         xerces: "xerces:xercesImpl:2.9.1",
-        objenesis: 'org.objenesis:objenesis:1.2 at jar'
+        objenesis: 'org.objenesis:objenesis:1.2 at jar',
+        jsoup:'org.jsoup:jsoup:1.6.3'
 ]
 
 libraries.maven3 = dependencies.module("org.apache.maven:maven-core:3.0.4") {
diff --git a/gradle/versioning.gradle b/gradle/versioning.gradle
index cbc21d9..7b715eb 100644
--- a/gradle/versioning.gradle
+++ b/gradle/versioning.gradle
@@ -50,5 +50,3 @@ if (finalRelease) {
     isSnapshot = true
     version += "-$buildTimestamp"
 }
-
-println "Gradle version: $version"
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 609bad0..8e1362d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu Jan 03 17:29:33 GMT 2013
+#Mon Mar 11 12:15:50 GMT 2013
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions-snapshots/gradle-1.4-20130116153842+0000-bin.zip
\ No newline at end of file
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.5-rc-1-bin.zip
diff --git a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
index d69be48..f74a00f 100644
--- a/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
+++ b/subprojects/announce/src/main/groovy/org/gradle/api/plugins/announce/internal/NotifySend.groovy
@@ -47,6 +47,7 @@ class NotifySend implements Announcer {
             if (icon) {
                 args '-i', icon.absolutePath
             }
+            args '--hint=int:transient:1'
             args title, message
         }
     }
diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/IoActions.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/IoActions.java
index d464404..03f4fdc 100644
--- a/subprojects/base-services/src/main/java/org/gradle/api/internal/IoActions.java
+++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/IoActions.java
@@ -34,8 +34,8 @@ public abstract class IoActions {
      * @param encoding The character encoding to write with
      * @param action The action to write the actual content
      */
-    public static void writeFile(File output, String encoding, Action<? super BufferedWriter> action) {
-        createFileWriteAction(output, encoding).execute(action);
+    public static void writeTextFile(File output, String encoding, Action<? super BufferedWriter> action) {
+        createTextFileWriteAction(output, encoding).execute(action);
     }
 
     /**
@@ -44,8 +44,8 @@ public abstract class IoActions {
      * @param output The file to write to
      * @param action The action to write the actual content
      */
-    public static void writeFile(File output, Action<? super BufferedWriter> action) {
-        writeFile(output, Charset.defaultCharset().name(), action);
+    public static void writeTextFile(File output, Action<? super BufferedWriter> action) {
+        writeTextFile(output, Charset.defaultCharset().name(), action);
     }
 
     /**
@@ -57,15 +57,15 @@ public abstract class IoActions {
      * @param encoding The character encoding to write with
      * @return An action that receives an action that performs the actual writing
      */
-    public static Action<Action<? super BufferedWriter>> createFileWriteAction(File output, String encoding) {
-        return new FileWriterIoAction(output, encoding);
+    public static Action<Action<? super BufferedWriter>> createTextFileWriteAction(File output, String encoding) {
+        return new TextFileWriterIoAction(output, encoding);
     }
 
-    private static class FileWriterIoAction implements Action<Action<? super BufferedWriter>> {
+    private static class TextFileWriterIoAction implements Action<Action<? super BufferedWriter>> {
         private final File file;
         private final String encoding;
 
-        private FileWriterIoAction(File file, String encoding) {
+        private TextFileWriterIoAction(File file, String encoding) {
             this.file = file;
             this.encoding = encoding;
         }
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java b/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
index 306962b..74dcf22 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/CompositeStoppable.java
@@ -41,14 +41,6 @@ public class CompositeStoppable implements Stoppable {
         return new CompositeStoppable().add(elements);
     }
 
-    public static CompositeStoppable closeable(Closeable... elements) {
-        return new CompositeStoppable().add(elements);
-    }
-
-    public static CompositeStoppable closeable(Iterable<? extends Closeable> elements) {
-        return new CompositeStoppable().add(elements);
-    }
-
     public CompositeStoppable add(Iterable<?> elements) {
         for (Object element : elements) {
             this.elements.add(toStoppable(element));
@@ -147,20 +139,4 @@ public class CompositeStoppable implements Stoppable {
             throw UncheckedException.throwAsUncheckedException(failure);
         }
     }
-
-    /**
-     * Similar to stop() but forwards the IOException from Closeables.
-     *
-     * @throws IOException
-     */
-    public void close() throws IOException {
-        try {
-            stop();
-        } catch (RuntimeException e) {
-            if (e.getCause() instanceof IOException) {
-                throw (IOException) e.getCause();
-            }
-            throw e;
-        }
-    }
 }
\ No newline at end of file
diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java b/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java
index 7a702a6..01a3951 100644
--- a/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java
+++ b/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java
@@ -15,7 +15,11 @@
  */
 package org.gradle.internal;
 
-import java.util.*;
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Provides access to frequently used system properties.
@@ -72,6 +76,10 @@ public class SystemProperties {
         return System.getProperty("java.version");
     }
 
+    public static File getCurrentDir() {
+        return new File(System.getProperty("user.dir"));
+    }
+
     /**
      * Returns the keys that are guaranteed to be contained in System.getProperties() by default,
      * as specified in the Javadoc for that 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 d2eff27..3b790f3 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
@@ -45,10 +45,16 @@ public abstract class CollectionUtils {
         return filter(list, new LinkedList<T>(), filter);
     }
 
-    public static <T> List<T> sort(List<? extends T> list, Comparator<? super T> comparator) {
-        List<T> sortedList = new ArrayList<T>(list);
-        Collections.sort(sortedList, comparator);
-        return sortedList;
+    public static <T> List<T> sort(Iterable<? extends T> things, Comparator<? super T> comparator) {
+        List<T> copy;
+        if (things instanceof Collection) {
+            //noinspection unchecked
+            copy = new ArrayList<T>((Collection<? extends T>) things);
+        } else {
+            copy = toList(things);
+        }
+        Collections.sort(copy, comparator);
+        return copy;
     }
 
     public static <T, C extends Collection<T>> C filter(Iterable<? extends T> source, C destination, Spec<? super T> filter) {
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
index a7fa944..9fb2e97 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/ActionsTest.groovy
@@ -71,6 +71,19 @@ class ActionsTest extends Specification {
         0 * _._
     }
 
+    def "composite action equality"() {
+        given:
+        def actions = (1..3).collect { Mock(Action) }
+
+        expect:
+        composite(actions[0], actions[1]) == composite(actions[0], actions[1])
+        composite(actions[0], actions[1]) != composite(actions[1], actions[0])
+        composite(actions[0], actions[1]) != composite(actions[0], actions[2])
+        composite() == composite()
+        composite() != composite(actions[0])
+        composite(actions[0]) != composite()
+    }
+
     def "cast before"() {
         given:
         def action = Mock(Action)
diff --git a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy
index 59572a4..889de58 100644
--- a/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy
+++ b/subprojects/base-services/src/test/groovy/org/gradle/api/internal/IoActionsTest.groovy
@@ -22,19 +22,18 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
 
-import static org.gradle.api.internal.IoActions.createFileWriteAction
-import static org.gradle.api.internal.IoActions.writeFile
+import static org.gradle.api.internal.IoActions.*
 
 class IoActionsTest extends Specification {
 
     @Rule TestNameTestDirectoryProvider tmp
 
-    def "can use file action to write to file"() {
+    def "can use file action to write to text file"() {
         given:
         def file = tmp.file("foo.txt")
 
         when:
-        createFileWriteAction(file, "UTF-8").execute(new Action<Writer>() {
+        createTextFileWriteAction(file, "UTF-8").execute(new Action<Writer>() {
             void execute(Writer writer) {
                 writer.write("bar")
             }
@@ -44,14 +43,14 @@ class IoActionsTest extends Specification {
         file.text == "bar"
     }
 
-    def "fails to write to file when can't create parent dir"() {
+    def "fails to write to text file when can't create parent dir"() {
         given:
         tmp.createFile("base")
         def file = tmp.file("base/foo.txt")
         def action = Mock(Action)
 
         when:
-        createFileWriteAction(file, "UTF-8").execute(action)
+        writeTextFile(file, "UTF-8", action)
 
         then:
         0 * action.execute(_)
@@ -60,13 +59,13 @@ class IoActionsTest extends Specification {
         e.cause.message.startsWith("Unable to create directory")
     }
 
-    def "can write file"() {
+    def "can write text file using specified encoding"() {
         given:
         def file = tmp.file("foo.txt")
         def enc = "utf-8"
 
         when:
-        writeFile(file, enc, new Action() {
+        writeTextFile(file, enc, new Action() {
             void execute(writer) {
                 writer.append("bar⌘")
             }
@@ -76,12 +75,12 @@ class IoActionsTest extends Specification {
         file.getText(enc) == "bar⌘"
     }
 
-    def "can write file with default encoding"() {
+    def "can write text file with default encoding"() {
         given:
         def file = tmp.file("foo.txt")
 
         when:
-        writeFile(file, new Action() {
+        writeTextFile(file, new Action() {
             void execute(writer) {
                 writer.append("bar⌘")
             }
@@ -90,5 +89,4 @@ class IoActionsTest extends Specification {
         then:
         file.text == "bar⌘"
     }
-
 }
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 4a7923f..d9b96e2 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
@@ -268,6 +268,21 @@ class CollectionUtilsTest extends Specification {
         toSet([]).empty
     }
 
+    def "sorting"() {
+        given:
+        def naturalComparator = { a, b -> a <=> b } as Comparator
+
+        expect:
+        def l = [1, 2, 3]
+        !sort(l, naturalComparator).is(l)
+
+        and:
+        sort([2, 1, 3], naturalComparator) == [1, 2, 3]
+        sort([2, 1, 3] as Set, naturalComparator) == [1, 2, 3]
+        sort([], naturalComparator) == []
+        sort([] as Set, naturalComparator) == []
+    }
+
     Spec<?> spec(Closure c) {
         Specs.convertClosureToSpec(c)
     }
diff --git a/subprojects/build-comparison/build-comparison.gradle b/subprojects/build-comparison/build-comparison.gradle
index 7c45cdf..9dd01ef 100644
--- a/subprojects/build-comparison/build-comparison.gradle
+++ b/subprojects/build-comparison/build-comparison.gradle
@@ -24,7 +24,7 @@ dependencies {
     compile libraries.guava
     compile libraries.slf4j_api
 
-    testCompile "org.jsoup:jsoup:1.6.3"
+    testCompile libraries.jsoup
 }
 
 processResources {
diff --git a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy
index 7836d3a..1d50e30 100644
--- a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy
+++ b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/BuildComparisonIntegrationSpec.groovy
@@ -26,7 +26,7 @@ import org.junit.Rule
 
 class BuildComparisonIntegrationSpec extends WellBehavedPluginTest {
     private static final String NOT_IDENTICAL_MESSAGE_PREFIX = "The build outcomes were not found to be identical. See the report at: file:///"
-    @Rule TestResources testResources
+    @Rule TestResources testResources = new TestResources(temporaryFolder)
 
     @Override
     String getPluginId() {
@@ -39,7 +39,7 @@ class BuildComparisonIntegrationSpec extends WellBehavedPluginTest {
     }
 
     def setup() {
-        executer.requireGradleHome(true)
+        executer.requireGradleHome()
         applyPlugin()
     }
 
@@ -290,7 +290,7 @@ class BuildComparisonIntegrationSpec extends WellBehavedPluginTest {
     }
 
     Document html(path = "build/reports/compareGradleBuilds/index.html") {
-        Jsoup.parse(file(path), "utf8")
+        Jsoup.parse(file(path), null)
     }
 
     TestFile storedFile(String path, String base = "build/reports/compareGradleBuilds/files") {
diff --git a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy
index 0d36e4b..8ee6117 100644
--- a/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy
+++ b/subprojects/build-comparison/src/integTest/groovy/org/gradle/api/plugins/buildcomparison/gradle/Pre12CompareGradleBuildsCrossVersionSpec.groovy
@@ -137,11 +137,11 @@ class Pre12CompareGradleBuildsCrossVersionSpec extends CrossVersionIntegrationSp
     }
 
     protected GradleExecuter currentExecuter() {
-        current.executer(temporaryFolder).requireGradleHome(true).withTasks("compareGradleBuilds")
+        current.executer(temporaryFolder).requireGradleHome().withTasks("compareGradleBuilds")
     }
 
     Document html(path = "build/reports/compareGradleBuilds/index.html") {
-        Jsoup.parse(file(path), "utf8")
+        Jsoup.parse(file(path), null)
     }
 
     String getSourceBuildVersion(Document html = this.html()) {
diff --git a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java
index 83720a4..af75bac 100644
--- a/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java
+++ b/subprojects/build-comparison/src/main/groovy/org/gradle/api/plugins/buildcomparison/gradle/internal/GradleBuildComparison.java
@@ -239,7 +239,7 @@ public class GradleBuildComparison {
         fileStore.moveFilestore(new File(reportDir, FILES_DIR_NAME));
 
         final Charset encoding = Charset.defaultCharset();
-        IoActions.writeFile(new File(reportDir, HTML_REPORT_FILE_NAME), encoding.name(), new Action<BufferedWriter>() {
+        IoActions.writeTextFile(new File(reportDir, HTML_REPORT_FILE_NAME), encoding.name(), new Action<BufferedWriter>() {
             public void execute(BufferedWriter writer) {
                 createResultRenderer(encoding, reportDir, hostAttributes).render(result, writer);
             }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
index dde6fb3..dd47625 100644
--- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginIntegrationTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.api.plugins.quality
 
 import org.gradle.integtests.fixtures.WellBehavedPluginTest
 import org.hamcrest.Matcher
+
 import static org.gradle.util.Matchers.containsLine
 import static org.hamcrest.Matchers.containsString
 import static org.hamcrest.Matchers.not
@@ -35,7 +36,6 @@ class PmdPluginIntegrationTest extends WellBehavedPluginTest {
         goodCode()
 
         expect:
-        executer.withArguments("--info")
         succeeds("check")
         file("build/reports/pmd/main.xml").exists()
         file("build/reports/pmd/test.xml").exists()
@@ -67,11 +67,23 @@ class PmdPluginIntegrationTest extends WellBehavedPluginTest {
         output.contains("2 PMD rule violations were found. See the report at:")
     }
 
+    def "can set target JDK for PMD versions prior to 5.0"() {
+        badCode()
+        buildFile << """
+            pmd {
+                toolVersion = '4.3'
+                targetJdk = 1.4
+            }
+        """
+
+        expect:
+        //with 1.4 target, code can't be parsed due to usage of Generics
+        //TODO: Allow to expose this error from ant pmd. (pniederw: not sure what this means)
+        succeeds("check")
+    }
+
     def "can configure reporting"() {
-        given:
         goodCode()
-
-        and:
         buildFile << """
             pmdMain {
                 reports {
@@ -80,25 +92,44 @@ class PmdPluginIntegrationTest extends WellBehavedPluginTest {
                 }
             }
         """
+
         expect:
         succeeds("check")
-
         !file("build/reports/pmd/main.xml").exists()
         file("htmlReport.html").exists()
     }
 
+    def "use custom rule set files"() {
+        customCode()
+        customRuleSet()
+
+        buildFile << """
+            pmd {
+                ruleSets = []
+                ruleSetFiles = files("customRuleSet.xml")
+            }
+        """
+
+        expect:
+        fails("pmdMain")
+        failure.assertHasDescription("Execution failed for task ':pmdMain'")
+        failure.assertThatCause(containsString("1 PMD rule violations were found. See the report at:"))
+        file("build/reports/pmd/main.xml").assertContents(not(containsClass("org.gradle.Class1")))
+        file("build/reports/pmd/main.xml").assertContents(containsClass("org.gradle.Class2"))
+    }
+
     private void writeBuildFile() {
         file("build.gradle") << """
-apply plugin: "java"
-apply plugin: "pmd"
+            apply plugin: "java"
+            apply plugin: "pmd"
 
-repositories {
-    mavenCentral()
-}
+            repositories {
+                mavenCentral()
+            }
         """
     }
 
-    private Matcher<String> containsClass(String className) {
+    private static Matcher<String> containsClass(String className) {
         containsLine(containsString(className.replace(".", File.separator)))
     }
 
@@ -113,6 +144,30 @@ repositories {
         file("src/main/java/org/gradle/Class1.java") <<
                 "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
         file("src/test/java/org/gradle/Class1Test.java") <<
-                "package org.gradle; class Class1Test { {} public boolean equals(Object arg) { return true; } }"
+                "package org.gradle; class Class1Test<T> { {} public boolean equals(Object arg) { return true; } }"
+    }
+
+    private customCode() {
+        // class that would fail basic rule set but doesn't fail custom rule set
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; public class Class1 { public void doit() { boolean x = true; if (x) {} } }" // empty then-block
+        // class that wouldn't fail basic rule set but does fail custom rule set
+        file("src/main/java/org/gradle/Class2.java") <<
+                "package org.gradle; public class Class2 { public void doit() { boolean x = true; if (x) x = false; } }" // missing braces
+    }
+
+    private customRuleSet() {
+        file ("customRuleSet.xml") << """
+            <ruleset name="custom"
+                xmlns="http://pmd.sf.net/ruleset/1.0.0"
+                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
+                xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
+
+                <description>Custom rule set</description>
+
+                <rule ref="rulesets/braces.xml"/>
+            </ruleset>
+        """
     }
 }
diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginVersionIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginVersionIntegrationTest.groovy
new file mode 100644
index 0000000..5ca45d1
--- /dev/null
+++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/PmdPluginVersionIntegrationTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins.quality
+
+import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetVersions
+import org.hamcrest.Matcher
+
+import static org.gradle.util.Matchers.containsLine
+import static org.hamcrest.Matchers.containsString
+import static org.hamcrest.Matchers.not
+
+ at TargetVersions(['4.3', '5.0.2'])
+class PmdPluginVersionIntegrationTest extends MultiVersionIntegrationSpec {
+    def "can use different PMD versions"() {
+        given:
+        badCode()
+        buildFile << """
+            apply plugin: "java"
+            apply plugin: "pmd"
+
+            repositories {
+                mavenCentral()
+            }
+
+            pmd {
+                toolVersion = '$version'
+            }
+        """
+
+        expect:
+        fails("check")
+        failure.assertHasDescription("Execution failed for task ':pmdTest'")
+        failure.assertThatCause(containsString("2 PMD rule violations were found. See the report at:"))
+        file("build/reports/pmd/main.xml").assertContents(not(containsClass("org.gradle.Class1")))
+        file("build/reports/pmd/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
+    }
+
+    private badCode() {
+        file("src/main/java/org/gradle/Class1.java") <<
+                "package org.gradle; class Class1 { public boolean isFoo(Object arg) { return true; } }"
+        file("src/test/java/org/gradle/Class1Test.java") <<
+                "package org.gradle; class Class1Test { {} public boolean equals(Object arg) { return true; } }"
+    }
+
+    private static Matcher<String> containsClass(String className) {
+        containsLine(containsString(className.replace(".", File.separator)))
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy
index d712977..4e7083e 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/FindBugs.groovy
@@ -90,6 +90,13 @@ class FindBugs extends SourceTask implements VerificationTask, Reporting<FindBug
     String reportLevel
 
     /**
+     * The maximum heap size for the forked findbugs process (ex: '1g').
+     */
+    @Input
+    @Optional
+    String maxHeapSize
+
+    /**
      * The bug detectors which should be run. The bug detectors are specified by their class names,
      * without any package qualification. By default, all detectors which are not disabled by default are run.
      */
@@ -185,6 +192,7 @@ class FindBugs extends SourceTask implements VerificationTask, Reporting<FindBug
             .withDebugging(logger.isDebugEnabled())
             .withEffort(getEffort())
             .withReportLevel(getReportLevel())
+            .withMaxHeapSize(getMaxHeapSize())
             .withVisitors(getVisitors())
             .withOmitVisitors(getOmitVisitors())
             .withExcludeFilter(getExcludeFilter())
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
index 00da2cb..02e545c 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.groovy
@@ -15,14 +15,14 @@
  */
 package org.gradle.api.plugins.quality
 
+import org.gradle.api.GradleException
 import org.gradle.api.file.FileCollection
-import org.gradle.internal.reflect.Instantiator
 import org.gradle.api.internal.project.IsolatedAntBuilder
 import org.gradle.api.plugins.quality.internal.PmdReportsImpl
 import org.gradle.api.reporting.Reporting
 import org.gradle.api.tasks.*
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.logging.ConsoleRenderer
-import org.gradle.api.GradleException
 
 import javax.inject.Inject
 
@@ -48,6 +48,12 @@ class Pmd extends SourceTask implements VerificationTask, Reporting<PmdReports>
     List<String> ruleSets
 
     /**
+     * The target JDK to use with PMD.
+     */
+    @Input
+    TargetJdk targetJdk
+
+    /**
      * The custom rule set files to be used. See the <a href="http://pmd.sourceforge.net/howtomakearuleset.html">official documentation</a> for
      * how to author a rule set file.
      *
@@ -68,34 +74,48 @@ class Pmd extends SourceTask implements VerificationTask, Reporting<PmdReports>
      */
     boolean ignoreFailures
 
-    @Inject
-    Pmd(Instantiator instantiator, IsolatedAntBuilder antBuilder) {
+    @Inject Pmd(Instantiator instantiator, IsolatedAntBuilder antBuilder) {
         reports = instantiator.newInstance(PmdReportsImpl, this)
         this.antBuilder = antBuilder
     }
 
     @TaskAction
     void run() {
+        def prePmd5 = getPmdClasspath().any {
+            it.name ==~ /pmd-([1-4]\.[0-9\.]+)\.jar/
+        }
+        def antPmdArgs = [failOnRuleViolation: false, failuresPropertyName: "pmdFailureCount"]
+        if (prePmd5) {
+            // NOTE: PMD 5.0.2 apparently introduces an element called "language" that serves the same purpose
+            // http://sourceforge.net/p/pmd/bugs/1004/
+            // http://java-pmd.30631.n5.nabble.com/pmd-pmd-db05bc-pmd-AntTask-support-for-language-td5710041.html
+            antPmdArgs["targetjdk"] = getTargetJdk().getName()
+        } else {
+            // allow PmdPlugin to set a version-independent default
+            if (getRuleSets() == ["basic"]) {
+                setRuleSets(["java-basic"])
+            }
+        }
+
         antBuilder.withClasspath(getPmdClasspath()).execute {
             ant.taskdef(name: 'pmd', classname: 'net.sourceforge.pmd.ant.PMDTask')
-            ant.pmd(failOnRuleViolation: false, failuresPropertyName: "pmdFailureCount") {
-                getSource().addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
-                getRuleSets().each {
-                    ruleset(it)
-                }
-                getRuleSetFiles().each {
-                    ruleset(it)
-                }
+                ant.pmd(antPmdArgs) {
+                    getSource().addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
+                    getRuleSets().each {
+                        ruleset(it)
+                    }
+                    getRuleSetFiles().each {
+                        ruleset(it)
+                    }
 
-                if (reports.html.enabled) {
-                    assert reports.html.destination.parentFile.exists()
-                    formatter(type: 'betterhtml', toFile: reports.html.destination)
-                }
-                if (reports.xml.enabled) {
-                    formatter(type: 'xml', toFile: reports.xml.destination)
+                    if (reports.html.enabled) {
+                        assert reports.html.destination.parentFile.exists()
+                        formatter(type: prePmd5 ? "betterhtml" : "html", toFile: reports.html.destination)
+                    }
+                    if (reports.xml.enabled) {
+                        formatter(type: 'xml', toFile: reports.xml.destination)
+                    }
                 }
-            }
-
             def failureCount = ant.project.properties["pmdFailureCount"]
             if (failureCount) {
                 def message = "$failureCount PMD rule violations were found."
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy
index d13dc6b..1af0df5 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.groovy
@@ -37,7 +37,20 @@ class PmdExtension extends CodeQualityExtension {
      * Example: ruleSets = ["basic", "braces"]
      */
     List<String> ruleSets
-    
+
+    /**
+     * The target jdk to use with pmd, 1.3, 1.4, 1.5, 1.6, 1.7 or jsp
+     */
+    TargetJdk targetJdk
+
+    /**
+     * Sets the target jdk used with pmd.
+     *
+     * @value The value for the target jdk as defined by {@link TargetJdk#toVersion(Object)}
+     */
+    void setTargetJdk(def value) {
+        targetJdk = TargetJdk.toVersion(value)
+    }
     /**
      * Convenience method for adding rule sets.
      *
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdPlugin.groovy
index f084a9c..2116c66 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
@@ -15,14 +15,15 @@
  */
 package org.gradle.api.plugins.quality
 
+import org.gradle.api.JavaVersion
 import org.gradle.api.plugins.quality.internal.AbstractCodeQualityPlugin
 import org.gradle.api.tasks.SourceSet
+import org.gradle.util.VersionNumber
 
 /**
- *  A plugin for the <a href="http://pmd.sourceforge.net/">PMD source code analyzer.
+ *  A plugin for the <a href="http://pmd.sourceforge.net/">PMD</a> source code analyzer.
  * <p>
- * Declares a <tt>findbugs</tt> configuration which needs to be configured with the FindBugs library to be used.
- * Additional plugins can be added to the <tt>findbugsPlugins</tt> configuration.
+ * Declares a <tt>pmd</tt> configuration which needs to be configured with the PMD library to be used.
  * <p>
  * For each source set that is to be analyzed, a {@link Pmd} task is created and configured to analyze all Java code.
  * <p
@@ -49,20 +50,42 @@ class PmdPlugin extends AbstractCodeQualityPlugin<Pmd> {
         extension = project.extensions.create("pmd", PmdExtension, project)
         extension.with {
             toolVersion = "4.3"
+            // NOTE: should change default rule set to java-basic once we bump default version to 5.0+
+            // this will also require a change to Pmd.run() (convert java-basic to basic for old versions,
+            // instead of basic to java-basic for new versions)
             ruleSets = ["basic"]
             ruleSetFiles = project.files()
         }
+        extension.getConventionMapping().with{
+            targetJdk = { getDefaultTargetJdk(project.sourceCompatibility) }
+        }
         return extension
     }
 
+    TargetJdk getDefaultTargetJdk(JavaVersion javaVersion) {
+        try {
+            return TargetJdk.toVersion(javaVersion.toString())
+        } catch(IllegalArgumentException ignored) {
+            // TargetJDK does not include 1.1, 1.2 and 1.8;
+            // Use same fallback as PMD
+            return TargetJdk.VERSION_1_4
+        }
+    }
+
     @Override
     protected void configureTaskDefaults(Pmd task, String baseName) {
+
         task.conventionMapping.with {
             pmdClasspath = {
                 def config = project.configurations['pmd']
                 if (config.dependencies.empty) {
+                    VersionNumber version = VersionNumber.parse(extension.toolVersion)
                     project.dependencies {
-                        pmd "pmd:pmd:$extension.toolVersion"
+                        if (version < VersionNumber.parse("5.0.0")) {
+                            pmd "pmd:pmd:$extension.toolVersion"
+                        } else {
+                            pmd "net.sourceforge.pmd:pmd:$extension.toolVersion"
+                        }
                     }
                 }
                 config
@@ -70,7 +93,7 @@ class PmdPlugin extends AbstractCodeQualityPlugin<Pmd> {
             ruleSets = { extension.ruleSets }
             ruleSetFiles = { extension.ruleSetFiles }
             ignoreFailures = { extension.ignoreFailures }
-
+            targetJdk = { extension.targetJdk }
             task.reports.all { report ->
                 report.conventionMapping.with {
                     enabled = { true }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/TargetJdk.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/TargetJdk.java
new file mode 100644
index 0000000..62637a3
--- /dev/null
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/TargetJdk.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins.quality;
+
+/**
+ * Represents the PMD targetjdk property available for PMD < 5.0
+ */
+public enum TargetJdk {
+    VERSION_1_3,
+    VERSION_1_4,
+    VERSION_1_5,
+    VERSION_1_6,
+    VERSION_1_7,
+    VERSION_JSP;
+
+    /**
+     * Converts the given object into a {@code TargetJdk}.
+     *
+     * @param value An object whose toString() value is to be converted. May be null.
+     * @return The version, or null if the provided value is null.
+     * @throws IllegalArgumentException when the provided value cannot be converted.
+     */
+    public static TargetJdk toVersion(Object value) throws IllegalArgumentException {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof TargetJdk) {
+            return (TargetJdk) value;
+        }
+
+        String name = value.toString();
+        if (name.equalsIgnoreCase("1.7")) {
+            return VERSION_1_7;
+        } else if (name.equalsIgnoreCase("1.6")) {
+            return VERSION_1_6;
+        } else if (name.equalsIgnoreCase("1.5")) {
+            return VERSION_1_5;
+        } else if (name.equalsIgnoreCase("1.4")) {
+            return VERSION_1_4;
+        } else if (name.equalsIgnoreCase("1.3")) {
+            return VERSION_1_3;
+        } else if (name.equalsIgnoreCase("jsp")) {
+            return VERSION_JSP;
+        } else {
+            throw new IllegalArgumentException(String.format("Could not determine targetjdk from '%s'.", name));
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+
+    public String getName() {
+        return name().substring("VERSION_".length()).replace('_', '.').toLowerCase();
+    }
+}
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpec.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpec.java
index 7447001..c477e09 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpec.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpec.java
@@ -23,10 +23,12 @@ import java.util.List;
 
 public class FindBugsSpec implements Serializable {
     private List<String> arguments;
+    private String maxHeapSize;
     private boolean debugEnabled;
 
-    public FindBugsSpec(List<String> arguments, boolean debugEnabled) {
+    public FindBugsSpec(List<String> arguments, String maxHeapSize, boolean debugEnabled) {
         this.debugEnabled = debugEnabled;
+        this.maxHeapSize = maxHeapSize;
         this.arguments = arguments;
     }
 
@@ -34,10 +36,14 @@ public class FindBugsSpec implements Serializable {
         return arguments;
     }
 
+    public String getMaxHeapSize() {
+        return maxHeapSize;
+    }
+    
     public boolean isDebugEnabled() {
         return debugEnabled;
     }
-
+    
     public String toString() {
         return Objects.toStringHelper(this).add("arguments", arguments).add("debugEnabled", debugEnabled).toString();
     }
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpecBuilder.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpecBuilder.java
index df59ff2..d4dfda5 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpecBuilder.java
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsSpecBuilder.java
@@ -41,6 +41,7 @@ public class FindBugsSpecBuilder {
 
     private String effort;
     private String reportLevel;
+    private String maxHeapSize;
     private Collection<String> visitors;
     private Collection<String> omitVisitors;
     private File excludeFilter;
@@ -90,6 +91,11 @@ public class FindBugsSpecBuilder {
         this.reportLevel = reportLevel;
         return this;
     }
+    
+    public FindBugsSpecBuilder withMaxHeapSize(String maxHeapSize) {
+        this.maxHeapSize = maxHeapSize;
+        return this;
+    }
 
     public FindBugsSpecBuilder withVisitors(Collection<String> visitors) {
         this.visitors = visitors;
@@ -192,8 +198,8 @@ public class FindBugsSpecBuilder {
         for (File classFile : classes.getFiles()) {
             args.add(classFile.getAbsolutePath());
         }
-
-        return new FindBugsSpec(args, debugEnabled);
+        
+        return new FindBugsSpec(args, maxHeapSize, debugEnabled);
     }
 
     private boolean has(String str) {
diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy
index 7e52b3e..aaaca07 100644
--- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy
+++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/internal/findbugs/FindBugsWorkerManager.groovy
@@ -40,6 +40,7 @@ class FindBugsWorkerManager {
         builder.sharedPackages(Arrays.asList("edu.umd.cs.findbugs"));
         JavaExecHandleBuilder javaCommand = builder.getJavaCommand();
         javaCommand.setWorkingDir(workingDir);
+        javaCommand.setMaxHeapSize(spec.getMaxHeapSize());
 
         WorkerProcess process = builder.worker(new FindBugsWorkerServer(spec)).build()
         return process
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy
index 04c9172..b708011 100644
--- a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/PmdPluginTest.groovy
@@ -21,6 +21,7 @@ import org.gradle.api.plugins.ReportingBasePlugin
 import org.gradle.api.tasks.SourceSet
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
+
 import static org.gradle.util.Matchers.dependsOn
 import static org.hamcrest.Matchers.*
 import static spock.util.matcher.HamcrestSupport.that
@@ -70,6 +71,29 @@ class PmdPluginTest extends Specification {
         configuresPmdTask("pmdOther", project.sourceSets.other)
     }
 
+    def "configures pmd targetjdk based on sourcecompatibilityLevel"() {
+        project.plugins.apply(JavaBasePlugin)
+        when:
+        project.setSourceCompatibility(sourceCompatibility)
+        project.sourceSets {
+            main
+        }
+        then:
+        project.tasks.getByName("pmdMain").targetJdk == targetJdk
+
+        where:
+        sourceCompatibility | targetJdk
+        1.3                 | TargetJdk.VERSION_1_3
+        1.4                 | TargetJdk.VERSION_1_4
+        1.5                 | TargetJdk.VERSION_1_5
+        1.6                 | TargetJdk.VERSION_1_6
+        1.7                 | TargetJdk.VERSION_1_7
+        // 1.4 is the default in the pmd plugin so we use it as a default too
+        1.8 | TargetJdk.VERSION_1_4
+        1.1 | TargetJdk.VERSION_1_4
+        1.2 | TargetJdk.VERSION_1_4
+    }
+
     private void configuresPmdTask(String taskName, SourceSet sourceSet) {
         def task = project.tasks.findByName(taskName)
         assert task instanceof Pmd
@@ -84,7 +108,7 @@ class PmdPluginTest extends Specification {
             assert ignoreFailures == false
         }
     }
-    
+
     def "configures any additional PMD tasks"() {
         def task = project.tasks.add("pmdCustom", Pmd)
 
@@ -149,7 +173,7 @@ class PmdPluginTest extends Specification {
             assert ignoreFailures == true
         }
     }
-    
+
     def "can customize any additional PMD tasks via extension"() {
         def task = project.tasks.add("pmdCustom", Pmd)
         project.pmd {
@@ -170,5 +194,5 @@ class PmdPluginTest extends Specification {
         task.outputs.files.files == task.reports.enabled*.destination as Set
         task.ignoreFailures == true
     }
-    
+
 }
diff --git a/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/TargetJdkSpec.groovy b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/TargetJdkSpec.groovy
new file mode 100644
index 0000000..57e7511
--- /dev/null
+++ b/subprojects/code-quality/src/test/groovy/org/gradle/api/plugins/quality/TargetJdkSpec.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins.quality
+
+import spock.lang.Specification
+
+class TargetJdkSpec extends Specification {
+
+    def toStringReturnsVersion() {
+        expect:
+        TargetJdk.VERSION_1_3.toString() == "1.3"
+        TargetJdk.VERSION_1_4.toString() == "1.4"
+        TargetJdk.VERSION_1_5.toString() == "1.5"
+        TargetJdk.VERSION_1_6.toString() == "1.6"
+        TargetJdk.VERSION_1_7.toString() == "1.7"
+        TargetJdk.VERSION_JSP.toString() == "jsp"
+    }
+
+    def convertsStringToVersion() {
+        expect:
+        TargetJdk.toVersion("1.3") == TargetJdk.VERSION_1_3
+        TargetJdk.toVersion("1.4") == TargetJdk.VERSION_1_4
+        TargetJdk.toVersion("1.5") == TargetJdk.VERSION_1_5
+        TargetJdk.toVersion("1.6") == TargetJdk.VERSION_1_6
+        TargetJdk.toVersion("1.7") == TargetJdk.VERSION_1_7
+        TargetJdk.toVersion("jsp") == TargetJdk.VERSION_JSP
+        TargetJdk.toVersion("JSP") == TargetJdk.VERSION_JSP
+    }
+
+    def failsToConvertStringToVersionForUnknownVersion() {
+        expect:
+        conversionFails("1.1");
+        conversionFails("1.2");
+        conversionFails("1");
+        conversionFails("2");
+
+        conversionFails("17");
+
+        conversionFails("a");
+        conversionFails("");
+        conversionFails("  ");
+
+        conversionFails("1.54");
+        conversionFails("1.9");
+        conversionFails("1.10");
+        conversionFails("2.0");
+        conversionFails("1_4");
+    }
+
+    def convertsVersionToVersion() {
+        expect:
+        TargetJdk.toVersion(TargetJdk.VERSION_1_4) == TargetJdk.VERSION_1_4
+    }
+
+    def convertsNumberToVersion() {
+        expect:
+        TargetJdk.toVersion(1.3) == TargetJdk.VERSION_1_3
+        TargetJdk.toVersion(1.4) == TargetJdk.VERSION_1_4
+        TargetJdk.toVersion(1.5) == TargetJdk.VERSION_1_5
+        TargetJdk.toVersion(1.6) == TargetJdk.VERSION_1_6
+        TargetJdk.toVersion(1.7) == TargetJdk.VERSION_1_7
+    }
+
+    def failsToConvertNumberToVersionForUnknownVersion() {
+        expect:
+        conversionFails(1.1);
+        conversionFails(1.2);
+        conversionFails(1);
+        conversionFails(2);
+        conversionFails(17);
+        conversionFails(1.21);
+        conversionFails(2.0);
+        conversionFails(4.2);
+    }
+
+    def convertsNullToNull() {
+        expect:
+        TargetJdk.toVersion(null) == null
+    }
+
+    private void conversionFails(Object value) {
+        try {
+            TargetJdk.toVersion(value);
+            org.junit.Assert.fail();
+        } catch (IllegalArgumentException e) {
+            assert e.getMessage() == "Could not determine targetjdk from '" + value + "'."
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
index c68c402..89a69ea 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactDependenciesIntegrationTest.groovy
@@ -25,11 +25,10 @@ import org.junit.Test
 import spock.lang.Issue
 
 import static org.hamcrest.Matchers.containsString
-import static org.hamcrest.Matchers.startsWith
 
 class ArtifactDependenciesIntegrationTest extends AbstractIntegrationTest {
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Before
     public void setup() {
@@ -609,11 +608,14 @@ task test << {
     public void reportsUnknownDependencyError() {
         File buildFile = testFile("projectWithUnknownDependency.gradle");
         ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
-        failure.assertHasFileName("Build file '" + buildFile.getPath() + "'");
-        failure.assertHasDescription("Execution failed for task ':listJars'");
-        failure.assertThatCause(startsWith("Could not resolve all dependencies for configuration ':compile'"));
-        failure.assertThatCause(containsString("Could not find test:unknownProjectA:1.2."));
-        failure.assertThatCause(containsString("Could not find test:unknownProjectB:2.1.5."));
+
+        failure
+                .assertHasFileName("Build file '" + buildFile.getPath() + "'")
+                .assertHasDescription("Execution failed for task ':listJars'");
+
+        failure.assertResolutionFailure(':compile')
+                .assertHasCause("Could not find test:unknownProjectA:1.2.")
+                .assertHasCause("Could not find test:unknownProjectB:2.1.5.")
     }
 
     @Test
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
index 4f73e39..615b6c0 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ArtifactOnlyResolutionIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.test.fixtures.maven.MavenHttpModule
 import org.junit.Rule
 
 class ArtifactOnlyResolutionIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule public final TestResources resources = new TestResources();
+    @Rule public final TestResources resources = new TestResources(temporaryFolder);
 
     MavenHttpModule projectA
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
index 13af454..6c39df9 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/CacheResolveIntegrationTest.groovy
@@ -16,8 +16,6 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
-import org.gradle.test.fixtures.file.TestFile;
-
 
 public class CacheResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
@@ -27,7 +25,7 @@ public class CacheResolveIntegrationTest extends AbstractDependencyResolutionTes
         given:
         def module = ivyHttpRepo.module('group', 'projectA', '1.2').publish()
 
-        def cacheDir = new TestFile(executer.gradleUserHomeDir).file('caches').toURI()
+        def cacheDir = executer.gradleUserHomeDir.file('caches').toURI()
 
         and:
         buildFile << """
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java
deleted file mode 100644
index 241d8bb..0000000
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependenciesResolveIntegrationTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.integtests.resolve;
-
-import org.gradle.integtests.fixtures.Sample;
-import org.gradle.integtests.fixtures.executer.GradleContextualExecuter;
-import org.gradle.integtests.fixtures.executer.GradleDistribution;
-import org.gradle.integtests.fixtures.executer.GradleExecuter;
-import org.gradle.integtests.fixtures.executer.UnderDevelopmentGradleDistribution;
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.io.File;
-
-/**
- * @author Hans Dockter
- */
-public class DependenciesResolveIntegrationTest {
-    @Rule public final TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider();
-    private final GradleDistribution dist = new UnderDevelopmentGradleDistribution();
-    private final GradleExecuter executer = new GradleContextualExecuter(dist, testDirectoryProvider);
-    @Rule public final Sample sample = new Sample("dependencies");
-
-    @Test
-    public void testResolve() {
-        executer.requireOwnGradleUserHomeDir();
-
-        // the actual testing is done in the build script.
-        File projectDir = sample.getDir();
-        executer.inDirectory(projectDir).withTasks("test").run();
-    }   
-}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
index eaebb35..df3c85f 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/DependencyResolveRulesIntegrationTest.groovy
@@ -19,6 +19,8 @@ package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
+
 /**
  * @author Szczepan Faber, @date 03.03.11
  */
@@ -351,7 +353,7 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
                 assert a.selectionReason.conflictResolution
                 assert a.selectionReason.selectedByRule
                 assert !a.selectionReason.forced
-                assert a.selectionReason.description == 'conflict resolution by rule'
+                assert a.selectionReason.description == 'selected by rule and conflict resolution'
 	        }
 """
 
@@ -485,7 +487,10 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
 	        task check << {
                 def deps = configurations.conf.incoming.resolutionResult.allDependencies as List
                 assert deps.size() == 1
-                assert deps[0].failure
+                assert deps[0].attempted.group == 'org.utils'
+                assert deps[0].attempted.name == 'api'
+                assert deps[0].attempted.version == '1.123.15'
+                assert deps[0].attemptedReason.selectedByRule
                 assert deps[0].failure.message.contains('1.123.15')
                 assert deps[0].requested.version == '1.3'
 	        }
@@ -495,8 +500,7 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
         def failure = runAndFail("check", "resolveConf")
 
         then:
-        failure.dependencyResolutionFailure
-            .assertFailedConfiguration(":conf")
+        failure.assertResolutionFailure(":conf")
             .assertHasCause("Could not find org.utils:api:1.123.15")
     }
 
@@ -578,13 +582,177 @@ class DependencyResolveRulesIntegrationTest extends AbstractIntegrationSpec {
         def failure = runAndFail("resolveConf")
 
         then:
-        failure.dependencyResolutionFailure
-                .assertFailedConfiguration(":conf")
+        failure.assertResolutionFailure(":conf")
                 .assertHasCause("Could not resolve org.utils:impl:1.3.")
                 .assertHasCause("Unhappy :(")
                 .assertFailedDependencyRequiredBy(":root:1.0")
     }
 
+    void "can substitute module name and resolve conflict"()
+    {
+        mavenRepo.module("org.utils", "a",  '1.2').publish()
+        mavenRepo.module("org.utils", "b",  '2.0').publish()
+        mavenRepo.module("org.utils", "b",  '2.1').publish()
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org.utils:a:1.2', 'org.utils:b:2.0'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                if (it.requested.name == 'a') {
+                    it.useTarget(it.requested.group + ':b:2.1')
+                }
+	        }
+
+	        task check << {
+                def modules = configurations.conf.incoming.resolutionResult.allModuleVersions as List
+                assert !modules.find { it.id.name == 'a' }
+                def b = modules.find { it.id.name == 'b' }
+                assert b.id.version == '2.1'
+                assert b.selectionReason.conflictResolution
+                assert b.selectionReason.selectedByRule
+                assert !b.selectionReason.forced
+                assert b.selectionReason.description == 'selected by rule and conflict resolution'
+	        }
+"""
+
+        when:
+        run("check", "dependencies")
+
+        then:
+        output.contains(toPlatformLineSeparators("""conf
++--- org.utils:a:1.2 -> org.utils:b:2.1
+\\--- org.utils:b:2.0 -> 2.1"""))
+    }
+
+    def "can substitute module group"()
+    {
+        mavenRepo.module("org", "a", "1.0").publish()
+        mavenRepo.module("org", "b").dependsOn("org", "a", "2.0").publish()
+        mavenRepo.module("org", "a", "2.0").dependsOn("org", "c", "1.0").publish()
+        mavenRepo.module("org", "c").publish()
+        //a1
+        //b->a2->c
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org:a:1.0', 'foo:b:1.0'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                if (it.requested.group == 'foo') {
+                    it.useTarget('org:' + it.requested.name + ':' + it.requested.version)
+                }
+	        }
+"""
+
+        when:
+        run("dependencies")
+
+        then:
+        output.contains(toPlatformLineSeparators("""
++--- org:a:1.0 -> 2.0
+|    \\--- org:c:1.0
+\\--- foo:b:1.0 -> org:b:1.0
+     \\--- org:a:2.0 (*)"""))
+    }
+
+    def "can substitute module group, name and version"()
+    {
+        mavenRepo.module("org", "a", "1.0").publish()
+        mavenRepo.module("org", "b").dependsOn("org", "a", "2.0").publish()
+        mavenRepo.module("org", "a", "2.0").dependsOn("org", "c", "1.0").publish()
+        mavenRepo.module("org", "c").publish()
+        //a1
+        //b->a2->c
+
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org:a:1.0', 'foo:bar:baz'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                if (it.requested.group == 'foo') {
+                    it.useTarget group: 'org', name: 'b', version: '1.0'
+                }
+	        }
+"""
+
+        when:
+        run("dependencies")
+
+        then:
+        output.contains(toPlatformLineSeparators("""
++--- org:a:1.0 -> 2.0
+|    \\--- org:c:1.0
+\\--- foo:bar:baz -> org:b:1.0
+     \\--- org:a:2.0 (*)"""))
+    }
+
+    def "provides decent feedback when target module incorrectly specified"()
+    {
+        buildFile << """
+            $common
+
+            dependencies {
+                conf 'org:a:1.0', 'foo:bar:baz'
+            }
+
+            configurations.conf.resolutionStrategy.eachDependency {
+                it.useTarget "foobar"
+	        }
+"""
+
+        when:
+        runAndFail("dependencies")
+
+        then:
+        failure.assertResolutionFailure(":conf").assertHasCause("Invalid format: 'foobar'")
+    }
+
+    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.allModuleVersions as List
+                assert modules.find { it.id.name == 'b' && it.id.version == '4.0' && it.selectionReason.conflictResolution }
+            }
+"""
+
+        expect:
+        run("check")
+    }
+
     String getCommon() {
         """configurations { conf }
         repositories {
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ForcedModulesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ForcedModulesIntegrationTest.groovy
new file mode 100644
index 0000000..94bebef
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ForcedModulesIntegrationTest.groovy
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+/**
+ * @author Szczepan Faber, @date 03.03.11
+ */
+class ForcedModulesIntegrationTest extends AbstractIntegrationSpec {
+
+    void "can force the version of a particular module"() {
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
+
+        buildFile << """
+apply plugin: 'java'
+repositories { maven { url "${mavenRepo.uri}" } }
+
+dependencies {
+    compile 'org:foo:1.3.3'
+}
+
+configurations.all {
+    resolutionStrategy.force 'org:foo:1.4.4'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.4.4.jar']
+}
+"""
+
+        expect:
+        run("checkDeps")
+    }
+
+    void "can force already resolved version of a module and avoid conflict"() {
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
+
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories { maven { url "${mavenRepo.uri}" } }
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+}
+
+allprojects {
+    configurations.all {
+	    resolutionStrategy {
+	        force 'org:foo:1.3.3'
+	        failOnVersionConflict()
+	    }
+	}
+}
+
+"""
+
+        expect:
+        run("api:dependencies", "tool:dependencies")
+    }
+
+    void "can force arbitrary version of a module and avoid conflict"() {
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foobar", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
+        mavenRepo.module("org", "foo", '1.5.5').publish()
+
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories { maven { url "${mavenRepo.uri}" } }
+	group = 'org.foo.unittests'
+	version = '1.0'
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+    task checkDeps(dependsOn: configurations.compile) << {
+        assert configurations.compile*.name == ['api-1.0.jar', 'impl-1.0.jar', 'foo-1.5.5.jar']
+        def metaData = configurations.compile.resolvedConfiguration
+        def api = metaData.firstLevelModuleDependencies.find { it.moduleName == 'api' }
+        assert api.children.size() == 1
+        assert api.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
+        def impl = metaData.firstLevelModuleDependencies.find { it.moduleName == 'impl' }
+        assert impl.children.size() == 1
+        assert impl.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
+    }
+}
+
+allprojects {
+    configurations.all {
+        resolutionStrategy {
+            failOnVersionConflict()
+            force 'org:foo:1.5.5'
+        }
+    }
+}
+
+"""
+
+        expect:
+        run(":tool:checkDeps")
+    }
+
+    void "latest strategy respects forced modules"() {
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
+
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories { maven { url "${mavenRepo.uri}" } }
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+	}
+	configurations.all {
+	    resolutionStrategy {
+	        failOnVersionConflict()
+	        force 'org:foo:1.3.3'
+	    }
+	}
+    task checkDeps(dependsOn: configurations.compile) << {
+        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.3.3.jar']
+    }
+}
+"""
+
+        expect:
+        run("tool:checkDeps")
+    }
+
+    void "strict conflict strategy can be used with forced modules"() {
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
+        mavenRepo.module("org", "foo", '1.5.5').publish()
+
+        settingsFile << "include 'api', 'impl', 'tool'"
+
+        buildFile << """
+allprojects {
+	apply plugin: 'java'
+	repositories {
+		maven { url "${mavenRepo.uri}" }
+	}
+}
+
+project(':api') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.4.4')
+	}
+}
+
+project(':impl') {
+	dependencies {
+		compile (group: 'org', name: 'foo', version:'1.3.3')
+	}
+}
+
+project(':tool') {
+	dependencies {
+		compile project(':api')
+		compile project(':impl')
+		compile('org:foo:1.5.5'){
+		    force = true
+		}
+	}
+
+	configurations.all { resolutionStrategy.failOnVersionConflict() }
+}
+"""
+
+        expect:
+        run("tool:dependencies")
+    }
+
+    void "can force the version of a direct dependency"() {
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
+
+        buildFile << """
+apply plugin: 'java'
+repositories { maven { url "${mavenRepo.uri}" } }
+
+dependencies {
+    compile 'org:foo:1.4.4'
+    compile ('org:foo:1.3.3') { force = true }
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.3.3.jar']
+}
+"""
+
+        expect:
+        executer.withTasks("checkDeps").run()
+    }
+
+    void "forcing transitive dependency does not add extra dependency"() {
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("hello", "world", '1.4.4').publish()
+
+        buildFile << """
+apply plugin: 'java'
+repositories { maven { url "${mavenRepo.uri}" } }
+
+dependencies {
+    compile 'org:foo:1.3.3'
+}
+
+configurations.all {
+    resolutionStrategy.force 'hello:world:1.4.4'
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-1.3.3.jar']
+}
+"""
+
+        expect:
+        run("checkDeps")
+    }
+
+    void "when forcing the same module last declaration wins"() {
+        mavenRepo.module("org", "foo", '2.0').publish()
+
+        buildFile << """
+apply plugin: 'java'
+repositories { maven { url "${mavenRepo.uri}" } }
+
+dependencies {
+    compile 'org:foo:1.0'
+}
+
+configurations.all {
+    resolutionStrategy {
+        force 'org:foo:1.5'
+        force 'org:foo:1.9'
+        force 'org:foo:2.0'
+    }
+}
+
+task checkDeps << {
+    assert configurations.compile*.name == ['foo-2.0.jar']
+}
+"""
+
+        expect:
+        run("checkDeps")
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy
new file mode 100644
index 0000000..23e3602
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependenciesIntegrationTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.integtests.resolve
+
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+import spock.lang.Issue
+
+/**
+ * by Szczepan Faber, created at: 11/21/12
+ */
+class ProjectDependenciesIntegrationTest extends AbstractDependencyResolutionTest {
+
+    @Issue("GRADLE-2477") //this is a feature on its own but also covers one of the reported issues
+    def "resolving project dependency triggers configuration of the target project"() {
+        settingsFile << "include 'impl'"
+        buildFile << """
+            apply plugin: 'java'
+            dependencies {
+                compile project(":impl")
+            }
+            repositories {
+                //resolving project must declare the repo
+                maven { url '${mavenRepo.uri}' }
+            }
+            println "Resolved at configuration time: " + configurations.compile.files*.name
+        """
+
+        mavenRepo.module("org", "foo").publish()
+        file("impl/build.gradle") << """
+            apply plugin: 'java'
+            dependencies {
+                compile "org:foo:1.0"
+            }
+        """
+
+        when:
+        run()
+
+        then:
+        result.output.contains "Resolved at configuration time: [impl.jar, foo-1.0.jar]"
+    }
+
+    def "configuring project dependencies by map is validated"() {
+        settingsFile << "include 'impl'"
+        buildFile << """
+            allprojects { configurations.add('conf') }
+            task extraKey << {
+                def dep = dependencies.project(path: ":impl", configuration: ":conf", foo: "bar")
+                assert dep.foo == "bar"
+            }
+            task missingPath << {
+                dependencies.project(paths: ":impl", configuration: ":conf")
+            }
+            task missingConfiguration << {
+                dependencies.project(path: ":impl", configurations: ":conf")
+            }
+        """
+
+        when:
+        executer.withDeprecationChecksDisabled()
+        run("extraKey")
+
+        then:
+        noExceptionThrown()
+
+        when:
+        executer.withDeprecationChecksDisabled()
+        run("missingConfiguration")
+
+        then:
+        noExceptionThrown()
+
+        when:
+        runAndFail("missingPath")
+
+        then:
+        failureHasCause("Required keys [path] are missing from map")
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy
index 3434139..aae6410 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ResolutionStrategySamplesIntegrationTest.groovy
@@ -17,24 +17,36 @@
 package org.gradle.integtests.resolve
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.junit.Rule
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.util.Requires
-import org.gradle.util.TestPrecondition
+import org.junit.Rule
 
 /**
  * @author Szczepan Faber, @date 03.03.11
  */
 class ResolutionStrategySamplesIntegrationTest extends AbstractIntegrationSpec {
 
-    @Rule public final Sample sample = new Sample('userguide/artifacts/resolutionStrategy')
+    @Rule public final Sample sample = new Sample(temporaryFolder, 'userguide/artifacts/resolutionStrategy')
 
-    @Requires(TestPrecondition.NOT_JDK5)
-    //because some of the api of the samples does not work with JDK5, see GRADLE-1949
     void "can resolve dependencies"()
     {
+        mavenRepo.module("org", "foo").publish()
+        mavenRepo.module("org", "bar").publish()
+        mavenRepo.module("org.gradle", "gradle-core", "1.4").publish()
+        mavenRepo.module("org.software", "some-library", "1.2.1").publish()
+        mavenRepo.module("org.codehaus", "groovy", "1.7").publish()
+        mavenRepo.module("org.slf4j", "log4j-over-slf4j", "1.7.2").publish()
+
         sample.dir.file("build.gradle") << """
             configurations { conf }
+            repositories { maven { url "${mavenRepo.uri}" } }
+            dependencies {
+                conf "org:foo:1.0"
+                conf "org.gradle:gradle-core:1.0"
+                conf "org:bar:default"
+                conf "org.software:some-library:1.2"
+                conf "org.codehaus:groovy-all:1.7"
+                conf "log4j:log4j:1.2"
+            }
             task resolveConf << { configurations.conf.files }
         """
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy
index f166133..0d4da25 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/VersionConflictResolutionIntegrationTest.groovy
@@ -15,31 +15,28 @@
  */
 package org.gradle.integtests.resolve
 
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.junit.Test
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import spock.lang.Issue
 
+import static org.gradle.util.TextUtil.toPlatformLineSeparators
 import static org.hamcrest.Matchers.containsString
 
 /**
  * @author Szczepan Faber, @date 03.03.11
  */
-class VersionConflictResolutionIntegrationTest extends AbstractIntegrationTest {
+class VersionConflictResolutionIntegrationTest extends AbstractIntegrationSpec {
 
-    @Test
     void "strict conflict resolution should fail due to conflict"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
 
-        def settingsFile = file("settings.gradle")
         settingsFile << "include 'api', 'impl', 'tool'"
 
-        def buildFile = file("build.gradle")
         buildFile << """
 allprojects {
 	apply plugin: 'java'
 	repositories {
-		maven { url "${repo.uri}" }
+		maven { url "${mavenRepo.uri}" }
 	}
 }
 
@@ -65,26 +62,21 @@ project(':tool') {
 }
 """
 
-        //when
-        def result = executer.withTasks("tool:dependencies").runWithFailure()
-
-        //then
-        result.assertThatCause(containsString('A conflict was found between the following modules:'))
+        expect:
+        runAndFail("tool:dependencies")
+        failure.assertThatCause(containsString('A conflict was found between the following modules:'))
     }
 
-    @Test
     void "strict conflict resolution should pass when no conflicts"() {
-        repo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.3.3').publish()
 
-        def settingsFile = file("settings.gradle")
         settingsFile << "include 'api', 'impl', 'tool'"
 
-        def buildFile = file("build.gradle")
         buildFile << """
 allprojects {
 	apply plugin: 'java'
 	repositories {
-		maven { url "${repo.uri}" }
+		maven { url "${mavenRepo.uri}" }
 	}
 }
 
@@ -110,194 +102,21 @@ project(':tool') {
 }
 """
 
-        //when
-        executer.withTasks("tool:dependencies").run()
-
-        //then no exceptions are thrown
-    }
-
-    @Test
-    void "strict conflict strategy can be used with forced modules"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-        repo.module("org", "foo", '1.5.5').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-		compile('org:foo:1.5.5'){
-		    force = true
-		}
-	}
-
-	configurations.all { resolutionStrategy.failOnVersionConflict() }
-}
-"""
-
-        //when
-        executer.withTasks("tool:dependencies").run()
-
-        //then no exceptions are thrown because we forced a certain version of conflicting dependency
-    }
-
-    @Test
-    void "can force already resolved version of a module and avoid conflict"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':tool') {
-
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-}
-
-allprojects {
-    configurations.all {
-	    resolutionStrategy {
-	        force 'org:foo:1.3.3'
-	        failOnVersionConflict()
-	    }
-	}
-}
-
-"""
-
-        //when
-        executer.withTasks("api:dependencies", "tool:dependencies").run()
-
-        //then no exceptions are thrown because we forced a certain version of conflicting dependency
-    }
-
-    @Test
-    void "can force arbitrary version of a module and avoid conflict"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foobar", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-        repo.module("org", "foo", '1.5.5').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-	group = 'org.foo.unittests'
-	version = '1.0'
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-    task checkDeps(dependsOn: configurations.compile) << {
-        assert configurations.compile*.name == ['api-1.0.jar', 'impl-1.0.jar', 'foo-1.5.5.jar']
-        def metaData = configurations.compile.resolvedConfiguration
-        def api = metaData.firstLevelModuleDependencies.find { it.moduleName == 'api' }
-        assert api.children.size() == 1
-        assert api.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
-        def impl = metaData.firstLevelModuleDependencies.find { it.moduleName == 'impl' }
-        assert impl.children.size() == 1
-        assert impl.children.find { it.moduleName == 'foo' && it.moduleVersion == '1.5.5' }
-    }
-}
-
-allprojects {
-    configurations.all {
-        resolutionStrategy {
-            failOnVersionConflict()
-            force 'org:foo:1.5.5'
-        }
-    }
-}
-
-"""
-
-        // expect
-        executer.withTasks(":tool:checkDeps").run()
+        expect:
+        run("tool:dependencies")
     }
 
-    @Test
     void "resolves to the latest version by default"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
+        mavenRepo.module("org", "foo", '1.3.3').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
 
-        def settingsFile = file("settings.gradle")
         settingsFile << "include 'api', 'impl', 'tool'"
 
-        def buildFile = file("build.gradle")
         buildFile << """
 allprojects {
 	apply plugin: 'java'
 	repositories {
-		maven { url "${repo.uri}" }
+		maven { url "${mavenRepo.uri}" }
 	}
 }
 
@@ -324,153 +143,17 @@ project(':tool') {
 }
 """
 
-        //expect
-        executer.withTasks("tool:checkDeps").run()
-    }
-
-    @Test
-    void "latest strategy respects forced modules"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def settingsFile = file("settings.gradle")
-        settingsFile << "include 'api', 'impl', 'tool'"
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-allprojects {
-	apply plugin: 'java'
-	repositories {
-		maven { url "${repo.uri}" }
-	}
-}
-
-project(':api') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.3.3')
-	}
-}
-
-project(':impl') {
-	dependencies {
-		compile (group: 'org', name: 'foo', version:'1.4.4')
-	}
-}
-
-project(':tool') {
-	dependencies {
-		compile project(':api')
-		compile project(':impl')
-	}
-	configurations.all {
-	    resolutionStrategy {
-	        failOnVersionConflict()
-	        force 'org:foo:1.3.3'
-	    }
-	}
-    task checkDeps(dependsOn: configurations.compile) << {
-        assert configurations.compile*.name == ['api.jar', 'impl.jar', 'foo-1.3.3.jar']
-    }
-}
-"""
-
-        //expect
-        executer.withTasks("tool:checkDeps").run()
+        expect:
+        run("tool:checkDeps")
     }
 
-    @Test
-    void "can force the version of a particular module"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-apply plugin: 'java'
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-dependencies {
-    compile 'org:foo:1.3.3'
-}
-
-configurations.all {
-    resolutionStrategy.force 'org:foo:1.4.4'
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['foo-1.4.4.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    void "can force the version of a direct dependency"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-apply plugin: 'java'
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-dependencies {
-    compile 'org:foo:1.4.4'
-    compile ('org:foo:1.3.3') { force = true }
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['foo-1.3.3.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
-    void "forcing transitive dependency does not add extra dependency"() {
-        repo.module("org", "foo", '1.3.3').publish()
-        repo.module("hello", "world", '1.4.4').publish()
-
-        def buildFile = file("build.gradle")
-        buildFile << """
-apply plugin: 'java'
-repositories {
-    maven { url "${repo.uri}" }
-}
-
-dependencies {
-    compile 'org:foo:1.3.3'
-}
-
-configurations.all {
-    resolutionStrategy.force 'hello:world:1.4.4'
-}
-
-task checkDeps << {
-    assert configurations.compile*.name == ['foo-1.3.3.jar']
-}
-"""
-
-        //expect
-        executer.withTasks("checkDeps").run()
-    }
-
-    @Test
     void "does not attempt to resolve an evicted dependency"() {
-        repo.module("org", "external", "1.2").publish()
-        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.0").publish()
+        mavenRepo.module("org", "external", "1.2").publish()
+        mavenRepo.module("org", "dep", "2.2").dependsOn("org", "external", "1.0").publish()
 
-        def buildFile = file("build.gradle")
         buildFile << """
 repositories {
-    maven { url "${repo.uri}" }
+    maven { url "${mavenRepo.uri}" }
 }
 
 configurations { compile }
@@ -485,20 +168,19 @@ task checkDeps << {
 }
 """
 
-        //expect
-        executer.withTasks("checkDeps").run()
+        expect:
+        run("checkDeps")
     }
 
-    @Test
     void "resolves dynamic dependency before resolving conflict"() {
-        repo.module("org", "external", "1.2").publish()
-        repo.module("org", "external", "1.4").publish()
-        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.+").publish()
+        mavenRepo.module("org", "external", "1.2").publish()
+        mavenRepo.module("org", "external", "1.4").publish()
+        mavenRepo.module("org", "dep", "2.2").dependsOn("org", "external", "1.+").publish()
 
         def buildFile = file("build.gradle")
         buildFile << """
 repositories {
-    maven { url "${repo.uri}" }
+    maven { url "${mavenRepo.uri}" }
 }
 
 configurations { compile }
@@ -513,19 +195,17 @@ task checkDeps << {
 }
 """
 
-        //expect
-        executer.withTasks("checkDeps").run()
+        expect:
+        run("checkDeps")
     }
 
-    @Test
     void "fails when version selected by conflict resolution does not exist"() {
-        repo.module("org", "external", "1.2").publish()
-        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
+        mavenRepo.module("org", "external", "1.2").publish()
+        mavenRepo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
 
-        def buildFile = file("build.gradle")
         buildFile << """
 repositories {
-    maven { url "${repo.uri}" }
+    maven { url "${mavenRepo.uri}" }
 }
 
 configurations { compile }
@@ -540,20 +220,18 @@ task checkDeps << {
 }
 """
 
-        //expect
-        def failure = executer.withTasks("checkDeps").runWithFailure()
+        expect:
+        runAndFail("checkDeps")
         failure.assertHasCause("Could not find org:external:1.4.")
     }
 
-    @Test
     void "does not fail when evicted version does not exist"() {
-        repo.module("org", "external", "1.4").publish()
-        repo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
+        mavenRepo.module("org", "external", "1.4").publish()
+        mavenRepo.module("org", "dep", "2.2").dependsOn("org", "external", "1.4").publish()
 
-        def buildFile = file("build.gradle")
         buildFile << """
 repositories {
-    maven { url "${repo.uri}" }
+    maven { url "${mavenRepo.uri}" }
 }
 
 configurations { compile }
@@ -568,30 +246,26 @@ task checkDeps << {
 }
 """
 
-        //expect
-        executer.withTasks("checkDeps").run()
+        expect:
+        run("checkDeps")
     }
 
-    @Test
     void "takes newest dynamic version when dynamic version forced"() {
-        //given
-        repo.module("org", "foo", '1.3.0').publish()
+        mavenRepo.module("org", "foo", '1.3.0').publish()
 
-        repo.module("org", "foo", '1.4.1').publish()
-        repo.module("org", "foo", '1.4.4').publish()
-        repo.module("org", "foo", '1.4.9').publish()
+        mavenRepo.module("org", "foo", '1.4.1').publish()
+        mavenRepo.module("org", "foo", '1.4.4').publish()
+        mavenRepo.module("org", "foo", '1.4.9').publish()
 
-        repo.module("org", "foo", '1.6.0').publish()
+        mavenRepo.module("org", "foo", '1.6.0').publish()
 
-        def settingsFile = file("settings.gradle")
         settingsFile << "include 'api', 'impl', 'tool'"
 
-        def buildFile = file("build.gradle")
         buildFile << """
 allprojects {
 	apply plugin: 'java'
 	repositories {
-		maven { url "${repo.uri}" }
+		maven { url "${mavenRepo.uri}" }
 	}
 }
 
@@ -627,27 +301,25 @@ project(':tool') {
 
 """
 
-        //expect
-        executer.withTasks("tool:checkDeps").run()
+        expect:
+        run("tool:checkDeps")
     }
 
-    @Test
     void "parent pom does not participate in forcing mechanism"() {
-        //given
-        repo.module("org", "foo", '1.3.0').publish()
-        repo.module("org", "foo", '2.4.0').publish()
+        mavenRepo.module("org", "foo", '1.3.0').publish()
+        mavenRepo.module("org", "foo", '2.4.0').publish()
 
-        def parent = repo.module("org", "someParent", "1.0")
+        def parent = mavenRepo.module("org", "someParent", "1.0")
         parent.type = 'pom'
         parent.dependsOn("org", "foo", "1.3.0")
         parent.publish()
 
-        def otherParent = repo.module("org", "someParent", "2.0")
+        def otherParent = mavenRepo.module("org", "someParent", "2.0")
         otherParent.type = 'pom'
         otherParent.dependsOn("org", "foo", "2.4.0")
         otherParent.publish()
 
-        def module = repo.module("org", "someArtifact", '1.0')
+        def module = mavenRepo.module("org", "someArtifact", '1.0')
         module.parentPomSection = """
 <parent>
   <groupId>org</groupId>
@@ -657,11 +329,10 @@ project(':tool') {
 """
         module.publish()
 
-        def buildFile = file("build.gradle")
         buildFile << """
 apply plugin: 'java'
 repositories {
-    maven { url "${repo.uri}" }
+    maven { url "${mavenRepo.uri}" }
 }
 
 dependencies {
@@ -683,11 +354,10 @@ task checkDeps << {
 }
 """
 
-        //expect
-        executer.withTasks("checkDeps").withArguments('-s').run()
+        expect:
+        run("checkDeps")
     }
 
-    @Test
     void "previously evicted nodes should contain correct target version"() {
         /*
         a1->b1
@@ -708,7 +378,7 @@ task checkDeps << {
         ivyRepo.module("org", "b", '2.0').dependsOn("org", "a", "1.0").publish()
         ivyRepo.module("org", "a", '2.0').dependsOn("org", "b", '2.0').publish()
 
-        file("build.gradle") << """
+        buildFile << """
             repositories {
                 ivy { url "${ivyRepo.uri}" }
             }
@@ -732,10 +402,10 @@ task checkDeps << {
             }
         """
 
-        executer.withTasks("checkDeps").run()
+        expect:
+        run("checkDeps")
     }
 
-    @Test
     @Issue("GRADLE-2555")
     void "can deal with transitive with parent in conflict"() {
         /*
@@ -754,38 +424,19 @@ task checkDeps << {
             - Having "b" depend directly on "in-conflict" does not produce the error, needs to go through "b-child"
          */
 
-        mavenRepo.module("org", "target-child", "1.0").
-                publish()
-
-        mavenRepo.module("org", "target", "1.0").
-                dependsOn("org", "target-child", "1.0").
-                publish()
-
-        mavenRepo.module("org", "in-conflict", "1.0").
-                dependsOn("org", "target", "1.0").
-                publish()
-
-        mavenRepo.module("org", "in-conflict", "2.0").
-                dependsOn("org", "target", "1.0").
-                publish()
+        mavenRepo.module("org", "target-child", "1.0").publish()
+        mavenRepo.module("org", "target", "1.0").dependsOn("org", "target-child", "1.0").publish()
+        mavenRepo.module("org", "in-conflict", "1.0").dependsOn("org", "target", "1.0").publish()
+        mavenRepo.module("org", "in-conflict", "2.0").dependsOn("org", "target", "1.0").publish()
 
-        mavenRepo.module("org", "a", '1.0').
-                dependsOn("org", "in-conflict", "1.0").
-                publish()
+        mavenRepo.module("org", "a", '1.0').dependsOn("org", "in-conflict", "1.0").publish()
 
-        mavenRepo.module("org", "b-child", '1.0').
-                dependsOn("org", "in-conflict", "2.0").
-                publish()
+        mavenRepo.module("org", "b-child", '1.0').dependsOn("org", "in-conflict", "2.0").publish()
 
-        mavenRepo.module("org", "b", '1.0').
-                dependsOn("org", "b-child", "1.0").
-                publish()
+        mavenRepo.module("org", "b", '1.0').dependsOn("org", "b-child", "1.0").publish()
 
-        when:
-        file("build.gradle") << """
-            repositories {
-                maven { url "${mavenRepo.uri}" }
-            }
+        buildFile << """
+            repositories { maven { url "${mavenRepo.uri}" } }
 
             configurations { conf }
 
@@ -806,12 +457,77 @@ task checkDeps << {
         }
         """
 
-        executer.withTasks("checkDeps").run()
+        expect:
+        run("checkDeps")
     }
 
-    //TODO SF add coverage with conflicting forced modules
+    @Issue("GRADLE-2555")
+    void "batched up conflicts with conflicted parent and child"() {
+        /*
+        Dependency tree:
+
+        a->c1
+        b->c2->x1
+        d->x1
+        f->x2
 
-    def getRepo() {
-        return maven(file("repo"))
+        Everything is resolvable but not x2
+
+        Scenario:
+         - We have batched up conflicts
+         - parent of one conflicted version is also conflicted
+         - conflicted parent is positioned on the conflicts queue after the conflicted child (the order of declaring dependencies matters)
+         - winning parent depends on a child that previously was evicted
+         - finally, the winning child is an unresolved dependency
+        */
+        mavenRepo.module("org", "c", '1.0').publish()
+        mavenRepo.module("org", "x", '1.0').publish()
+        mavenRepo.module("org", "c", '2.0').dependsOn("org", "x", '1.0').publish()
+        mavenRepo.module("org", "a").dependsOn("org", "c", "1.0").publish()
+        mavenRepo.module("org", "b").dependsOn("org", "c", "2.0").publish()
+        mavenRepo.module("org", "d").dependsOn("org", "x", "1.0").publish()
+        mavenRepo.module("org", "f").dependsOn("org", "x", "2.0").publish()
+
+        buildFile << """
+            repositories { maven { url "${mavenRepo.uri}" } }
+            configurations {
+                childFirst
+                parentFirst
+            }
+            dependencies {
+                //conflicted child is resolved first
+                childFirst 'org:d:1.0', 'org:f:1.0', 'org:a:1.0', 'org:b:1.0'
+                //conflicted parent is resolved first
+                parentFirst 'org:a:1.0', 'org:b:1.0', 'org:d:1.0', 'org:f:1.0'
+            }
+        """
+
+        when:
+        run("dependencies")
+
+        then:
+        //TODO SF proper assertions
+        output.contains(toPlatformLineSeparators("""
+childFirst
++--- org:d:1.0
+|    \\--- org:x:1.0 -> 2.0 FAILED
++--- org:f:1.0
+|    \\--- org:x:2.0 FAILED
++--- org:a:1.0
+|    \\--- org:c:1.0 -> 2.0
+|         \\--- org:x:1.0 -> 2.0 FAILED
+\\--- org:b:1.0
+     \\--- org:c:2.0 (*)
+
+parentFirst
++--- org:a:1.0
+|    \\--- org:c:1.0 -> 2.0
+|         \\--- org:x:1.0 -> 2.0 FAILED
++--- org:b:1.0
+|    \\--- org:c:2.0 (*)
++--- org:d:1.0
+|    \\--- org:x:1.0 -> 2.0 FAILED
+\\--- org:f:1.0
+     \\--- org:x:2.0 FAILED"""))
     }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
index 10de9b2..8adfc88 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/AliasedArtifactResolutionIntegrationTest.groovy
@@ -24,7 +24,7 @@ class AliasedArtifactResolutionIntegrationTest extends AbstractDependencyResolut
     def ivyRepo1 = ivyHttpRepo("ivy1")
     def ivyRepo2 = ivyHttpRepo("ivy2")
 
-    def "setup"() {
+    def setup() {
         server.start()
 
         buildFile << """
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
index 01a9040..65c1318 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/M3CacheReuseCrossVersionIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.test.fixtures.maven.MavenHttpRepository
 import org.gradle.test.fixtures.server.http.HttpServer
 import org.junit.Rule
 
-// TODO:DAZ Support for milestone-3 does not include POM reuse. We should probably ditch milestone-3 support after 1.0.
+// Special case for milestone-3 since that version (and earlier versions) do not include POM reuse.
 @TargetVersions('1.0-milestone-3')
 class M3CacheReuseCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
     @Rule public final HttpServer server = new HttpServer()
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
index 9e6662f..291946f 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/MavenM2CacheReuseIntegrationTest.groovy
@@ -16,13 +16,12 @@
 package org.gradle.integtests.resolve.artifactreuse
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
-import org.gradle.test.fixtures.maven.M2Installation
 
 class MavenM2CacheReuseIntegrationTest extends AbstractDependencyResolutionTest {
     def "uses cached artifacts from maven local cache"() {
         given:
         def module1 = mavenHttpRepo.module('gradletest.maven.local.cache.test', "foo", "1.0").publish()
-        def m2Installation = new M2Installation(testDirectory).generateGlobalSettingsFile()
+        m2Installation.generateGlobalSettingsFile()
         def module2 = m2Installation.mavenRepo().module('gradletest.maven.local.cache.test', "foo", "1.0").publish()
         server.start()
 
@@ -52,4 +51,4 @@ task retrieve(type: Sync) {
         then:
         file('build/foo-1.0.jar').assertIsCopyOf(module2.artifactFile)
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
index 737544f..1c79efa 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/artifactreuse/ResolutionOverrideIntegrationTest.groovy
@@ -16,7 +16,6 @@
 package org.gradle.integtests.resolve.artifactreuse
 
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
-import org.hamcrest.Matchers
 
 class ResolutionOverrideIntegrationTest extends AbstractDependencyResolutionTest {
     public void "will refresh non-changing module when run with --refresh-dependencies"() {
@@ -216,7 +215,7 @@ task listJars << {
 
         and:
         failure.assertHasDescription('Execution failed for task \':listJars\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
-        failure.assertThatCause(Matchers.containsString('No cached version of org.name:projectA:1.2 available for offline mode'))
+        failure.assertResolutionFailure(":compile")
+            .assertHasCause('No cached version of org.name:projectA:1.2 available for offline mode')
     }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
index 16ac149..67e2a21 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvySFtpResolverIntegrationTest.groovy
@@ -26,7 +26,7 @@ class IvySFtpResolverIntegrationTest extends AbstractIntegrationSpec {
     @Rule
     public final SFTPServer server = new SFTPServer(this)
 
-    @Rule ProgressLoggingFixture progressLogging
+    @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
 
     def "setup"() {
         requireOwnGradleUserHomeDir()
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
index cf784ff..f9d2964 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/custom/IvyUrlResolverIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.junit.Rule
 
 class IvyUrlResolverIntegrationTest extends AbstractDependencyResolutionTest {
 
-    @Rule ProgressLoggingFixture progressLogging
+    @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
 
     def setup() {
         server.expectUserAgent(null) // custom resolver uses apache/ivy as useragent strings
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
index 3458408..d64a2e1 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/AbstractHttpsRepoResolveIntegrationTest.groovy
@@ -23,7 +23,7 @@ import org.junit.Rule
 import static org.gradle.util.Matchers.containsText
 
 abstract class AbstractHttpsRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
-    @Rule TestResources resources
+    @Rule TestResources resources = new TestResources(temporaryFolder)
     File clientStore // contains the client's public and private keys
     File serverStore // contains the server's public and private keys
 
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
index 4f780ca..cce1527 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/http/HttpAuthenticationDependencyResolutionIntegrationTest.groovy
@@ -163,9 +163,10 @@ task listJars << {
         fails 'listJars'
 
         and:
-        failure.assertHasDescription('Execution failed for task \':listJars\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
-        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+        failure
+            .assertHasDescription('Execution failed for task \':listJars\'.')
+            .assertResolutionFailure(':compile')
+            .assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
 
         where:
         authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST, HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
@@ -207,9 +208,10 @@ task listJars << {
         fails 'listJars'
 
         and:
-        failure.assertHasDescription('Execution failed for task \':listJars\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':compile\'.')
-        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+        failure
+            .assertHasDescription('Execution failed for task \':listJars\'.')
+            .assertResolutionFailure(':compile')
+            .assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
 
         where:
         authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST, HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
index b9630a2..fca2690 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyBrokenRemoteResolveIntegrationTest.groovy
@@ -81,10 +81,11 @@ task showBroken << { println configurations.broken.files }
         fails("showBroken")
 
         then:
-        failure.assertHasDescription('Execution failed for task \':showBroken\'.')
-        failure.assertHasCause('Could not resolve all dependencies for configuration \':broken\'.')
-        failure.assertHasCause('Could not resolve group:projectA:1.3.')
-        failure.assertHasCause("Could not GET '${ivyHttpRepo.uri}/group/projectA/1.3/ivy-1.3.xml'. Received status code 500 from server: broken")
+        failure
+            .assertHasDescription('Execution failed for task \':showBroken\'.')
+            .assertResolutionFailure(':broken')
+            .assertHasCause('Could not resolve group:projectA:1.3.')
+            .assertHasCause("Could not GET '${ivyHttpRepo.uri}/group/projectA/1.3/ivy-1.3.xml'. Received status code 500 from server: broken")
 
         when:
         server.resetExpectations()
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorResolveIntegrationTest.groovy
new file mode 100644
index 0000000..858d735
--- /dev/null
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyDescriptorResolveIntegrationTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.resolve.ivy
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
+
+class IvyDescriptorResolveIntegrationTest extends AbstractDependencyResolutionTest {
+    def "substitutes system properties into ivy descriptor"() {
+        given:
+        ivyRepo.module("org.gradle", "test", "1.45")
+                .dependsOn('org.gradle.${sys_prop}', 'module_${sys_prop}', 'v_${sys_prop}')
+                .publish()
+
+        ivyRepo.module("org.gradle.111", "module_111", "v_111").publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${ivyRepo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45"
+}
+
+task check << {
+    configurations.compile.resolvedConfiguration.firstLevelModuleDependencies.each {
+        it.children.each { transitive ->
+            assert transitive.moduleGroup == "org.gradle.111"
+            assert transitive.moduleName == "module_111"
+            assert transitive.moduleVersion == "v_111"
+        }
+    }
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'module_111-v_111.jar']
+}
+"""
+
+        when:
+        executer.withArgument("-Dsys_prop=111")
+
+        then:
+        succeeds "check"
+    }
+
+    def "merges values from included descriptor file"() {
+        given:
+        final parentModule = ivyRepo.module("org.gradle.parent", "parent_module", "1.1").dependsOn("org.gradle.dep", "dep_module", "1.1").publish()
+        ivyRepo.module("org.gradle.dep", "dep_module", "1.1").publish()
+
+        final module = ivyRepo.module("org.gradle", "test", "1.45")
+        final extendAttributes = ["organisation": "org.gradle.parent", "module": "parent_module", "revision": "1.1"]
+        if (includeLocation) {
+            extendAttributes["location"] = parentModule.ivyFile.absolutePath
+        }
+        module.withXml {
+            asNode().info[0].appendNode("extends", extendAttributes)
+        }
+        module.publish()
+
+        and:
+        buildFile << """
+repositories { ivy { url "${ivyRepo.uri}" } }
+configurations { compile }
+dependencies {
+    compile "org.gradle:test:1.45"
+}
+
+task check << {
+    assert configurations.compile.collect { it.name } == ['test-1.45.jar', 'dep_module-1.1.jar']
+}
+"""
+
+        expect:
+        succeeds "check"
+
+        where:
+        name | includeLocation
+        "with explicit location" | true
+        "without explicit location" | false
+    }
+}
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
index 61d01d5..5f51151 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyHttpRepoResolveIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.junit.Rule
 
 class IvyHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
-    @Rule ProgressLoggingFixture progressLogger
+    @Rule ProgressLoggingFixture progressLogger = new ProgressLoggingFixture(executer, temporaryFolder)
 
     public void "can resolve and cache dependencies from an HTTP Ivy repository"() {
         server.start()
@@ -220,4 +220,72 @@ task retrieve(type: Sync) {
         then:
         file('libs').assertHasDescendants('projectA-1.2.jar')
     }
+
+    def "reuses cached details when switching ivy resolve mode"() {
+        given:
+        server.start()
+        buildFile << """
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy {
+            url "${ivyHttpRepo.uri}"
+            resolve.dynamicMode = project.hasProperty('useDynamicResolve')
+        }
+    }
+    compile 'org:projectA:1.2'
+}
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+        def moduleA = ivyHttpRepo.module('org', 'projectA', '1.2')
+                .dependsOn(organisation: 'org', module: 'projectB', revision: '1.5', revConstraint: 'latest.integration')
+                .publish()
+
+        def moduleB15 = ivyHttpRepo.module('org', 'projectB', '1.5')
+                .publish()
+
+        def moduleB16 = ivyHttpRepo.module('org', 'projectB', '1.6')
+                .publish()
+
+        when:
+        moduleA.expectIvyGet()
+        moduleA.expectJarGet()
+        moduleB15.expectIvyGet()
+        moduleB15.expectJarGet()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.5.jar')
+
+        when:
+        server.resetExpectations()
+        ivyHttpRepo.expectDirectoryListGet('org', 'projectB')
+        moduleB16.expectIvyGet()
+        moduleB16.expectJarGet()
+        executer.withArguments("-PuseDynamicResolve=true")
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.6.jar')
+
+        when:
+        server.resetExpectations()
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.5.jar')
+
+        when:
+        server.resetExpectations()
+        executer.withArguments("-PuseDynamicResolve=true")
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.6.jar')
+    }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
index 2d81061..5197a3b 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/ivy/IvyResolveIntegrationTest.groovy
@@ -18,20 +18,19 @@ package org.gradle.integtests.resolve.ivy
 import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 
 class IvyResolveIntegrationTest extends AbstractDependencyResolutionTest {
-
     def "dependency includes all artifacts and transitive dependencies of referenced configuration"() {
         given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: "classifier")
-        module.artifact(name: "test-extra")
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
+        ivyRepo.module("org.gradle", "test", "1.45")
+                .dependsOn("org.gradle", "other", "preview-1")
+                .artifact(classifier: "classifier")
+                .artifact(name: "test-extra")
+                .publish()
+
+        ivyRepo.module("org.gradle", "other", "preview-1").publish()
 
         and:
         buildFile << """
-repositories { ivy { url "${repo.uri}" } }
+repositories { ivy { url "${ivyRepo.uri}" } }
 configurations { compile }
 dependencies {
     compile "org.gradle:test:1.45"
@@ -48,17 +47,16 @@ task check << {
 
     def "dependency that references a classifier includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
         given:
-        def repo = ivyRepo()
-        repo.module("org.gradle", "test", "1.45")
+        ivyRepo.module("org.gradle", "test", "1.45")
                 .dependsOn("org.gradle", "other", "preview-1")
                 .artifact(classifier: "classifier")
                 .artifact(name: "test-extra")
                 .publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
+        ivyRepo.module("org.gradle", "other", "preview-1").publish()
 
         and:
         buildFile << """
-repositories { ivy { url "${repo.uri}" } }
+repositories { ivy { url "${ivyRepo.uri}" } }
 configurations { compile }
 dependencies {
     compile "org.gradle:test:1.45:classifier"
@@ -75,17 +73,16 @@ task check << {
 
     def "dependency that references an artifact includes the matching artifact only plus the transitive dependencies of referenced configuration"() {
         given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.artifact(classifier: "classifier")
-        module.artifact(name: "test-extra")
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").publish()
+        ivyRepo.module("org.gradle", "test", "1.45")
+                .dependsOn("org.gradle", "other", "preview-1")
+                .artifact(classifier: "classifier")
+                .artifact(name: "test-extra")
+                .publish()
+        ivyRepo.module("org.gradle", "other", "preview-1").publish()
 
         and:
         buildFile << """
-repositories { ivy { url "${repo.uri}" } }
+repositories { ivy { url "${ivyRepo.uri}" } }
 configurations { compile }
 dependencies {
     compile ("org.gradle:test:1.45") {
@@ -107,17 +104,16 @@ task check << {
 
     def "transitive flag of referenced configuration affects its transitive dependencies only"() {
         given:
-        def repo = ivyRepo()
-        def module = repo.module("org.gradle", "test", "1.45")
-        module.dependsOn("org.gradle", "other", "preview-1")
-        module.nonTransitive('default')
-        module.publish()
-        repo.module("org.gradle", "other", "preview-1").dependsOn("org.gradle", "other2", "7").publish()
-        repo.module("org.gradle", "other2", "7").publish()
+        ivyRepo.module("org.gradle", "test", "1.45")
+                .dependsOn("org.gradle", "other", "preview-1")
+                .nonTransitive('default')
+                .publish()
+        ivyRepo.module("org.gradle", "other", "preview-1").dependsOn("org.gradle", "other2", "7").publish()
+        ivyRepo.module("org.gradle", "other2", "7").publish()
 
         and:
         buildFile << """
-repositories { ivy { url "${repo.uri}" } }
+repositories { ivy { url "${ivyRepo.uri}" } }
 configurations {
     compile
     runtime.extendsFrom compile
@@ -141,4 +137,111 @@ task check << {
         expect:
         succeeds "check"
     }
+
+    def "correctly handles wildcard configuration mapping in transitive dependencies"() {
+        given:
+        buildFile << """
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy { url "${ivyRepo.uri}" }
+    }
+    compile 'ivy.configuration:projectA:1.2'
+}
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+        when: "projectA uses a wildcard configuration mapping for dependency on projectB"
+        def moduleA = ivyRepo.module('ivy.configuration', 'projectA', '1.2')
+                .configuration('parent')
+                .artifact([:])
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectB', revision: '1.5', conf: 'runtime->*')
+                .publish()
+
+        ivyRepo.module('ivy.configuration', 'projectB', '1.5')
+                .configuration('child')
+                .artifact([name: 'projectB', conf: 'runtime'])
+                .artifact([name: 'projectB-child', conf: 'child'])
+                .dependsOn(organisation: 'ivy.configuration', module: 'projectC', revision: '1.7', conf: 'child->*')
+                .publish()
+
+        ivyRepo.module('ivy.configuration', 'projectC', '1.7').artifact([:]).publish()
+
+        and:
+        succeeds 'retrieve'
+
+        then: "artifacts and dependencies from all configurations of projectB are included"
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.5.jar', 'projectB-child-1.5.jar', 'projectC-1.7.jar')
+
+        when: "projectB-1.5 is replaced by conflict resolution with projectB-1.6 that has a different set of configurations"
+
+        ivyRepo.module('ivy.configuration', 'projectB', '1.6')
+                .configuration('other')
+                .artifact([name: 'projectB-other', conf: 'other'])
+                .publish()
+
+        ivyRepo.module('ivy.configuration', 'projectD', '1.0')
+                .dependsOn('ivy.configuration', 'projectB', '1.6')
+                .publish()
+
+        moduleA.dependsOn('ivy.configuration', 'projectD', '1.0').publish()
+
+        and:
+        succeeds 'retrieve'
+
+        then: "we resolve artifacts from projectB-1.6 only"
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.6.jar', 'projectB-other-1.6.jar', 'projectD-1.0.jar')
+    }
+
+    def "prefers revConstraint over rev when dynamic resolve mode is used"() {
+        given:
+        buildFile << """
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        ivy {
+            url "${ivyRepo.uri}"
+            resolve.dynamicMode = project.hasProperty('useDynamicResolve')
+        }
+    }
+    compile 'org:projectA:1.2'
+}
+task retrieve(type: Sync) {
+  from configurations.compile
+  into 'libs'
+}
+"""
+        ivyRepo.module('org', 'projectA', '1.2')
+                .dependsOn(organisation: 'org', module: 'projectB', revision: '1.5', revConstraint: '1.6')
+                .dependsOn(organisation: 'org', module: 'projectC', revision: 'alpha-12')
+                .publish()
+
+        ivyRepo.module('org', 'projectB', '1.5')
+                .publish()
+
+        ivyRepo.module('org', 'projectB', '1.6')
+                .publish()
+
+        ivyRepo.module('org', 'projectC', 'alpha-12')
+                .publish()
+
+        when:
+        executer.withArguments("-PuseDynamicResolve=true")
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.6.jar', 'projectC-alpha-12.jar')
+
+        when:
+        run 'retrieve'
+
+        then:
+        file('libs').assertHasDescendants('projectA-1.2.jar', 'projectB-1.5.jar', 'projectC-alpha-12.jar')
+    }
 }
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
index 6445672..caaf70a 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenHttpRepoResolveIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.junit.Rule
 
 class MavenHttpRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
-    @Rule ProgressLoggingFixture progressLogging
+    @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
 
     def "can resolve and cache dependencies from HTTP Maven repository"() {
         given:
diff --git a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
index 837e573..b764ff5 100644
--- a/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
+++ b/subprojects/core-impl/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy
@@ -15,20 +15,18 @@
  */
 package org.gradle.integtests.resolve.maven
 
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.test.fixtures.maven.M2Installation
+import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest
 import org.gradle.test.fixtures.maven.MavenModule
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
 
 import static org.hamcrest.Matchers.containsString
 
-class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
+class MavenLocalRepoResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
     @Rule SetSystemProperties sysProp = new SetSystemProperties()
-    M2Installation m2Installation;
 
-    public void setup() {
+    def setup() {
         requireOwnGradleUserHomeDir()
         buildFile << """
                 repositories {
@@ -44,11 +42,10 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
                     into 'build'
                 }"""
 
-        m2Installation = new M2Installation(testDirectory)
         using m2Installation
     }
 
-    public void "can resolve artifacts from local m2 when user settings.xml does not exist"() {
+    def "can resolve artifacts from local m2 when user settings.xml does not exist"() {
         given:
         def moduleA = m2Installation.mavenRepo().module('group', 'projectA', '1.2').publish()
 
@@ -60,7 +57,7 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
 
     }
 
-    public void "can resolve artifacts from local m2 with custom local repository defined in user settings.xml"() {
+    def "can resolve artifacts from local m2 with custom local repository defined in user settings.xml"() {
         given:
         def artifactRepo = maven("artifactrepo")
         m2Installation.generateUserSettingsFile(artifactRepo)
@@ -73,7 +70,7 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
         hasArtifact(moduleA)
     }
 
-    public void "can resolve artifacts from local m2 with custom local repository defined in global settings.xml"() {
+    def "can resolve artifacts from local m2 with custom local repository defined in global settings.xml"() {
         given:
         def artifactRepo = maven("artifactrepo")
         m2Installation.generateGlobalSettingsFile(artifactRepo)
@@ -86,7 +83,7 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
         hasArtifact(moduleA)
     }
 
-    public void "local repository in user settings take precedence over the local repository global settings"() {
+    def "local repository in user settings take precedence over the local repository global settings"() {
         given:
         def globalRepo = maven("globalArtifactRepo")
         def userRepo = maven("userArtifactRepo")
@@ -101,7 +98,7 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
         hasArtifact(moduleA)
     }
 
-    public void "fail with meaningful error message if settings.xml is invalid"() {
+    def "fail with meaningful error message if settings.xml is invalid"() {
         given:
         m2Installation.userSettingsFile << "invalid content"
 
@@ -112,7 +109,7 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractIntegrationSpec {
         failure.assertThatCause(containsString(String.format("Non-parseable settings %s:", m2Installation.userSettingsFile.absolutePath)));
     }
 
-    public void "mavenLocal is ignored if no local maven repository exists"() {
+    def "mavenLocal is ignored if no local maven repository exists"() {
         given:
         def anotherRepo = maven("another-local-repo")
         def moduleA = anotherRepo.module('group', 'projectA', '1.2').publishWithChangedContent()
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
index 874e154..7b5dae9 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactDependencyResolver.java
@@ -17,10 +17,13 @@ package org.gradle.api.internal.artifacts;
 
 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;
 
 /**
  * @author Hans Dockter
  */
 public interface ArtifactDependencyResolver {
-    ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException;
+    ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException;
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationResolver.java
new file mode 100644
index 0000000..501959b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationResolver.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.artifacts.ResolveException;
+import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ConfigurationResolver {
+    ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException;
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java
index 92fbe7d..69e7831 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyFactory.java
@@ -23,9 +23,8 @@ import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryDelegate;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
-import org.gradle.api.internal.notations.ClientModuleNotationParser;
-import org.gradle.api.internal.notations.DependencyNotationParser;
 import org.gradle.api.internal.notations.ProjectDependencyFactory;
+import org.gradle.api.internal.notations.api.NotationParser;
 
 import java.util.Map;
 
@@ -33,12 +32,12 @@ import java.util.Map;
  * @author Hans Dockter
  */
 public class DefaultDependencyFactory implements DependencyFactory {
-    private final DependencyNotationParser dependencyNotationParser;
-    private final ClientModuleNotationParser clientModuleNotationParser;
-    private ProjectDependencyFactory projectDependencyFactory;
+    private final NotationParser<Dependency> dependencyNotationParser;
+    private final NotationParser<ClientModule> clientModuleNotationParser;
+    private final ProjectDependencyFactory projectDependencyFactory;
 
-    public DefaultDependencyFactory(DependencyNotationParser dependencyNotationParser,
-                                    ClientModuleNotationParser clientModuleNotationParser,
+    public DefaultDependencyFactory(NotationParser<Dependency> dependencyNotationParser,
+                                    NotationParser<ClientModule> clientModuleNotationParser,
                                     ProjectDependencyFactory projectDependencyFactory) {
         this.dependencyNotationParser = dependencyNotationParser;
         this.clientModuleNotationParser = clientModuleNotationParser;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
index 5a8e4c9..59e2b1a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts;
 import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.gradle.StartParameter;
 import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.dsl.ArtifactHandler;
 import org.gradle.api.artifacts.dsl.DependencyHandler;
 import org.gradle.api.internal.ClassPathRegistry;
@@ -27,8 +28,8 @@ import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationCont
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
 import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler;
-import org.gradle.api.internal.artifacts.dsl.DefaultPublishArtifactFactory;
 import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
+import org.gradle.api.internal.artifacts.dsl.PublishArtifactNotationParserFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
@@ -61,6 +62,7 @@ import org.gradle.api.internal.filestore.ivy.ArtifactRevisionIdFileStore;
 import org.gradle.api.internal.notations.*;
 import org.gradle.api.internal.notations.api.NotationParser;
 import org.gradle.cache.CacheRepository;
+import org.gradle.initialization.ProjectAccessListener;
 import org.gradle.internal.SystemProperties;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.internal.service.DefaultServiceRegistry;
@@ -84,12 +86,12 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
     }
 
     protected ResolveModuleDescriptorConverter createResolveModuleDescriptorConverter() {
-        DependencyDescriptorFactory dependencyDescriptorFactoryDelegate = get(DependencyDescriptorFactoryDelegate.class);
         return new ResolveModuleDescriptorConverter(
                 get(ModuleDescriptorFactory.class),
+                get(DependencyDescriptorFactory.class),
                 get(ConfigurationsToModuleDescriptorConverter.class),
                 new DefaultDependenciesToModuleDescriptorConverter(
-                        dependencyDescriptorFactoryDelegate,
+                        get(DependencyDescriptorFactory.class),
                         get(ExcludeRuleConverter.class)));
 
     }
@@ -108,40 +110,36 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         return new DefaultExcludeRuleConverter();
     }
 
-    protected ExternalModuleDependencyDescriptorFactory createExternalModuleDependencyDescriptorFactory() {
-        return new ExternalModuleDependencyDescriptorFactory(get(ExcludeRuleConverter.class));
+    protected ExternalModuleIvyDependencyDescriptorFactory createExternalModuleDependencyDescriptorFactory() {
+        return new ExternalModuleIvyDependencyDescriptorFactory(get(ExcludeRuleConverter.class));
     }
 
     protected ConfigurationsToModuleDescriptorConverter createConfigurationsToModuleDescriptorConverter() {
         return new DefaultConfigurationsToModuleDescriptorConverter();
     }
 
-    protected DependencyDescriptorFactoryDelegate createDependencyDescriptorFactory() {
+    protected DependencyDescriptorFactory createDependencyDescriptorFactory() {
         DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory = new DefaultModuleDescriptorFactoryForClientModule();
-        DependencyDescriptorFactoryDelegate dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
-                new ClientModuleDependencyDescriptorFactory(
+        DependencyDescriptorFactory dependencyDescriptorFactory = new DefaultDependencyDescriptorFactory(
+                new ClientModuleIvyDependencyDescriptorFactory(
                         get(ExcludeRuleConverter.class),
                         clientModuleDescriptorFactory
                 ),
-                new ProjectDependencyDescriptorFactory(
+                new ProjectIvyDependencyDescriptorFactory(
                         get(ExcludeRuleConverter.class)),
-                get(ExternalModuleDependencyDescriptorFactory.class));
-        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactoryDelegate);
-        return dependencyDescriptorFactoryDelegate;
+                get(ExternalModuleIvyDependencyDescriptorFactory.class));
+        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactory);
+        return dependencyDescriptorFactory;
     }
 
     protected DependencyFactory createDependencyFactory() {
         Instantiator instantiator = get(Instantiator.class);
 
-        ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(get(StartParameter.class).isBuildProjectDependencies());
+        DefaultProjectDependencyFactory factory = new DefaultProjectDependencyFactory(
+                get(ProjectAccessListener.class), instantiator, get(StartParameter.class).isBuildProjectDependencies());
 
-        ProjectDependencyFactory projectDependencyFactory = new ProjectDependencyFactory(
-                projectDependenciesBuildInstruction,
-                instantiator);
-
-        DependencyProjectNotationParser projParser = new DependencyProjectNotationParser(
-                projectDependenciesBuildInstruction,
-                instantiator);
+        ProjectDependencyFactory projectDependencyFactory = new ProjectDependencyFactory(factory);
+        DependencyProjectNotationParser projParser = new DependencyProjectNotationParser(factory);
 
         NotationParser<? extends Dependency> moduleMapParser = new DependencyMapNotationParser<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class);
         NotationParser<? extends Dependency> moduleStringParser = new DependencyStringNotationParser<DefaultExternalModuleDependency>(instantiator, DefaultExternalModuleDependency.class);
@@ -154,11 +152,9 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
                 projParser,
                 new DependencyClassPathNotationParser(instantiator, get(ClassPathRegistry.class), new IdentityFileResolver()));
 
-        DependencyNotationParser dependencyNotationParser = new DependencyNotationParser(notationParsers);
-
         return new DefaultDependencyFactory(
-                dependencyNotationParser,
-                new ClientModuleNotationParser(instantiator),
+                new DependencyNotationParser(notationParsers),
+                new ClientModuleNotationParserFactory(instantiator).create(),
                 projectDependencyFactory);
     }
 
@@ -257,6 +253,40 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         );
     }
 
+    protected ResolveIvyFactory createResolveIvyFactory() {
+        StartParameter startParameter = get(StartParameter.class);
+        StartParameterResolutionOverride startParameterResolutionOverride = new StartParameterResolutionOverride(startParameter);
+        return new ResolveIvyFactory(
+                get(IvyFactory.class),
+                get(SettingsConverter.class),
+                get(ModuleResolutionCache.class),
+                get(ModuleDescriptorCache.class),
+                get(ArtifactAtRepositoryCachedArtifactIndex.class),
+                get(CacheLockingManager.class),
+                startParameterResolutionOverride,
+                get(BuildCommencedTimeProvider.class));
+    }
+
+    protected ArtifactDependencyResolver createArtifactDependencyResolver() {
+        ArtifactDependencyResolver resolver = new DefaultDependencyResolver(
+                get(ResolveIvyFactory.class),
+                get(PublishModuleDescriptorConverter.class),
+                new ResolvedArtifactFactory(
+                        get(CacheLockingManager.class)
+                ),
+                new DefaultProjectModuleRegistry(
+                        get(PublishModuleDescriptorConverter.class)),
+                get(ProjectAccessListener.class),
+                get(CacheLockingManager.class)
+        );
+        return new ErrorHandlingArtifactDependencyResolver(
+                new ShortcircuitEmptyConfigsArtifactDependencyResolver(
+                        new SelfResolvingDependencyResolver(
+                                new CacheLockingArtifactDependencyResolver(
+                                        get(CacheLockingManager.class),
+                                        resolver))));
+    }
+
     private class DefaultDependencyResolutionServices implements DependencyResolutionServices {
         private final ServiceRegistry parent;
         private final FileResolver fileResolver;
@@ -311,9 +341,9 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
         public ConfigurationContainerInternal getConfigurationContainer() {
             if (configurationContainer == null) {
                 final Instantiator instantiator = parent.get(Instantiator.class);
-                ArtifactDependencyResolver dependencyResolver = createDependencyResolver(getResolveRepositoryHandler());
+                ConfigurationResolver resolver = createDependencyResolver(getResolveRepositoryHandler());
                 configurationContainer = instantiator.newInstance(DefaultConfigurationContainer.class,
-                        dependencyResolver, instantiator, domainObjectContext, parent.get(ListenerManager.class),
+                        resolver, instantiator, domainObjectContext, parent.get(ListenerManager.class),
                         dependencyMetaDataProvider);
             }
             return configurationContainer;
@@ -328,11 +358,8 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
 
         public ArtifactHandler getArtifactHandler() {
             if (artifactHandler == null) {
-                artifactHandler = new DefaultArtifactHandler(
-                        getConfigurationContainer(),
-                        new DefaultPublishArtifactFactory(
-                                get(Instantiator.class),
-                                dependencyMetaDataProvider));
+                NotationParser<PublishArtifact> publishArtifactNotationParser = new PublishArtifactNotationParserFactory(get(Instantiator.class), dependencyMetaDataProvider).create();
+                artifactHandler = new DefaultArtifactHandler(getConfigurationContainer(), publishArtifactNotationParser);
             }
             return artifactHandler;
         }
@@ -341,37 +368,10 @@ public class DefaultDependencyManagementServices extends DefaultServiceRegistry
                 return new DefaultArtifactPublicationServices(DefaultDependencyResolutionServices.this);
         }
 
-        ArtifactDependencyResolver createDependencyResolver(DefaultRepositoryHandler resolverProvider) {
-            StartParameter startParameter = get(StartParameter.class);
-            StartParameterResolutionOverride startParameterResolutionOverride = new StartParameterResolutionOverride(startParameter);
-            ResolveIvyFactory ivyFactory = new ResolveIvyFactory(
-                    get(IvyFactory.class),
-                    resolverProvider,
-                    get(SettingsConverter.class),
-                    get(ModuleResolutionCache.class),
-                    get(ModuleDescriptorCache.class),
-                    get(ArtifactAtRepositoryCachedArtifactIndex.class),
-                    get(CacheLockingManager.class),
-                    startParameterResolutionOverride,
-                    get(BuildCommencedTimeProvider.class));
-
-            ResolvedArtifactFactory resolvedArtifactFactory = new ResolvedArtifactFactory(
-                    get(CacheLockingManager.class)
-            );
-
-            ArtifactDependencyResolver resolver = new DefaultDependencyResolver(
-                    ivyFactory,
-                    get(PublishModuleDescriptorConverter.class),
-                    resolvedArtifactFactory,
-                    new DefaultProjectModuleRegistry(
-                            get(PublishModuleDescriptorConverter.class))
-            );
-            return new ErrorHandlingArtifactDependencyResolver(
-                    new ShortcircuitEmptyConfigsArtifactDependencyResolver(
-                            new SelfResolvingDependencyResolver(
-                                    new CacheLockingArtifactDependencyResolver(
-                                            get(CacheLockingManager.class),
-                                            resolver))));
+        ConfigurationResolver createDependencyResolver(DefaultRepositoryHandler repositories) {
+            return new DefaultConfigurationResolver(
+                    get(ArtifactDependencyResolver.class),
+                    repositories);
         }
 
         ArtifactPublisher createArtifactPublisher() {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultProjectDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultProjectDependencyFactory.java
new file mode 100644
index 0000000..bf9301b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/DefaultProjectDependencyFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.initialization.ProjectAccessListener;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * by Szczepan Faber, created at: 2/5/13
+ */
+public class DefaultProjectDependencyFactory {
+    private final ProjectAccessListener projectAccessListener;
+    private final Instantiator instantiator;
+    private final boolean buildProjectDependencies;
+
+    public DefaultProjectDependencyFactory(ProjectAccessListener projectAccessListener, Instantiator instantiator, boolean buildProjectDependencies) {
+        this.projectAccessListener = projectAccessListener;
+        this.instantiator = instantiator;
+        this.buildProjectDependencies = buildProjectDependencies;
+    }
+
+    public ProjectDependency create(ProjectInternal project, String configuration) {
+        return instantiator.newInstance(DefaultProjectDependency.class, project, configuration, projectAccessListener, buildProjectDependencies);
+    }
+
+    public ProjectDependency create(Project project) {
+        return instantiator.newInstance(DefaultProjectDependency.class, project, projectAccessListener, buildProjectDependencies);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
index a75f151..3042e4e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
@@ -51,7 +51,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     private Set<Configuration> extendsFrom = new LinkedHashSet<Configuration>();
     private String description;
     private ConfigurationsProvider configurationsProvider;
-    private final ArtifactDependencyResolver dependencyResolver;
+    private final ConfigurationResolver resolver;
     private final ListenerManager listenerManager;
     private final DependencyMetaDataProvider metaDataProvider;
     private final DefaultDependencySet dependencies;
@@ -71,13 +71,13 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
     private final ResolutionStrategyInternal resolutionStrategy;
 
     public DefaultConfiguration(String path, String name, ConfigurationsProvider configurationsProvider,
-                                ArtifactDependencyResolver dependencyResolver, ListenerManager listenerManager,
+                                ConfigurationResolver resolver, ListenerManager listenerManager,
                                 DependencyMetaDataProvider metaDataProvider,
                                 ResolutionStrategyInternal resolutionStrategy) {
         this.path = path;
         this.name = name;
         this.configurationsProvider = configurationsProvider;
-        this.dependencyResolver = dependencyResolver;
+        this.resolver = resolver;
         this.listenerManager = listenerManager;
         this.metaDataProvider = metaDataProvider;
         this.resolutionStrategy = resolutionStrategy;
@@ -237,7 +237,7 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
                 DependencyResolutionListener broadcast = getDependencyResolutionBroadcast();
                 ResolvableDependencies incoming = getIncoming();
                 broadcast.beforeResolve(incoming);
-                cachedResolverResults = dependencyResolver.resolve(this);
+                cachedResolverResults = resolver.resolve(this);
                 if (cachedResolverResults.getResolvedConfiguration().hasError()) {
                     state = State.RESOLVED_WITH_FAILURES;
                 } else {
@@ -362,7 +362,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, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy.copy());
+                configurationsProvider, resolver, listenerManager, metaDataProvider, resolutionStrategy.copy());
         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
@@ -561,7 +561,6 @@ public class DefaultConfiguration extends AbstractFileCollection implements Conf
         }
 
         public ResolutionResult getResolutionResult() {
-            //TODO SF unit test
             DefaultConfiguration.this.resolveNow();
             return DefaultConfiguration.this.cachedResolverResults.getResolutionResult();
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
index 5e7747d..0a2eb49 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
@@ -23,7 +23,7 @@ import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.UnknownConfigurationException;
 import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
 import org.gradle.api.internal.DomainObjectContext;
-import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.ConfigurationResolver;
 import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.listener.ListenerManager;
@@ -38,7 +38,7 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
         implements ConfigurationContainerInternal, ConfigurationsProvider {
     public static final String DETACHED_CONFIGURATION_DEFAULT_NAME = "detachedConfiguration";
     
-    private final ArtifactDependencyResolver dependencyResolver;
+    private final ConfigurationResolver resolver;
     private final Instantiator instantiator;
     private final DomainObjectContext context;
     private final ListenerManager listenerManager;
@@ -46,11 +46,11 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
 
     private int detachedConfigurationDefaultNameCounter = 1;
 
-    public DefaultConfigurationContainer(ArtifactDependencyResolver dependencyResolver,
+    public DefaultConfigurationContainer(ConfigurationResolver resolver,
                                          Instantiator instantiator, DomainObjectContext context, ListenerManager listenerManager,
                                          DependencyMetaDataProvider dependencyMetaDataProvider) {
         super(Configuration.class, instantiator, new Configuration.Namer());
-        this.dependencyResolver = dependencyResolver;
+        this.resolver = resolver;
         this.instantiator = instantiator;
         this.context = context;
         this.listenerManager = listenerManager;
@@ -60,7 +60,7 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
     @Override
     protected Configuration doCreate(String name) {
         return instantiator.newInstance(DefaultConfiguration.class, context.absoluteProjectPath(name),
-                name, this, dependencyResolver, listenerManager,
+                name, this, resolver, listenerManager,
                 dependencyMetaDataProvider, instantiator.newInstance(DefaultResolutionStrategy.class));
     }
 
@@ -92,10 +92,10 @@ public class DefaultConfigurationContainer extends AbstractNamedDomainObjectCont
     }
 
     public Configuration detachedConfiguration(Dependency... dependencies) {
-        DetachedConfigurationsProvider detachedConfigurationsProvider = new DetachedConfigurationsProvider();
         String name = DETACHED_CONFIGURATION_DEFAULT_NAME + detachedConfigurationDefaultNameCounter++;
+        DetachedConfigurationsProvider detachedConfigurationsProvider = new DetachedConfigurationsProvider();
         DefaultConfiguration detachedConfiguration = new DefaultConfiguration(
-                name, name, detachedConfigurationsProvider, dependencyResolver,
+                name, name, detachedConfigurationsProvider, resolver,
                 listenerManager, dependencyMetaDataProvider, new DefaultResolutionStrategy());
         DomainObjectSet<Dependency> detachedDependencies = detachedConfiguration.getDependencies();
         for (Dependency dependency : dependencies) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFile.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFile.java
new file mode 100644
index 0000000..c30cf42
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFile.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dsl;
+
+import org.apache.commons.lang.StringUtils;
+
+import java.io.File;
+
+/**
+ * Given a Module and a File that is to be an artifact, attempts to determine the appropriate name+classifier+extension from the file name.
+ */
+public class ArtifactFile {
+    private String name;
+    private String classifier;
+    private String extension;
+
+    public ArtifactFile(File file, String version) {
+        name = file.getName();
+        extension = "";
+        classifier = "";
+        boolean done = false;
+
+        int startVersion = StringUtils.lastIndexOf(name, "-" + version);
+        if (startVersion >= 0) {
+            int endVersion = startVersion + version.length() + 1;
+            if (endVersion == name.length()) {
+                name = name.substring(0, startVersion);
+                done = true;
+            } else if (endVersion < name.length() && name.charAt(endVersion) == '-') {
+                String tail = name.substring(endVersion + 1);
+                name = name.substring(0, startVersion);
+                classifier = StringUtils.substringBeforeLast(tail, ".");
+                extension = StringUtils.substringAfterLast(tail, ".");
+                done = true;
+            } else if (endVersion < name.length() && StringUtils.lastIndexOf(name, ".") == endVersion) {
+                extension = name.substring(endVersion + 1);
+                name = name.substring(0, startVersion);
+                done = true;
+            }
+        }
+        if (!done) {
+            extension = StringUtils.substringAfterLast(name, ".");
+            name = StringUtils.substringBeforeLast(name, ".");
+        }
+        if (extension.length() == 0) {
+            extension = null;
+        }
+        if (classifier.length() == 0) {
+            classifier = null;
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
deleted file mode 100644
index ceef34d..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dsl;
-
-import org.apache.commons.lang.StringUtils;
-import org.apache.tools.ant.Task;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
-import org.gradle.api.internal.notations.NotationParserBuilder;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.TopLevelNotationParser;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
-import org.gradle.api.tasks.bundling.AbstractArchiveTask;
-
-import java.io.File;
-import java.util.Collection;
-
-/**
- * @author Hans Dockter
- */
-public class DefaultPublishArtifactFactory implements NotationParser<PublishArtifact>, TopLevelNotationParser {
-    private final Instantiator instantiator;
-    private final DependencyMetaDataProvider metaDataProvider;
-    private final NotationParser<PublishArtifact> delegate;
-
-    public DefaultPublishArtifactFactory(Instantiator instantiator, DependencyMetaDataProvider metaDataProvider) {
-        this.instantiator = instantiator;
-        this.metaDataProvider = metaDataProvider;
-        FileNotationParser fileParser = new FileNotationParser();
-        delegate = new NotationParserBuilder<PublishArtifact>()
-                .resultingType(PublishArtifact.class)
-                .parser(new ArchiveTaskNotationParser())
-                .parser(new FileMapNotationParser(fileParser))
-                .parser(fileParser)
-                .toComposite();
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        delegate.describe(candidateFormats);
-    }
-
-    public PublishArtifact parseNotation(Object notation) {
-        return delegate.parseNotation(notation);
-    }
-
-    private class ArchiveTaskNotationParser extends TypedNotationParser<AbstractArchiveTask, PublishArtifact> {
-        private ArchiveTaskNotationParser() {
-            super(AbstractArchiveTask.class);
-        }
-
-        @Override
-        public void describe(Collection<String> candidateFormats) {
-            candidateFormats.add("Instances of AbstractArchiveTask, e.g. jar.");
-        }
-
-        @Override
-        protected PublishArtifact parseType(AbstractArchiveTask notation) {
-            return instantiator.newInstance(ArchivePublishArtifact.class, notation);
-        }
-    }
-
-    private class FileMapNotationParser extends MapNotationParser<PublishArtifact> {
-        private final FileNotationParser fileParser;
-
-        private FileMapNotationParser(FileNotationParser fileParser) {
-            this.fileParser = fileParser;
-        }
-
-        protected PublishArtifact parseMap(@MapKey("file") File file) {
-            return fileParser.parseType(file);
-        }
-    }
-
-    private class FileNotationParser extends TypedNotationParser<File, PublishArtifact> {
-        private FileNotationParser() {
-            super(File.class);
-        }
-
-        @Override
-        protected PublishArtifact parseType(File file) {
-            Module module = metaDataProvider.getModule();
-
-            String name = file.getName();
-            String extension = "";
-            String classifier = "";
-            boolean done = false;
-
-            int startVersion = StringUtils.lastIndexOf(name, "-" + module.getVersion());
-            if (startVersion >= 0) {
-                int endVersion = startVersion + module.getVersion().length() + 1;
-                if (endVersion == name.length()) {
-                    name = name.substring(0, startVersion);
-                    done = true;
-                } else if (endVersion < name.length() && name.charAt(endVersion) == '-') {
-                    String tail = name.substring(endVersion + 1);
-                    name = name.substring(0, startVersion);
-                    classifier = StringUtils.substringBeforeLast(tail, ".");
-                    extension = StringUtils.substringAfterLast(tail, ".");
-                    done = true;
-                } else if (endVersion < name.length() && StringUtils.lastIndexOf(name, ".") == endVersion) {
-                    extension = name.substring(endVersion + 1);
-                    name = name.substring(0, startVersion);
-                    done = true;
-                }
-            }
-            if (!done) {
-                extension = StringUtils.substringAfterLast(name, ".");
-                name = StringUtils.substringBeforeLast(name, ".");
-            }
-            if (extension.length() == 0) {
-                extension = null;
-            }
-            if (classifier.length() == 0) {
-                classifier = null;
-            }
-
-            return instantiator.newInstance(DefaultPublishArtifact.class, name, extension, extension, classifier, null, file, new Task[0]);
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParser.java
deleted file mode 100644
index d9078f7..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParser.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dsl;
-
-import org.gradle.api.IllegalDependencyNotation;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ModuleVersionSelector;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
-import org.gradle.api.internal.notations.NotationParserBuilder;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.TopLevelNotationParser;
-import org.gradle.api.internal.notations.parsers.MapKey;
-import org.gradle.api.internal.notations.parsers.MapNotationParser;
-import org.gradle.api.internal.notations.parsers.TypedNotationParser;
-
-import java.util.Collection;
-import java.util.Set;
-
-/**
- * by Szczepan Faber, created at: 10/11/11
- */
-public class ForcedModuleNotationParser implements TopLevelNotationParser, NotationParser<Set<ModuleVersionSelector>> {
-
-    private NotationParser<Set<ModuleVersionSelector>> delegate = new NotationParserBuilder<ModuleVersionSelector>()
-            .resultingType(ModuleVersionSelector.class)
-            .parser(new ForcedModuleStringParser())
-            .parser(new ForcedModuleMapParser())
-            .toFlatteningComposite();
-
-    public Set<ModuleVersionSelector> parseNotation(Object notation) {
-        assert notation != null : "notation cannot be null";
-        return delegate.parseNotation(notation);
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        delegate.describe(candidateFormats);
-    }
-
-    static class ForcedModuleMapParser extends MapNotationParser<ModuleVersionSelector> {
-        @Override
-        public void describe(Collection<String> candidateFormats) {
-            candidateFormats.add("Maps, e.g. [group: 'org.gradle', name:'gradle-core', version: '1.0'].");
-        }
-
-        protected ModuleVersionSelector parseMap(@MapKey("group") String group, @MapKey("name") String name, @MapKey("version") String version) {
-            return selector(group, name, version);
-        }
-    }
-
-    static class ForcedModuleStringParser extends TypedNotationParser<CharSequence, ModuleVersionSelector> {
-
-        public ForcedModuleStringParser() {
-            super(CharSequence.class);
-        }
-
-        @Override
-        public void describe(Collection<String> candidateFormats) {
-            candidateFormats.add("Strings/CharSequences, e.g. 'org.gradle:gradle-core:1.0'.");
-        }
-
-        public ModuleVersionSelector parseType(CharSequence notation) {
-            ParsedModuleStringNotation parsed;
-            try {
-                parsed = new ParsedModuleStringNotation(notation.toString(), null);
-            } catch (IllegalDependencyNotation e) {
-                throw new InvalidUserDataException(
-                    "Invalid format: '" + notation + "'. The Correct notation is a 3-part group:name:version notation,"
-                    + "e.g: 'org.gradle:gradle-core:1.0'");
-            }
-
-            if (parsed.getGroup() == null || parsed.getName() == null || parsed.getVersion() == null) {
-                throw new InvalidUserDataException(
-                    "Invalid format: '" + notation + "'. Group, name and version cannot be empty. Correct example: "
-                    + "'org.gradle:gradle-core:1.0'");
-            }
-            return selector(parsed.getGroup(), parsed.getName(), parsed.getVersion());
-        }
-    }
-
-    static ModuleVersionSelector selector(final String group, final String name, final String version) {
-        return new DefaultModuleVersionSelector(group, name, version);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsers.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsers.java
new file mode 100644
index 0000000..d852d60
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsers.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dsl;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.notations.NotationParserBuilder;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+
+import java.util.Collection;
+import java.util.Set;
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector;
+
+/**
+ * by Szczepan Faber, created at: 10/11/11
+ */
+public class ModuleVersionSelectorParsers {
+
+    public static NotationParser<Set<ModuleVersionSelector>> multiParser() {
+        return builder().toFlatteningComposite();
+    }
+
+    public static NotationParser<ModuleVersionSelector> parser() {
+        return builder().toComposite();
+    }
+
+    private static NotationParserBuilder<ModuleVersionSelector> builder() {
+        return new NotationParserBuilder<ModuleVersionSelector>()
+                .resultingType(ModuleVersionSelector.class)
+                .parser(new StringParser())
+                .parser(new MapParser());
+    }
+
+    static class MapParser extends MapNotationParser<ModuleVersionSelector> {
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Maps, e.g. [group: 'org.gradle', name:'gradle-core', version: '1.0'].");
+        }
+
+        protected ModuleVersionSelector parseMap(@MapKey("group") String group, @MapKey("name") String name, @MapKey("version") String version) {
+            return newSelector(group, name, version);
+        }
+    }
+
+    static class StringParser extends TypedNotationParser<CharSequence, ModuleVersionSelector> {
+
+        public StringParser() {
+            super(CharSequence.class);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Strings/CharSequences, e.g. 'org.gradle:gradle-core:1.0'.");
+        }
+
+        public ModuleVersionSelector parseType(CharSequence notation) {
+            ParsedModuleStringNotation parsed;
+            try {
+                parsed = new ParsedModuleStringNotation(notation.toString(), null);
+            } catch (IllegalDependencyNotation e) {
+                throw new InvalidUserDataException(
+                        "Invalid format: '" + notation + "'. The Correct notation is a 3-part group:name:version notation, "
+                                + "e.g: 'org.gradle:gradle-core:1.0'");
+            }
+
+            if (parsed.getGroup() == null || parsed.getName() == null || parsed.getVersion() == null) {
+                throw new InvalidUserDataException(
+                        "Invalid format: '" + notation + "'. Group, name and version cannot be empty. Correct example: "
+                                + "'org.gradle:gradle-core:1.0'");
+            }
+            return newSelector(parsed.getGroup(), parsed.getName(), parsed.getVersion());
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactory.java
new file mode 100644
index 0000000..0064e2d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactory.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dsl;
+
+import org.apache.tools.ant.Task;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
+import org.gradle.api.internal.notations.NotationParserBuilder;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * @author Hans Dockter
+ */
+public class PublishArtifactNotationParserFactory implements Factory<NotationParser<PublishArtifact>> {
+    private final Instantiator instantiator;
+    private final DependencyMetaDataProvider metaDataProvider;
+
+    public PublishArtifactNotationParserFactory(Instantiator instantiator, DependencyMetaDataProvider metaDataProvider) {
+        this.instantiator = instantiator;
+        this.metaDataProvider = metaDataProvider;
+    }
+
+    public NotationParser<PublishArtifact> create() {
+        FileNotationParser fileParser = new FileNotationParser();
+        return new NotationParserBuilder<PublishArtifact>()
+                .resultingType(PublishArtifact.class)
+                .parser(new ArchiveTaskNotationParser())
+                .parser(new FileMapNotationParser(fileParser))
+                .parser(fileParser)
+                .toComposite();
+    }
+
+    private class ArchiveTaskNotationParser extends TypedNotationParser<AbstractArchiveTask, PublishArtifact> {
+        private ArchiveTaskNotationParser() {
+            super(AbstractArchiveTask.class);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Instances of AbstractArchiveTask, e.g. jar.");
+        }
+
+        @Override
+        protected PublishArtifact parseType(AbstractArchiveTask notation) {
+            return instantiator.newInstance(ArchivePublishArtifact.class, notation);
+        }
+    }
+
+    private class FileMapNotationParser extends MapNotationParser<PublishArtifact> {
+        private final FileNotationParser fileParser;
+
+        private FileMapNotationParser(FileNotationParser fileParser) {
+            this.fileParser = fileParser;
+        }
+
+        protected PublishArtifact parseMap(@MapKey("file") File file) {
+            return fileParser.parseType(file);
+        }
+    }
+
+    private class FileNotationParser extends TypedNotationParser<File, PublishArtifact> {
+        private FileNotationParser() {
+            super(File.class);
+        }
+
+        @Override
+        protected PublishArtifact parseType(File file) {
+            Module module = metaDataProvider.getModule();
+            ArtifactFile artifactFile = new ArtifactFile(file, module.getVersion());
+            return instantiator.newInstance(DefaultPublishArtifact.class, artifactFile.getName(), artifactFile.getExtension(), artifactFile.getExtension(),
+                                            artifactFile.getClassifier(), null, file, new Task[0]);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java
index 3ffb3ae..cf52b33 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/BuildableModuleVersionResolveResult.java
@@ -17,14 +17,20 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData;
 
 public interface BuildableModuleVersionResolveResult extends ModuleVersionResolveResult {
     /**
      * Marks the module version as resolved, with the given meta-data and artifact resolver.
      */
-    void resolved(ModuleVersionIdentifier moduleRevisionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver);
+    void resolved(ModuleVersionIdentifier moduleVersionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver);
+
+    /**
+     * Marks the module version as resolved, with the given meta-data and artifact resolver.
+     */
+    void resolved(ModuleVersionMetaData metaData, ArtifactResolver artifactResolver);
 
     /**
      * Marks the resolve as failed with the given exception.
@@ -34,12 +40,12 @@ public interface BuildableModuleVersionResolveResult extends ModuleVersionResolv
     /**
      * Marks the module version as not found.
      */
-    void notFound(ModuleVersionIdentifier moduleVersionIdentifier);
+    void notFound(ModuleVersionSelector versionSelector);
 
     /**
      * Replaces the meta-data in the result. Result must already be resolved.
      */
-    void setMetaData(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor);
+    void setMetaData(ModuleDescriptor descriptor);
 
     /**
      * Replaces the artifact resolver in the result. Result must already be resolved.
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java
index ddcc74b..f34a8e4 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolver.java
@@ -19,8 +19,11 @@ import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 import org.gradle.internal.Factory;
 
+import java.util.List;
+
 public class CacheLockingArtifactDependencyResolver implements ArtifactDependencyResolver {
     private final CacheLockingManager lockingManager;
     private final ArtifactDependencyResolver resolver;
@@ -30,10 +33,10 @@ public class CacheLockingArtifactDependencyResolver implements ArtifactDependenc
         this.resolver = resolver;
     }
 
-    public ResolverResults resolve(final ConfigurationInternal configuration) throws ResolveException {
+    public ResolverResults resolve(final ConfigurationInternal configuration, final List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
         return lockingManager.useCache(String.format("resolve %s", configuration), new Factory<ResolverResults>() {
             public ResolverResults create() {
-                return resolver.resolve(configuration);
+                return resolver.resolve(configuration, repositories);
             }
         });
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
index a7cd877..4410d69 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResult.java
@@ -17,37 +17,42 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DefaultBuildableModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData;
 
 public class DefaultBuildableModuleVersionResolveResult implements BuildableModuleVersionResolveResult {
-    private ModuleVersionIdentifier moduleVersionIdentifier;
-    private ModuleDescriptor moduleDescriptor;
+    private ModuleVersionMetaData metaData;
     private ModuleVersionResolveException failure;
     private ArtifactResolver artifactResolver;
 
     public DefaultBuildableModuleVersionResolveResult failed(ModuleVersionResolveException failure) {
-        moduleDescriptor = null;
+        metaData = null;
         this.failure = failure;
         return this;
     }
 
+    public void notFound(ModuleVersionSelector versionSelector) {
+        failed(new ModuleVersionNotFoundException(versionSelector));
+    }
 
-    public void notFound(ModuleVersionIdentifier moduleVersionIdentifier) {
-        failed(new ModuleVersionNotFoundException(moduleVersionIdentifier));
+    public void resolved(ModuleVersionIdentifier moduleVersionId, ModuleDescriptor descriptor, ArtifactResolver artifactResolver) {
+        DefaultBuildableModuleVersionMetaData metaData = new DefaultBuildableModuleVersionMetaData();
+        metaData.resolved(moduleVersionId, descriptor, false, null);
+        resolved(metaData, artifactResolver);
     }
 
-    public void resolved(ModuleVersionIdentifier moduleVersionIdentifier, ModuleDescriptor descriptor, ArtifactResolver artifactResolver) {
-        this.moduleVersionIdentifier = moduleVersionIdentifier;
-        this.moduleDescriptor = descriptor;
+    public void resolved(ModuleVersionMetaData metaData, ArtifactResolver artifactResolver) {
+        this.metaData = metaData;
         this.artifactResolver = artifactResolver;
     }
 
-    public void setMetaData(ModuleRevisionId moduleRevisionId, ModuleDescriptor descriptor) {
+    public void setMetaData(ModuleDescriptor descriptor) {
         assertResolved();
-        this.moduleVersionIdentifier = toModuleVersionIdentifier(moduleRevisionId);
-        this.moduleDescriptor = descriptor;
+        DefaultBuildableModuleVersionMetaData newMetaData = new DefaultBuildableModuleVersionMetaData();
+        newMetaData.resolved(metaData.getId(), descriptor, metaData.isChanging(), null);
+        this.metaData = newMetaData;
     }
 
     public void setArtifactResolver(ArtifactResolver artifactResolver) {
@@ -57,12 +62,12 @@ public class DefaultBuildableModuleVersionResolveResult implements BuildableModu
 
     public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
         assertResolved();
-        return moduleVersionIdentifier;
+        return metaData.getId();
     }
 
-    public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
+    public ModuleVersionMetaData getMetaData() throws ModuleVersionResolveException {
         assertResolved();
-        return moduleDescriptor;
+        return metaData;
     }
 
     public ArtifactResolver getArtifactResolver() throws ModuleVersionResolveException {
@@ -75,10 +80,6 @@ public class DefaultBuildableModuleVersionResolveResult implements BuildableModu
         return failure;
     }
 
-    private ModuleVersionIdentifier toModuleVersionIdentifier(ModuleRevisionId moduleRevisionId) {
-        return new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
-    }
-
     private void assertResolved() {
         assertHasResult();
         if (failure != null) {
@@ -87,7 +88,7 @@ public class DefaultBuildableModuleVersionResolveResult implements BuildableModu
     }
 
     private void assertHasResult() {
-        if (failure == null && moduleDescriptor == null) {
+        if (failure == null && metaData == null) {
             throw new IllegalStateException("No result has been specified.");
         }
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java
new file mode 100644
index 0000000..54e8fdc
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultConfigurationResolver.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice;
+
+import org.gradle.api.artifacts.ResolveException;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.internal.Transformers;
+import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
+import org.gradle.api.internal.artifacts.ConfigurationResolver;
+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.util.CollectionUtils;
+
+import java.util.List;
+
+public class DefaultConfigurationResolver implements ConfigurationResolver {
+    private final ArtifactDependencyResolver resolver;
+    private final RepositoryHandler repositories;
+
+    public DefaultConfigurationResolver(ArtifactDependencyResolver resolver, RepositoryHandler repositories) {
+        this.resolver = resolver;
+        this.repositories = repositories;
+    }
+
+    public ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException {
+        List<ResolutionAwareRepository> resolutionAwareRepositories = CollectionUtils.collect(repositories, Transformers.cast(ResolutionAwareRepository.class));
+        return resolver.resolve(configuration, resolutionAwareRepositories);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
index 0c3333f..b8fa9f1 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetails.java
@@ -19,6 +19,7 @@ package org.gradle.api.internal.artifacts.ivyservice;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
 import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
+import org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector;
@@ -55,6 +56,11 @@ public class DefaultDependencyResolveDetails implements DependencyResolveDetails
         this.selectionReason = selectionReason;
     }
 
+    public void useTarget(Object notation) {
+        this.target = ModuleVersionSelectorParsers.parser().parseNotation(notation);
+        this.selectionReason = VersionSelectionReasons.SELECTED_BY_RULE;
+    }
+
     public ModuleVersionSelectionReason getSelectionReason() {
         return selectionReason;
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
index 2e5b7ca..1cf335d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultLenientConfiguration.java
@@ -150,6 +150,10 @@ public class DefaultLenientConfiguration implements ResolvedConfigurationBuilder
         return artifacts;
     }
 
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
     private static class ResolvedDependencyArtifactsGraph implements DirectedGraphWithEdgeValues<ResolvedDependency, ResolvedArtifact> {
         public void getNodeValues(ResolvedDependency node, Collection<ResolvedArtifact> values,
                                   Collection<ResolvedDependency> connectedNodes) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java
index 5ee98a5..a0869af 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolvedConfiguration.java
@@ -17,15 +17,20 @@ package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.gradle.api.artifacts.*;
 import org.gradle.api.specs.Spec;
+import org.gradle.internal.Factory;
 
 import java.io.File;
 import java.util.Set;
 
+import static java.lang.String.format;
+
 public class DefaultResolvedConfiguration implements ResolvedConfiguration {
     private final DefaultLenientConfiguration configuration;
+    private final CacheLockingManager cacheLockingManager;
 
-    public DefaultResolvedConfiguration(DefaultLenientConfiguration configuration) {
+    public DefaultResolvedConfiguration(DefaultLenientConfiguration configuration, CacheLockingManager cacheLockingManager) {
         this.configuration = configuration;
+        this.cacheLockingManager = cacheLockingManager;
     }
 
     public boolean hasError() {
@@ -40,9 +45,13 @@ public class DefaultResolvedConfiguration implements ResolvedConfiguration {
         return configuration;
     }
 
-    public Set<File> getFiles(Spec<? super Dependency> dependencySpec) throws ResolveException {
+    public Set<File> getFiles(final Spec<? super Dependency> dependencySpec) throws ResolveException {
         rethrowFailure();
-        return configuration.getFilesStrict(dependencySpec);
+        return cacheLockingManager.useCache(format("resolving files from %s", configuration.getConfiguration()), new Factory<Set<File>>() {
+            public Set<File> create() {
+                return configuration.getFilesStrict(dependencySpec);
+            }
+        });
     }
 
     public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
index c4ea717..4c37d0b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
@@ -48,7 +48,7 @@ public class DefaultSettingsConverter implements SettingsConverter {
         return publishSettings;
     }
 
-    public IvySettings convertForResolve(DependencyResolver defaultResolver, List<DependencyResolver> resolvers) {
+    public IvySettings convertForResolve(DependencyResolver defaultResolver) {
         if (resolveSettings == null) {
             resolveSettings = settingsFactory.create();
         } else {
@@ -58,9 +58,6 @@ public class DefaultSettingsConverter implements SettingsConverter {
         resolveSettings.addResolver(defaultResolver);
         resolveSettings.setDefaultResolver(defaultResolver.getName());
         
-        for (DependencyResolver resolver : resolvers) {
-            resolveSettings.addResolver(resolver);
-        }
         return resolveSettings;
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
index 48d2233..a9c444c 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependency.java
@@ -18,23 +18,20 @@ package org.gradle.api.internal.artifacts.ivyservice;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.UnresolvedDependency;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 import org.gradle.util.DeprecationLogger;
 
 public class DefaultUnresolvedDependency implements UnresolvedDependency {
     private final Throwable problem;
     private final ModuleVersionSelector selector;
-    private final ModuleRevisionId revisionId;
 
-    public DefaultUnresolvedDependency(ModuleRevisionId id, Throwable problem) {
-        revisionId = id;
-        this.selector = new DefaultModuleVersionSelector(id.getOrganisation(), id.getName(), id.getRevision());
+    public DefaultUnresolvedDependency(ModuleVersionSelector selector, Throwable problem) {
+        this.selector = selector;
         this.problem = problem;
     }
 
     public String getId() {
         DeprecationLogger.nagUserOfReplacedMethod("UnresolvedDependency.getId()", "UnresolvedDependency.getSelector()");
-        return revisionId.toString();
+        return ModuleRevisionId.newInstance(selector.getGroup(), selector.getName(), selector.getVersion()).toString();
     }
 
     public ModuleVersionSelector getSelector() {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleResolver.java
index fd13ebd..73b9c3d 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleResolver.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
 
 /**
  * Resolves a dependency to the meta-data for a module.
@@ -24,5 +24,5 @@ public interface DependencyToModuleResolver {
     /**
      * Resolves the given dependency to a module version id. Failures are packaged up in the returned result.
      */
-    void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result);
+    void resolve(DependencyMetaData dependency, BuildableModuleVersionResolveResult result);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java
index da67224..91aa32d 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DependencyToModuleVersionIdResolver.java
@@ -15,7 +15,7 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
 
 /**
  * Resolves a dependency to the id for a module.
@@ -24,5 +24,5 @@ public interface DependencyToModuleVersionIdResolver {
     /**
      * Resolves the given dependency to a module version id. Note that failures are packaged up into the result.
      */
-    ModuleVersionIdResolveResult resolve(DependencyDescriptor dependencyDescriptor);
+    ModuleVersionIdResolveResult resolve(DependencyMetaData dependencyDescriptor);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java
index 90cd90b..444809a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolver.java
@@ -19,9 +19,11 @@ import org.gradle.api.artifacts.*;
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 import org.gradle.api.specs.Spec;
 
 import java.io.File;
+import java.util.List;
 import java.util.Set;
 
 public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependencyResolver {
@@ -31,10 +33,10 @@ public class ErrorHandlingArtifactDependencyResolver implements ArtifactDependen
         this.dependencyResolver = dependencyResolver;
     }
 
-    public ResolverResults resolve(final ConfigurationInternal configuration) {
+    public ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
         final ResolverResults results;
         try {
-            results = dependencyResolver.resolve(configuration);
+            results = dependencyResolver.resolve(configuration, repositories);
         } catch (final Throwable e) {
             return new ResolverResults(new BrokenResolvedConfiguration(e, configuration), wrapException(e, configuration));
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
index 402893c..f34fe63 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisher.java
@@ -22,9 +22,10 @@ import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishException;
 import org.gradle.api.internal.artifacts.ArtifactPublisher;
 import org.gradle.api.internal.artifacts.configurations.Configurations;
-import org.gradle.util.CollectionUtils;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -51,8 +52,11 @@ public class IvyBackedArtifactPublisher implements ArtifactPublisher {
         return ivyFactory.createIvy(settingsConverter.convertForPublish(publishResolvers));
     }
 
-    public void publish(Iterable<DependencyResolver> dependencyResolvers, Module module, Set<? extends Configuration> configurations, File descriptor) throws PublishException {
-        List<DependencyResolver> publishResolvers = CollectionUtils.toList(dependencyResolvers);
+    public void publish(Iterable<? extends PublicationAwareRepository> repositories, Module module, Set<? extends Configuration> configurations, File descriptor) throws PublishException {
+        List<DependencyResolver> publishResolvers = new ArrayList<DependencyResolver>();
+        for (PublicationAwareRepository repository : repositories) {
+            publishResolvers.add(repository.createPublisher());
+        }
         Ivy ivy = ivyForPublish(publishResolvers);
         Set<String> confs = Configurations.getNames(configurations, false);
         dependencyPublisher.publish(
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
index 8c65a4b..50bcbf7 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriter.java
@@ -26,7 +26,7 @@ import org.apache.ivy.util.XMLHelper;
 import org.apache.ivy.util.extendable.ExtendableItem;
 import org.gradle.api.Action;
 import org.gradle.api.internal.ErroringAction;
-import org.gradle.api.internal.xml.XmlTransformer;
+import org.gradle.api.internal.IoActions;
 import org.gradle.util.TextUtil;
 
 import java.io.File;
@@ -72,15 +72,7 @@ public class IvyXmlModuleDescriptorWriter implements IvyModuleDescriptorWriter {
     }
 
     public void write(ModuleDescriptor md, File output) {
-        write(md, output, null);
-    }
-
-    public void write(ModuleDescriptor md, File output, XmlTransformer descriptorTransformer) {
-        if (descriptorTransformer == null) {
-            descriptorTransformer = new XmlTransformer();
-        }
-
-        descriptorTransformer.transform(output, "UTF-8", createWriterAction(md));
+        IoActions.writeTextFile(output, "UTF-8", createWriterAction(md));
     }
 
     private static void printDependencies(ModuleDescriptor md, Writer writer) throws IOException {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
index 5d9712c..81b4c48 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionNotFoundException.java
@@ -32,6 +32,10 @@ public class ModuleVersionNotFoundException extends ModuleVersionResolveExceptio
         super(id, "Could not find %s.");
     }
 
+    public ModuleVersionNotFoundException(ModuleVersionSelector selector) {
+        super(selector, "Could not find any version that matches %s.");
+    }
+
     public ModuleVersionNotFoundException(ModuleVersionIdentifier id) {
         super(id, "Could not find %s.");
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
index b241209..a6a78c9 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveException.java
@@ -58,8 +58,8 @@ public class ModuleVersionResolveException extends AbstractMultiCauseException {
         initCause(cause);
     }
 
-    public ModuleVersionResolveException(ModuleRevisionId id, Iterable<? extends Throwable> causes) {
-        this(id, "Could not resolve %s.");
+    public ModuleVersionResolveException(ModuleVersionSelector selector, Iterable<? extends Throwable> causes) {
+        this(selector, "Could not resolve %s.");
         initCauses(causes);
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
index b99ef91..e30a25a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleVersionResolveResult.java
@@ -15,9 +15,9 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.Nullable;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData;
 
 public interface ModuleVersionResolveResult {
     /**
@@ -28,11 +28,11 @@ public interface ModuleVersionResolveResult {
     ModuleVersionIdentifier getId() throws ModuleVersionResolveException;
 
     /**
-     * Returns the descriptor for this module version.
+     * Returns the meta-data for this module version.
      *
      * @throws ModuleVersionResolveException If resolution was unsuccessful and the descriptor is not available.
      */
-    ModuleDescriptor getDescriptor() throws ModuleVersionResolveException;
+    ModuleVersionMetaData getMetaData() throws ModuleVersionResolveException;
 
     /**
      * Returns the resolve failure, if any.
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
index 408b5d4..3e14ed4 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
@@ -21,11 +21,13 @@ import org.gradle.api.internal.artifacts.ArtifactDependencyResolver;
 import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 import org.gradle.api.specs.Spec;
 import org.gradle.util.CollectionUtils;
 
 import java.io.File;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Set;
 
 public class SelfResolvingDependencyResolver implements ArtifactDependencyResolver {
@@ -35,8 +37,8 @@ public class SelfResolvingDependencyResolver implements ArtifactDependencyResolv
         this.resolver = resolver;
     }
 
-    public ResolverResults resolve(final ConfigurationInternal configuration) {
-        ResolverResults results = resolver.resolve(configuration);
+    public ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
+        ResolverResults results = resolver.resolve(configuration, repositories);
         ResolvedConfiguration resolvedConfiguration = results.getResolvedConfiguration();
         Set<Dependency> dependencies = configuration.getAllDependencies();
         CachingDependencyResolveContext resolveContext = new CachingDependencyResolveContext(configuration.isTransitive());
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
index d532b73..877ffde 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
@@ -28,7 +28,7 @@ public interface SettingsConverter {
 
     IvySettings convertForPublish(List<DependencyResolver> publishResolvers);
 
-    IvySettings convertForResolve(DependencyResolver defaultResolver, List<DependencyResolver> resolvers);
+    IvySettings convertForResolve(DependencyResolver defaultResolver);
 
     IvySettings getForResolve();
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
index 97fbc1d..f7820c0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolver.java
@@ -21,11 +21,13 @@ import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ResolverResults;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 import org.gradle.api.internal.artifacts.result.DefaultResolutionResult;
 import org.gradle.api.specs.Spec;
 
 import java.io.File;
 import java.util.Collections;
+import java.util.List;
 import java.util.Set;
 
 public class ShortcircuitEmptyConfigsArtifactDependencyResolver implements ArtifactDependencyResolver {
@@ -35,13 +37,13 @@ public class ShortcircuitEmptyConfigsArtifactDependencyResolver implements Artif
         this.dependencyResolver = dependencyResolver;
     }
 
-    public ResolverResults resolve(ConfigurationInternal configuration) {
+    public ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
         if (configuration.getAllDependencies().isEmpty()) {
             ModuleVersionIdentifier id = DefaultModuleVersionIdentifier.newId(configuration.getModule());
             DefaultResolutionResult emptyResult = new ResolutionResultBuilder().start(id).getResult();
             return new ResolverResults(new EmptyResolvedConfiguration(), emptyResult);
         }
-        return dependencyResolver.resolve(configuration);
+        return dependencyResolver.resolve(configuration, repositories);
     }
 
     private static class EmptyResolvedConfiguration implements ResolvedConfiguration {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java
index 730abe7..ade158c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolver.java
@@ -15,15 +15,12 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
 import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
 
 public class VersionForcingDependencyToModuleResolver implements DependencyToModuleVersionIdResolver {
@@ -35,8 +32,8 @@ public class VersionForcingDependencyToModuleResolver implements DependencyToMod
         this.rule = rule;
     }
 
-    public ModuleVersionIdResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
-        ModuleVersionSelector module = new DefaultModuleVersionSelector(dependencyDescriptor.getDependencyRevisionId().getOrganisation(), dependencyDescriptor.getDependencyRevisionId().getName(), dependencyDescriptor.getDependencyRevisionId().getRevision());
+    public ModuleVersionIdResolveResult resolve(DependencyMetaData dependency) {
+        ModuleVersionSelector module = dependency.getRequested();
         DefaultDependencyResolveDetails details = new DefaultDependencyResolveDetails(module);
         try {
             rule.execute(details);
@@ -44,13 +41,11 @@ public class VersionForcingDependencyToModuleResolver implements DependencyToMod
             return new FailedDependencyResolveRuleResult(module, e);
         }
         if (details.isUpdated()) {
-            ModuleId moduleId = new ModuleId(details.getTarget().getGroup(), details.getTarget().getName());
-            ModuleRevisionId revisionId = new ModuleRevisionId(moduleId, details.getTarget().getVersion());
-            DependencyDescriptor descriptor = dependencyDescriptor.clone(revisionId);
-            ModuleVersionIdResolveResult result = resolver.resolve(descriptor);
+            DependencyMetaData substitutedDependency = dependency.withRequestedVersion(details.getTarget());
+            ModuleVersionIdResolveResult result = resolver.resolve(substitutedDependency);
             return new SubstitutedModuleVersionIdResolveResult(result, details.getSelectionReason());
         }
-        return resolver.resolve(dependencyDescriptor);
+        return resolver.resolve(dependency);
     }
 
     private class FailedDependencyResolveRuleResult implements ModuleVersionIdResolveResult {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java
index cd7c769..cbf202d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolver.java
@@ -20,6 +20,7 @@ import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult;
 import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ClientModuleDependencyDescriptor;
 
 /**
@@ -32,16 +33,17 @@ public class ClientModuleResolver implements DependencyToModuleResolver {
         this.resolver = resolver;
     }
 
-    public void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result) {
-        resolver.resolve(dependencyDescriptor, result);
+    public void resolve(DependencyMetaData dependency, BuildableModuleVersionResolveResult result) {
+        resolver.resolve(dependency, result);
 
-        if (result.getFailure() != null || !(dependencyDescriptor instanceof ClientModuleDependencyDescriptor)) {
+        DependencyDescriptor descriptor = dependency.getDescriptor();
+        if (result.getFailure() != null || !(descriptor instanceof ClientModuleDependencyDescriptor)) {
             return;
         }
 
-        ClientModuleDependencyDescriptor clientModuleDependencyDescriptor = (ClientModuleDependencyDescriptor) dependencyDescriptor;
+        ClientModuleDependencyDescriptor clientModuleDependencyDescriptor = (ClientModuleDependencyDescriptor) descriptor;
         ModuleDescriptor moduleDescriptor = clientModuleDependencyDescriptor.getTargetModule();
 
-        result.setMetaData(moduleDescriptor.getModuleRevisionId(), moduleDescriptor);
+        result.setMetaData(moduleDescriptor);
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
index 74d26a0..2a3f3da 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/DefaultCachedModuleResolution.java
@@ -17,15 +17,16 @@ package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
 
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.internal.TimeProvider;
 
 class DefaultCachedModuleResolution implements ModuleResolutionCache.CachedModuleResolution {
-    private final ModuleRevisionId requestedVersion;
+    private final ModuleVersionIdentifier requestedVersion;
     private final ModuleVersionIdentifier resolvedVersion;
     private final long ageMillis;
 
     public DefaultCachedModuleResolution(ModuleRevisionId requestedVersion, ModuleResolutionCacheEntry entry, TimeProvider timeProvider) {
-        this.requestedVersion = requestedVersion;
+        this.requestedVersion = new DefaultModuleVersionIdentifier(requestedVersion.getOrganisation(), requestedVersion.getName(), requestedVersion.getRevision());
         this.resolvedVersion = entry.moduleVersionIdentifier;
         ageMillis = timeProvider.getCurrentTime() - entry.createTimestamp;
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ForceChangeDependencyDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ForceChangeDependencyDescriptor.java
deleted file mode 100644
index 24ab2b1..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/dynamicversions/ForceChangeDependencyDescriptor.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.dynamicversions;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.gradle.api.GradleException;
-
-import java.lang.reflect.Field;
-
-public class ForceChangeDependencyDescriptor {
-    public static DependencyDescriptor forceChangingFlag(DependencyDescriptor original, boolean isChanging) {
-        if (original.isChanging() == isChanging) {
-            return original;
-        }
-
-        DependencyDescriptor forcedChanging = original.clone(original.getDependencyRevisionId());
-        try {
-            Field field = DefaultDependencyDescriptor.class.getDeclaredField("isChanging");
-            field.setAccessible(true);
-            field.set(forcedChanging, isChanging);
-            return forcedChanging;
-        } catch (Exception e) {
-            throw new GradleException("Could not get cache options from AbstractResolver", e);
-        }
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
index 412db74..9280823 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/AbstractDependencyResolverAdapter.java
@@ -15,10 +15,11 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
+import org.apache.ivy.core.settings.IvySettings;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.internal.artifacts.repositories.cachemanager.LocalFileRepositoryCacheManager;
 
-public abstract class AbstractDependencyResolverAdapter implements ModuleVersionRepository {
+public abstract class AbstractDependencyResolverAdapter implements IvyAwareModuleVersionRepository {
     private final DependencyResolverIdentifier identifier;
     protected final DependencyResolver resolver;
 
@@ -40,6 +41,14 @@ public abstract class AbstractDependencyResolverAdapter implements ModuleVersion
         return String.format("Repository '%s'", resolver.getName());
     }
 
+    public void setSettings(IvySettings settings) {
+        settings.addResolver(resolver);
+    }
+
+    public boolean isDynamicResolveMode() {
+        return false;
+    }
+
     public boolean isLocal() {
         return resolver.getRepositoryCacheManager() instanceof LocalFileRepositoryCacheManager;
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.java
deleted file mode 100644
index d6b7908..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionDescriptor.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.Nullable;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
-
-/**
- * The result of attempting to resolve a dependency descriptor to the meta-data for a module version.
- */
-public interface BuildableModuleVersionDescriptor extends ModuleVersionDescriptor {
-    enum State {
-        Resolved, Missing, Failed, ProbablyMissing, Unknown
-    }
-
-    /**
-     * Returns the current state of this descriptor.
-     */
-    State getState();
-
-    @Nullable
-    ModuleVersionResolveException getFailure();
-
-    /**
-     * Marks the module version as resolved, with the given meta-data and provides the related CachedModuleDescriptor.
-     */
-    void resolved(ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource);
-
-    /**
-     * Marks the resolve as failed with the given exception.
-     */
-    void failed(ModuleVersionResolveException failure);
-
-    /**
-     * Marks the module version as definitely missing.
-     */
-    void missing();
-
-    /**
-     * Marks the module version as probably missing.
-     */
-    void probablyMissing();
-
-    /**
-     * The ModuleSource of the buildable result
-     */
-    public ModuleSource getModuleSource();
-
-
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaData.java
new file mode 100644
index 0000000..f731d7d
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/BuildableModuleVersionMetaData.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.Nullable;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+
+/**
+ * The result of attempting to resolve a dependency descriptor to the meta-data for a module version.
+ *
+ * TODO - detach this from ModuleVersionMetaData, so that it has-a instead of is-a.
+ */
+public interface BuildableModuleVersionMetaData extends ModuleVersionMetaData {
+    enum State {
+        Resolved, Missing, Failed, ProbablyMissing, Unknown
+    }
+
+    /**
+     * Returns the current state of this descriptor.
+     */
+    State getState();
+
+    @Nullable
+    ModuleVersionResolveException getFailure();
+
+    /**
+     * Marks the module version as resolved, with the given meta-data.
+     */
+    void resolved(ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource);
+
+    /**
+     * Marks the module version as resolved, with the given meta-data.
+     */
+    void resolved(ModuleVersionIdentifier id, ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource);
+
+    /**
+     * Marks the resolve as failed with the given exception.
+     */
+    void failed(ModuleVersionResolveException failure);
+
+    /**
+     * Marks the module version as definitely missing.
+     */
+    void missing();
+
+    /**
+     * Marks the module version as probably missing.
+     */
+    void probablyMissing();
+
+    /**
+     * The repository-specific source for this module version.
+     */
+    public ModuleSource getModuleSource();
+
+    void setModuleSource(ModuleSource moduleSource);
+
+    /**
+     * Replaces the dependencies of this module version.
+     */
+    void setDependencies(Iterable<? extends DependencyMetaData> dependencies);
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java
index db6a154..514cced 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CacheLockingModuleVersionRepository.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 
@@ -40,14 +39,10 @@ public class CacheLockingModuleVersionRepository implements ModuleVersionReposit
         return repository.getName();
     }
 
-    public boolean isLocal() {
-        return repository.isLocal();
-    }
-
-    public void getDependency(final DependencyDescriptor dependencyDescriptor, final BuildableModuleVersionDescriptor result) {
-        cacheLockingManager.longRunningOperation(String.format("Resolve %s using repository %s", dependencyDescriptor, getId()), new Runnable() {
+    public void getDependency(final DependencyMetaData dependency, final BuildableModuleVersionMetaData result) {
+        cacheLockingManager.longRunningOperation(String.format("Resolve %s using repository %s", dependency, getId()), new Runnable() {
             public void run() {
-                repository.getDependency(dependencyDescriptor, result);
+                repository.getDependency(dependency, result);
             }
         });
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
index 4b36bd1..8b82cb5 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/CachingModuleVersionRepository.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.artifacts.ArtifactIdentifier;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
@@ -26,7 +25,6 @@ import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
-import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ForceChangeDependencyDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
 import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
 import org.gradle.api.internal.externalresource.cached.CachedArtifact;
@@ -75,28 +73,25 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         return "Caching " + delegate.toString();
     }
 
-    public boolean isLocal() {
-        return delegate.isLocal();
+    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+        DependencyMetaData resolvedDependency = maybeUseCachedDynamicVersion(delegate, dependency);
+        lookupModuleInCache(delegate, resolvedDependency, result);
     }
 
-    public void getLocalDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
-        DependencyDescriptor resolvedDependencyDescriptor = maybeUseCachedDynamicVersion(delegate, dependencyDescriptor);
-        lookupModuleInCache(delegate, resolvedDependencyDescriptor, result);
-    }
-
-    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
-        delegate.getDependency(ForceChangeDependencyDescriptor.forceChangingFlag(dependencyDescriptor, true), result);
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+        DependencyMetaData forced = dependency.withChanging();
+        delegate.getDependency(forced, result);
         switch (result.getState()) {
             case Missing:
-                final ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
+                final ModuleRevisionId dependencyRevisionId = dependency.getDescriptor().getDependencyRevisionId();
                 final DefaultModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
-                moduleDescriptorCache.cacheModuleDescriptor(delegate, moduleVersionIdentifier, null, null, dependencyDescriptor.isChanging());
+                moduleDescriptorCache.cacheModuleDescriptor(delegate, moduleVersionIdentifier, null, null, dependency.isChanging());
                 break;
             case Resolved:
-                moduleResolutionCache.cacheModuleResolution(delegate, dependencyDescriptor.getDependencyRevisionId(), result.getId());
+                moduleResolutionCache.cacheModuleResolution(delegate, dependency.getDescriptor().getDependencyRevisionId(), result.getId());
                 final ModuleSource moduleSource = result.getModuleSource();
-                final ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.cacheModuleDescriptor(delegate, result.getId(), result.getDescriptor(), moduleSource, isChangingDependency(dependencyDescriptor, result));
-                result.resolved(result.getDescriptor(), result.isChanging(), new CachingModuleSource(cachedModuleDescriptor.getDescriptorHash(), cachedModuleDescriptor.isChangingModule(), moduleSource));
+                final ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.cacheModuleDescriptor(delegate, result.getId(), result.getDescriptor(), moduleSource, isChangingDependency(dependency, result));
+                result.setModuleSource(new CachingModuleSource(cachedModuleDescriptor.getDescriptorHash(), cachedModuleDescriptor.isChangingModule(), moduleSource));
                 break;
             case Failed:
                 break;
@@ -105,8 +100,8 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         }
     }
 
-    private DependencyDescriptor maybeUseCachedDynamicVersion(ModuleVersionRepository repository, DependencyDescriptor original) {
-        ModuleRevisionId originalId = original.getDependencyRevisionId();
+    private DependencyMetaData maybeUseCachedDynamicVersion(ModuleVersionRepository repository, DependencyMetaData original) {
+        ModuleRevisionId originalId = original.getDescriptor().getDependencyRevisionId();
         ModuleResolutionCache.CachedModuleResolution cachedModuleResolution = moduleResolutionCache.getCachedModuleResolution(repository, originalId);
         if (cachedModuleResolution != null && cachedModuleResolution.isDynamicVersion()) {
             ModuleVersionSelector selector = createModuleVersionSelector(originalId);
@@ -116,14 +111,14 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
                 return original;
             } else {
                 LOGGER.debug("Found resolved revision in dynamic revision cache of '{}': Using '{}' for '{}'", repository.getName(), cachedModuleResolution.getResolvedVersion(), originalId);
-                return original.clone(ModuleRevisionId.newInstance(resolvedVersion.getGroup(), resolvedVersion.getName(), resolvedVersion.getVersion()));
+                return original.withRequestedVersion(DefaultModuleVersionSelector.newSelector(resolvedVersion.getGroup(), resolvedVersion.getName(), resolvedVersion.getVersion()));
             }
         }
         return original;
     }
 
-    public void lookupModuleInCache(ModuleVersionRepository repository, DependencyDescriptor resolvedDependencyDescriptor, BuildableModuleVersionDescriptor result) {
-        ModuleRevisionId resolvedModuleVersionId = resolvedDependencyDescriptor.getDependencyRevisionId();
+    public void lookupModuleInCache(ModuleVersionRepository repository, DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+        ModuleRevisionId resolvedModuleVersionId = dependency.getDescriptor().getDependencyRevisionId();
         ModuleVersionIdentifier moduleVersionIdentifier = createModuleVersionIdentifier(resolvedModuleVersionId);
         ModuleDescriptorCache.CachedModuleDescriptor cachedModuleDescriptor = moduleDescriptorCache.getCachedModuleDescriptor(repository, moduleVersionIdentifier);
         if (cachedModuleDescriptor == null) {
@@ -144,7 +139,7 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
             }
             return;
         }
-        if (cachedModuleDescriptor.isChangingModule() || resolvedDependencyDescriptor.isChanging()) {
+        if (cachedModuleDescriptor.isChangingModule() || dependency.isChanging()) {
             if (cachePolicy.mustRefreshChangingModule(moduleVersionIdentifier, cachedModuleDescriptor.getModuleVersion(), cachedModuleDescriptor.getAgeMillis())) {
                 LOGGER.debug("Cached meta-data for changing module is expired: will perform fresh resolve of '{}' in '{}'", resolvedModuleVersionId, repository.getName());
                 return;
@@ -158,12 +153,11 @@ public class CachingModuleVersionRepository implements LocalAwareModuleVersionRe
         }
 
         LOGGER.debug("Using cached module metadata for module '{}' in '{}'", resolvedModuleVersionId, repository.getName());
-        // TODO:DAZ Could provide artifact metadata and file here from artifactFileStore (it's not needed currently)
-        result.resolved(cachedModuleDescriptor.getModuleDescriptor(), cachedModuleDescriptor.isChangingModule(), new CachingModuleSource(cachedModuleDescriptor.getDescriptorHash(), cachedModuleDescriptor.isChangingModule(), cachedModuleDescriptor.getModuleSource()));
+        result.resolved(moduleVersionIdentifier, cachedModuleDescriptor.getModuleDescriptor(), cachedModuleDescriptor.isChangingModule(), new CachingModuleSource(cachedModuleDescriptor.getDescriptorHash(), cachedModuleDescriptor.isChangingModule(), cachedModuleDescriptor.getModuleSource()));
     }
 
-    private boolean isChangingDependency(DependencyDescriptor descriptor, ModuleVersionDescriptor downloadedModule) {
-        if (descriptor.isChanging()) {
+    private boolean isChangingDependency(DependencyMetaData dependency, ModuleVersionMetaData downloadedModule) {
+        if (dependency.isChanging()) {
             return true;
         }
         return downloadedModule.isChanging();
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java
deleted file mode 100644
index 3ba4306..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptor.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
-
-public class DefaultBuildableModuleVersionDescriptor implements BuildableModuleVersionDescriptor {
-    private ModuleDescriptor moduleDescriptor;
-    private boolean changing;
-    private State state = State.Unknown;
-    private ModuleSource moduleSource;
-
-    private ModuleVersionResolveException failure;
-    private ModuleVersionIdentifier moduleVersionIdentifier;
-
-    public void reset(State state) {
-        this.state = state;
-        moduleDescriptor = null;
-        changing = false;
-        failure = null;
-    }
-
-    public void resolved(ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource) {
-        reset(State.Resolved);
-        moduleDescriptor = descriptor;
-        this.changing = changing;
-        final ModuleRevisionId moduleRevisionId = descriptor.getModuleRevisionId();
-        this.moduleVersionIdentifier = new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
-        this.moduleSource = moduleSource;
-    }
-
-    public void missing() {
-        reset(State.Missing);
-    }
-
-    public void probablyMissing() {
-        reset(State.ProbablyMissing);
-    }
-
-    public void failed(ModuleVersionResolveException failure) {
-        reset(State.Failed);
-        this.failure = failure;
-    }
-
-    public State getState() {
-        return state;
-    }
-
-    public ModuleVersionResolveException getFailure() {
-        assertHasResult();
-        return failure;
-    }
-
-    private void assertHasResult() {
-        if (state == State.Unknown) {
-            throw new IllegalStateException("No result has been specified.");
-        }
-    }
-
-    private void assertResolved() {
-        if (state == State.Failed) {
-            throw failure;
-        }
-        if (state != State.Resolved) {
-            throw new IllegalStateException("This module has not been resolved.");
-        }
-    }
-
-    public ModuleVersionIdentifier getId() {
-        assertResolved();
-        return moduleVersionIdentifier;
-    }
-
-    public ModuleDescriptor getDescriptor() {
-        assertResolved();
-        return moduleDescriptor;
-    }
-
-    public boolean isChanging() {
-        assertResolved();
-        return changing;
-    }
-
-    public ModuleSource getModuleSource() {
-        assertResolved();
-        return moduleSource;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaData.java
new file mode 100644
index 0000000..ec67ca1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaData.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
+import org.gradle.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultBuildableModuleVersionMetaData implements BuildableModuleVersionMetaData {
+    private ModuleDescriptor moduleDescriptor;
+    private boolean changing;
+    private State state = State.Unknown;
+    private ModuleSource moduleSource;
+    private List<DependencyMetaData> dependencies;
+    private ModuleVersionResolveException failure;
+    private ModuleVersionIdentifier moduleVersionIdentifier;
+
+    public void reset(State state) {
+        this.state = state;
+        moduleDescriptor = null;
+        changing = false;
+        failure = null;
+    }
+
+    public void resolved(ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource) {
+        ModuleRevisionId moduleRevisionId = descriptor.getModuleRevisionId();
+        DefaultModuleVersionIdentifier id = new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
+        resolved(id, descriptor, changing, moduleSource);
+    }
+
+    public void resolved(ModuleVersionIdentifier id, ModuleDescriptor descriptor, boolean changing, ModuleSource moduleSource) {
+        reset(State.Resolved);
+        this.moduleVersionIdentifier = id;
+        this.moduleDescriptor = descriptor;
+        this.changing = changing;
+        this.moduleSource = moduleSource;
+    }
+
+    public void missing() {
+        reset(State.Missing);
+    }
+
+    public void probablyMissing() {
+        reset(State.ProbablyMissing);
+    }
+
+    public void failed(ModuleVersionResolveException failure) {
+        reset(State.Failed);
+        this.failure = failure;
+    }
+
+    public State getState() {
+        return state;
+    }
+
+    public ModuleVersionResolveException getFailure() {
+        assertHasResult();
+        return failure;
+    }
+
+    private void assertHasResult() {
+        if (state == State.Unknown) {
+            throw new IllegalStateException("No result has been specified.");
+        }
+    }
+
+    private void assertResolved() {
+        if (state == State.Failed) {
+            throw failure;
+        }
+        if (state != State.Resolved) {
+            throw new IllegalStateException("This module has not been resolved.");
+        }
+    }
+
+    public ModuleVersionIdentifier getId() {
+        assertResolved();
+        return moduleVersionIdentifier;
+    }
+
+    public ModuleDescriptor getDescriptor() {
+        assertResolved();
+        return moduleDescriptor;
+    }
+
+    public List<DependencyMetaData> getDependencies() {
+        assertResolved();
+        if (dependencies == null) {
+            dependencies = new ArrayList<DependencyMetaData>();
+            for (final DependencyDescriptor dependencyDescriptor : moduleDescriptor.getDependencies()) {
+                dependencies.add(new DefaultDependencyMetaData(dependencyDescriptor));
+            }
+        }
+        return dependencies;
+    }
+
+    public void setDependencies(Iterable<? extends DependencyMetaData> dependencies) {
+        assertResolved();
+        this.dependencies = CollectionUtils.toList(dependencies);
+    }
+
+    public boolean isChanging() {
+        assertResolved();
+        return changing;
+    }
+
+    public ModuleSource getModuleSource() {
+        assertResolved();
+        return moduleSource;
+    }
+
+    public void setModuleSource(ModuleSource moduleSource) {
+        assertResolved();
+        this.moduleSource = moduleSource;
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaData.java
new file mode 100644
index 0000000..b442e75
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaData.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ReflectiveDependencyDescriptorFactory;
+import org.gradle.internal.UncheckedException;
+
+import java.lang.reflect.Field;
+
+class DefaultDependencyMetaData implements DependencyMetaData {
+    private final DependencyDescriptor dependencyDescriptor;
+    private DefaultModuleVersionSelector requested;
+
+    public DefaultDependencyMetaData(DependencyDescriptor dependencyDescriptor) {
+        this.dependencyDescriptor = dependencyDescriptor;
+        ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
+        requested = new DefaultModuleVersionSelector(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
+    }
+
+    @Override
+    public String toString() {
+        return dependencyDescriptor.toString();
+    }
+
+    public ModuleVersionSelector getRequested() {
+        return requested;
+    }
+
+    public boolean isChanging() {
+        return dependencyDescriptor.isChanging();
+    }
+
+    public DependencyDescriptor getDescriptor() {
+        return dependencyDescriptor;
+    }
+
+    public DependencyMetaData withRequestedVersion(String requestedVersion) {
+        if (requestedVersion.equals(requested.getVersion())) {
+            return this;
+        }
+        return new DefaultDependencyMetaData(dependencyDescriptor.clone(ModuleRevisionId.newInstance(dependencyDescriptor.getDependencyRevisionId(), requestedVersion)));
+    }
+
+    public DependencyMetaData withRequestedVersion(ModuleVersionSelector requestedVersion) {
+        if (requestedVersion.equals(requested)) {
+            return this;
+        }
+
+        ModuleRevisionId requestedId = ModuleRevisionId.newInstance(requestedVersion.getGroup(), requestedVersion.getName(), requestedVersion.getVersion());
+        DependencyDescriptor substitutedDescriptor = new ReflectiveDependencyDescriptorFactory().create(dependencyDescriptor, requestedId);
+        return new DefaultDependencyMetaData(substitutedDescriptor);
+    }
+
+    public DependencyMetaData withChanging() {
+        if (dependencyDescriptor.isChanging()) {
+            return this;
+        }
+
+        DependencyDescriptor forcedChanging = dependencyDescriptor.clone(dependencyDescriptor.getDependencyRevisionId());
+        try {
+            Field field = DefaultDependencyDescriptor.class.getDeclaredField("isChanging");
+            field.setAccessible(true);
+            field.set(forcedChanging, true);
+        } catch (Exception e) {
+            throw UncheckedException.throwAsUncheckedException(e);
+        }
+        return new DefaultDependencyMetaData(forcedChanging);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyMetaData.java
new file mode 100644
index 0000000..889998e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyMetaData.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.gradle.api.artifacts.ModuleVersionSelector;
+
+public interface DependencyMetaData {
+    ModuleVersionSelector getRequested();
+
+    DependencyDescriptor getDescriptor();
+
+    boolean isChanging();
+
+    /**
+     * Returns a copy of this dependency with the given requested version.
+     */
+    DependencyMetaData withRequestedVersion(String requestedVersion);
+
+    /**
+     * Returns a copy of this dependency with the given requested version.
+     */
+    DependencyMetaData withRequestedVersion(ModuleVersionSelector requestedVersion);
+
+    /**
+     * Returns a copy of this dependency with the changing flag set.
+     */
+    DependencyMetaData withChanging();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java
index 4cd82e0..e634fa2 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ExternalResourceResolverAdapter.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
 
@@ -25,14 +24,21 @@ import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceR
  */
 public class ExternalResourceResolverAdapter extends AbstractDependencyResolverAdapter {
     private final ExternalResourceResolver resolver;
+    private final boolean dynamicResolve;
 
-    public ExternalResourceResolverAdapter(ExternalResourceResolver resolver) {
+    public ExternalResourceResolverAdapter(ExternalResourceResolver resolver, boolean dynamicResolve) {
         super(resolver);
         this.resolver = resolver;
+        this.dynamicResolve = dynamicResolve;
     }
 
-    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
-        resolver.getDependency(dependencyDescriptor, result);
+    @Override
+    public boolean isDynamicResolveMode() {
+        return dynamicResolve;
+    }
+
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+        resolver.getDependency(dependency.getDescriptor(), result);
     }
 
     public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAwareModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAwareModuleVersionRepository.java
new file mode 100644
index 0000000..b999020
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyAwareModuleVersionRepository.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.settings.IvySettings;
+
+public interface IvyAwareModuleVersionRepository extends ModuleVersionRepository {
+    void setSettings(IvySettings settings);
+
+    // TODO - move this
+    boolean isDynamicResolveMode();
+
+    boolean isLocal();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
index 100f9c8..e4fe31b 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDependencyResolverAdapter.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.report.ArtifactDownloadReport;
 import org.apache.ivy.core.report.DownloadStatus;
 import org.apache.ivy.core.resolve.DownloadOptions;
@@ -43,15 +42,15 @@ public class IvyDependencyResolverAdapter extends AbstractDependencyResolverAdap
         super(resolver);
     }
 
-    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
         ResolveData resolveData = IvyContextualiser.getIvyContext().getResolveData();
         try {
-            ResolvedModuleRevision revision = resolver.getDependency(dependencyDescriptor, resolveData);
+            ResolvedModuleRevision revision = resolver.getDependency(dependency.getDescriptor(), resolveData);
             if (revision == null) {
-                LOGGER.debug("Performed resolved of module '{}' in repository '{}': not found", dependencyDescriptor.getDependencyRevisionId(), getName());
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': not found", dependency.getRequested(), getName());
                 result.missing();
             } else {
-                LOGGER.debug("Performed resolved of module '{}' in repository '{}': found", dependencyDescriptor.getDependencyRevisionId(), getName());
+                LOGGER.debug("Performed resolved of module '{}' in repository '{}': found", dependency.getRequested(), getName());
                 result.resolved(revision.getDescriptor(), isChanging(revision), null);
             }
         } catch (ParseException e) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepository.java
new file mode 100644
index 0000000..821e0d8
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepository.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class IvyDynamicResolveModuleVersionRepository implements LocalAwareModuleVersionRepository {
+    private final LocalAwareModuleVersionRepository repository;
+
+    public IvyDynamicResolveModuleVersionRepository(LocalAwareModuleVersionRepository repository) {
+        this.repository = repository;
+    }
+
+    public String getId() {
+        return repository.getId();
+    }
+
+    public String getName() {
+        return repository.getName();
+    }
+
+    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+        repository.getLocalDependency(dependency, result);
+        if (result.getState() == BuildableModuleVersionMetaData.State.Resolved) {
+            transformDependencies(result);
+        }
+    }
+
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+        repository.getDependency(dependency, result);
+        if (result.getState() == BuildableModuleVersionMetaData.State.Resolved) {
+            transformDependencies(result);
+        }
+    }
+
+    private void transformDependencies(BuildableModuleVersionMetaData result) {
+        List<DependencyMetaData> transformed = new ArrayList<DependencyMetaData>();
+        for (DependencyMetaData dependency : result.getDependencies()) {
+            transformed.add(dependency.withRequestedVersion(dependency.getDescriptor().getDynamicConstraintDependencyRevisionId().getRevision()));
+        }
+        result.setDependencies(transformed);
+    }
+
+    public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
+        repository.resolve(artifact, result, moduleSource);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java
index 6185f63..637cb5b 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolver.java
@@ -17,12 +17,10 @@ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.Configuration;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleId;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.version.VersionMatcher;
 import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.*;
@@ -41,13 +39,13 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
         this.versionMatcher = versionMatcher;
     }
 
-    public ModuleVersionIdResolveResult resolve(DependencyDescriptor dependencyDescriptor) {
-        if (versionMatcher.isDynamic(dependencyDescriptor.getDependencyRevisionId())) {
-            DynamicVersionResolveResult result = new DynamicVersionResolveResult(dependencyDescriptor);
+    public ModuleVersionIdResolveResult resolve(DependencyMetaData dependency) {
+        if (versionMatcher.isDynamic(dependency.getDescriptor().getDependencyRevisionId())) {
+            DynamicVersionResolveResult result = new DynamicVersionResolveResult(dependency);
             result.resolve();
             return result;
         }
-        return new StaticVersionResolveResult(dependencyDescriptor);
+        return new StaticVersionResolveResult(dependency);
     }
 
     private static class ErrorHandlingArtifactResolver implements ArtifactResolver {
@@ -66,17 +64,12 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
         }
     }
 
-    private class StaticVersionResolveResult implements ModuleVersionIdResolveResult {
-        private final DependencyDescriptor dependencyDescriptor;
+    private abstract class AbstractVersionResolveResult implements ModuleVersionIdResolveResult {
+        final DependencyMetaData dependency;
         private BuildableModuleVersionResolveResult resolveResult;
 
-        public StaticVersionResolveResult(DependencyDescriptor dependencyDescriptor) {
-            this.dependencyDescriptor = dependencyDescriptor;
-        }
-
-        public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
-            final ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
-            return new DefaultModuleVersionIdentifier(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
+        public AbstractVersionResolveResult(DependencyMetaData dependency) {
+            this.dependency = dependency;
         }
 
         public ModuleVersionResolveException getFailure() {
@@ -88,17 +81,17 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
                 resolveResult = new DefaultBuildableModuleVersionResolveResult();
                 try {
                     try {
-                        dependencyResolver.resolve(dependencyDescriptor, resolveResult);
+                        dependencyResolver.resolve(dependency, resolveResult);
                     } catch (Throwable t) {
-                        throw new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), t);
+                        throw new ModuleVersionResolveException(dependency.getRequested(), t);
                     }
                     if (resolveResult.getFailure() instanceof ModuleVersionNotFoundException) {
-                        throw notFound(dependencyDescriptor.getDependencyRevisionId());
+                        throw notFound();
                     }
                     if (resolveResult.getFailure() != null) {
                         throw resolveResult.getFailure();
                     }
-                    checkDescriptor(resolveResult.getDescriptor());
+                    checkDescriptor(resolveResult.getMetaData());
                     resolveResult.setArtifactResolver(new ErrorHandlingArtifactResolver(resolveResult.getArtifactResolver()));
                 } catch (ModuleVersionResolveException e) {
                     resolveResult.failed(e);
@@ -112,37 +105,53 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             return VersionSelectionReasons.REQUESTED;
         }
 
-        private void checkDescriptor(ModuleDescriptor descriptor) {
-            ModuleRevisionId id = descriptor.getModuleRevisionId();
-            if (!copy(id).equals(copy(dependencyDescriptor.getDependencyRevisionId()))) {
-                onUnexpectedModuleRevisionId(descriptor);
-            }
-            for (Configuration configuration : descriptor.getConfigurations()) {
+        protected void checkDescriptor(ModuleVersionMetaData metaData) {
+            ModuleDescriptor moduleDescriptor = metaData.getDescriptor();
+            for (Configuration configuration : moduleDescriptor.getConfigurations()) {
                 for (String parent : configuration.getExtends()) {
-                    if (descriptor.getConfiguration(parent) == null) {
-                        throw new ModuleVersionResolveException(id, String.format("Configuration '%s' extends unknown configuration '%s' in module descriptor for %%s.", configuration.getName(), parent));
+                    if (moduleDescriptor.getConfiguration(parent) == null) {
+                        throw new ModuleVersionResolveException(metaData.getId(), String.format("Configuration '%s' extends unknown configuration '%s' in module descriptor for %%s.", configuration.getName(), parent));
                     }
                 }
             }
         }
 
-        private ModuleRevisionId copy(ModuleRevisionId id) {
-            // Copy to get rid of extra attributes
-            return new ModuleRevisionId(new ModuleId(id.getOrganisation(), id.getName()), id.getRevision());
+        protected abstract ModuleVersionNotFoundException notFound();
+    }
+
+    private class StaticVersionResolveResult extends AbstractVersionResolveResult {
+        private final ModuleVersionIdentifier id;
+
+        public StaticVersionResolveResult(DependencyMetaData dependency) {
+            super(dependency);
+            ModuleVersionSelector requested = dependency.getRequested();
+            id = new DefaultModuleVersionIdentifier(requested.getGroup(), requested.getName(), requested.getVersion());
         }
 
-        protected ModuleVersionNotFoundException notFound(ModuleRevisionId id) {
-            return new ModuleVersionNotFoundException(id);
+        public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
+            return id;
+        }
+
+        public ModuleVersionSelectionReason getSelectionReason() {
+            return VersionSelectionReasons.REQUESTED;
         }
 
-        protected void onUnexpectedModuleRevisionId(ModuleDescriptor descriptor) {
-            throw new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), String.format("Received unexpected module descriptor %s for dependency %%s.", descriptor.getModuleRevisionId()));
+        @Override
+        protected void checkDescriptor(ModuleVersionMetaData metaData) {
+            if (!id.equals(metaData.getId())) {
+                throw new ModuleVersionResolveException(dependency.getRequested(), String.format("Received unexpected module descriptor %s for dependency %%s.", metaData.getId()));
+            }
+            super.checkDescriptor(metaData);
+        }
+
+        protected ModuleVersionNotFoundException notFound() {
+            return new ModuleVersionNotFoundException(id);
         }
     }
 
-    private class DynamicVersionResolveResult extends StaticVersionResolveResult {
-        public DynamicVersionResolveResult(DependencyDescriptor dependencyDescriptor) {
-            super(dependencyDescriptor);
+    private class DynamicVersionResolveResult extends AbstractVersionResolveResult {
+        public DynamicVersionResolveResult(DependencyMetaData dependency) {
+            super(dependency);
         }
 
         @Override
@@ -150,19 +159,13 @@ public class LazyDependencyToModuleResolver implements DependencyToModuleVersion
             return resolve().getFailure();
         }
 
-        @Override
         public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
             return resolve().getId();
         }
 
         @Override
-        protected ModuleVersionNotFoundException notFound(ModuleRevisionId id) {
-            return new ModuleVersionNotFoundException(id, "Could not find any version that matches %s.");
-        }
-
-        @Override
-        protected void onUnexpectedModuleRevisionId(ModuleDescriptor descriptor) {
-            // Don't care
+        protected ModuleVersionNotFoundException notFound() {
+            return new ModuleVersionNotFoundException(dependency.getRequested());
         }
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalAwareModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalAwareModuleVersionRepository.java
index 670dce9..2066cb0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalAwareModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalAwareModuleVersionRepository.java
@@ -16,16 +16,14 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-
 public interface LocalAwareModuleVersionRepository extends ModuleVersionRepository {
     /**
      * Locates the given dependency, using only local resources.
      */
-    void getLocalDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result);
+    void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result);
 
     /**
-     * Locates the given dependency, using whichever resources are appropriate. Always called after {@link #getLocalDependency(org.apache.ivy.core.module.descriptor.DependencyDescriptor, BuildableModuleVersionDescriptor)}.
+     * Locates the given dependency, using whichever resources are appropriate. Always called after {@link #getLocalDependency(DependencyMetaData, BuildableModuleVersionMetaData)}.
      */
-    void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result);
+    void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java
index 8e01d79..93268b6 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LocalModuleVersionRepository.java
@@ -17,7 +17,6 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 
 public class LocalModuleVersionRepository implements LocalAwareModuleVersionRepository {
@@ -27,19 +26,15 @@ public class LocalModuleVersionRepository implements LocalAwareModuleVersionRepo
         this.delegate = delegate;
     }
 
-    public boolean isLocal() {
-        return delegate.isLocal();
-    }
-
     public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
         delegate.resolve(artifact, result, moduleSource);
     }
 
-    public void getLocalDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
-        delegate.getDependency(dependencyDescriptor, result);
+    public void getLocalDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+        delegate.getDependency(dependency, result);
     }
 
-    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+    public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
         result.missing();
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LoopbackDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LoopbackDependencyResolver.java
index 65ba6cd..38f1308 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LoopbackDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LoopbackDependencyResolver.java
@@ -54,7 +54,7 @@ public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
 
     @Override
     public void setSettings(ResolverSettings settings) {
-        userResolverChain.setSettings(settings);
+        // don't care
     }
 
     @Override
@@ -62,16 +62,16 @@ public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
         final DependencyResolver loopback = this;
         return cacheLockingManager.useCache(String.format("Resolve %s", dd), new Factory<ResolvedModuleRevision>() {
             public ResolvedModuleRevision create() {
-                DefaultBuildableModuleVersionResolveResult dependency = new DefaultBuildableModuleVersionResolveResult();
+                DefaultBuildableModuleVersionResolveResult result = new DefaultBuildableModuleVersionResolveResult();
+                DefaultDependencyMetaData dependency = new DefaultDependencyMetaData(dd);
                 IvyContext ivyContext = IvyContext.pushNewCopyContext();
                 try {
                     ivyContext.setResolveData(data);
-                    userResolverChain.resolve(dd, dependency);
+                    userResolverChain.resolve(dependency, result);
                 } finally {
                     IvyContext.popContext();
                 }
-                // TODO:DAZ Need to create a metadata download report here
-                return new ResolvedModuleRevision(loopback, loopback, dependency.getDescriptor(), null);
+                return new ResolvedModuleRevision(loopback, loopback, result.getMetaData().getDescriptor(), null);
             }
         });
     }
@@ -82,11 +82,12 @@ public class LoopbackDependencyResolver extends RestrictedDependencyResolver {
             public ArtifactOrigin create() {
                 try {
                     DependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(artifact.getModuleRevisionId(), false);
-                    DefaultBuildableModuleVersionResolveResult dependency = new DefaultBuildableModuleVersionResolveResult();
-                    userResolverChain.resolve(dependencyDescriptor, dependency);
-                    DefaultBuildableArtifactResolveResult result = new DefaultBuildableArtifactResolveResult();
-                    dependency.getArtifactResolver().resolve(artifact, result);
-                    File artifactFile = result.getFile();
+                    DefaultBuildableModuleVersionResolveResult resolveResult = new DefaultBuildableModuleVersionResolveResult();
+                    DefaultDependencyMetaData dependency = new DefaultDependencyMetaData(dependencyDescriptor);
+                    userResolverChain.resolve(dependency, resolveResult);
+                    DefaultBuildableArtifactResolveResult artifactResolveResult = new DefaultBuildableArtifactResolveResult();
+                    resolveResult.getArtifactResolver().resolve(artifact, artifactResolveResult);
+                    File artifactFile = artifactResolveResult.getFile();
                     return new ArtifactOrigin(artifact, false, artifactFile.getAbsolutePath());
                 } catch (ModuleVersionNotFoundException e) {
                     return null;
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java
deleted file mode 100644
index 8a6d48a..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionDescriptor.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-
-// at some point, this is due to merge with ModuleVersionResolveResult
-public interface ModuleVersionDescriptor {
-    ModuleVersionIdentifier getId();
-
-    ModuleDescriptor getDescriptor();
-
-    boolean isChanging();
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionMetaData.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionMetaData.java
new file mode 100644
index 0000000..db1ad9c
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionMetaData.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+
+import java.util.List;
+
+/**
+ * The meta-data for a module version.
+ */
+public interface ModuleVersionMetaData {
+    ModuleVersionIdentifier getId();
+
+    ModuleDescriptor getDescriptor();
+
+    List<DependencyMetaData> getDependencies();
+
+    boolean isChanging();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
index 216493d..8b6df7d 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ModuleVersionRepository.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 
 /**
@@ -29,13 +28,10 @@ public interface ModuleVersionRepository {
 
     String getName();
 
-    void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result);
+    void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result);
 
     /**
      * Downloads the given artifact. Any failures are packaged up in the result.
      */
     void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource);
-
-    // TODO - should be internal to the implementation of this (is only used to communicate IvyDependencyResolverAdapter -> CachingModuleVersionRepository)
-    boolean isLocal();
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
index 29603b2..e672c0e 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolveIvyFactory.java
@@ -19,25 +19,20 @@ import org.apache.ivy.Ivy;
 import org.apache.ivy.core.resolve.ResolveData;
 import org.apache.ivy.core.resolve.ResolveOptions;
 import org.apache.ivy.core.settings.IvySettings;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.artifacts.cache.ResolutionRules;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
 import org.gradle.api.internal.artifacts.ivyservice.CacheLockingManager;
 import org.gradle.api.internal.artifacts.ivyservice.IvyFactory;
 import org.gradle.api.internal.artifacts.ivyservice.SettingsConverter;
 import org.gradle.api.internal.artifacts.ivyservice.dynamicversions.ModuleResolutionCache;
 import org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleDescriptorCache;
-import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
 import org.gradle.api.internal.externalresource.cached.CachedArtifactIndex;
 import org.gradle.internal.TimeProvider;
 import org.gradle.util.WrapUtil;
 
-import java.util.List;
-
 public class ResolveIvyFactory {
     private final IvyFactory ivyFactory;
-    private final ResolverProvider resolverProvider;
     private final SettingsConverter settingsConverter;
     private final ModuleResolutionCache moduleResolutionCache;
     private final ModuleDescriptorCache moduleDescriptorCache;
@@ -46,13 +41,12 @@ public class ResolveIvyFactory {
     private final StartParameterResolutionOverride startParameterResolutionOverride;
     private final TimeProvider timeProvider;
 
-    public ResolveIvyFactory(IvyFactory ivyFactory, ResolverProvider resolverProvider, SettingsConverter settingsConverter,
+    public ResolveIvyFactory(IvyFactory ivyFactory, SettingsConverter settingsConverter,
                              ModuleResolutionCache moduleResolutionCache, ModuleDescriptorCache moduleDescriptorCache,
                              CachedArtifactIndex artifactAtRepositoryCachedResolutionIndex,
                              CacheLockingManager cacheLockingManager, StartParameterResolutionOverride startParameterResolutionOverride,
                              TimeProvider timeProvider) {
         this.ivyFactory = ivyFactory;
-        this.resolverProvider = resolverProvider;
         this.settingsConverter = settingsConverter;
         this.moduleResolutionCache = moduleResolutionCache;
         this.moduleDescriptorCache = moduleDescriptorCache;
@@ -62,38 +56,38 @@ public class ResolveIvyFactory {
         this.timeProvider = timeProvider;
     }
 
-    public IvyAdapter create(ConfigurationInternal configuration) {
+    public IvyAdapter create(ConfigurationInternal configuration, Iterable<? extends ResolutionAwareRepository> repositories) {
         UserResolverChain userResolverChain = new UserResolverChain();
         ResolutionRules resolutionRules = configuration.getResolutionStrategy().getResolutionRules();
         startParameterResolutionOverride.addResolutionRules(resolutionRules);
 
         LoopbackDependencyResolver loopbackDependencyResolver = new LoopbackDependencyResolver(SettingsConverter.LOOPBACK_RESOLVER_NAME, userResolverChain, cacheLockingManager);
-        List<DependencyResolver> rawResolvers = resolverProvider.getResolvers();
 
-        IvySettings ivySettings = settingsConverter.convertForResolve(loopbackDependencyResolver, rawResolvers);
+        IvySettings ivySettings = settingsConverter.convertForResolve(loopbackDependencyResolver);
+        userResolverChain.setSettings(ivySettings);
+
         Ivy ivy = ivyFactory.createIvy(ivySettings);
         ResolveData resolveData = createResolveData(ivy, configuration.getName());
         IvyContextualiser contextualiser = new IvyContextualiser(ivy, resolveData);
-        for (DependencyResolver rawResolver : rawResolvers) {
-            // TODO:DAZ This could be lazily provided via the ivy context. Then we can change resolverProvider.getResolvers() -> getRepositories().
-            rawResolver.setSettings(ivySettings);
-            ModuleVersionRepository moduleVersionRepository;
-            if (rawResolver instanceof ExternalResourceResolver) {
-                moduleVersionRepository = new ExternalResourceResolverAdapter((ExternalResourceResolver) rawResolver);
-            } else {
-                moduleVersionRepository = new IvyDependencyResolverAdapter(rawResolver);
-            }
-            moduleVersionRepository = new CacheLockingModuleVersionRepository(moduleVersionRepository, cacheLockingManager);
-            moduleVersionRepository = startParameterResolutionOverride.overrideModuleVersionRepository(moduleVersionRepository);
-            LocalAwareModuleVersionRepository cachingRepository;
+
+        for (ResolutionAwareRepository repository : repositories) {
+            IvyAwareModuleVersionRepository moduleVersionRepository = repository.createResolver();
+            moduleVersionRepository.setSettings(ivySettings);
+
+            LocalAwareModuleVersionRepository localAwareRepository;
             if (moduleVersionRepository.isLocal()) {
-                cachingRepository = new LocalModuleVersionRepository(moduleVersionRepository);
+                localAwareRepository = new LocalModuleVersionRepository(moduleVersionRepository);
             } else {
-                cachingRepository = new CachingModuleVersionRepository(moduleVersionRepository, moduleResolutionCache, moduleDescriptorCache, artifactAtRepositoryCachedResolutionIndex,
+                ModuleVersionRepository wrapperRepository = new CacheLockingModuleVersionRepository(moduleVersionRepository, cacheLockingManager);
+                wrapperRepository = startParameterResolutionOverride.overrideModuleVersionRepository(wrapperRepository);
+                localAwareRepository = new CachingModuleVersionRepository(wrapperRepository, moduleResolutionCache, moduleDescriptorCache, artifactAtRepositoryCachedResolutionIndex,
                         configuration.getResolutionStrategy().getCachePolicy(), timeProvider);
             }
-            LocalAwareModuleVersionRepository ivyContextualisedRepository = contextualiser.contextualise(LocalAwareModuleVersionRepository.class, cachingRepository);
-            userResolverChain.add(ivyContextualisedRepository);
+            if (moduleVersionRepository.isDynamicResolveMode()) {
+                localAwareRepository = new IvyDynamicResolveModuleVersionRepository(localAwareRepository);
+            }
+            localAwareRepository = contextualiser.contextualise(LocalAwareModuleVersionRepository.class, localAwareRepository);
+            userResolverChain.add(localAwareRepository);
         }
 
         return new DefaultIvyAdapter(resolveData, userResolverChain);
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java
index d10a0e3..fc308e2 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RestrictedDependencyResolver.java
@@ -21,7 +21,7 @@ import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 
-abstract class RestrictedDependencyResolver extends DelegatingDependencyResolver {
+public abstract class RestrictedDependencyResolver extends DelegatingDependencyResolver {
     protected RestrictedDependencyResolver() {
         super(createAngryDelegate());
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
index 98a85ab..b75aeec 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java
@@ -16,7 +16,6 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.gradle.StartParameter;
 import org.gradle.api.Action;
 import org.gradle.api.artifacts.cache.ArtifactResolutionControl;
@@ -72,7 +71,7 @@ public class StartParameterResolutionOverride {
     }
 
     public ModuleVersionRepository overrideModuleVersionRepository(ModuleVersionRepository original) {
-        if (startParameter.isOffline() && !original.isLocal()) {
+        if (startParameter.isOffline()) {
             return new OfflineModuleVersionRepository(original);
         }
         return original;
@@ -97,8 +96,8 @@ public class StartParameterResolutionOverride {
             return false;
         }
 
-        public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
-            result.failed(new ModuleVersionResolveException(dependencyDescriptor.getDependencyRevisionId(), "No cached version of %s available for offline mode."));
+        public void getDependency(DependencyMetaData dependency, BuildableModuleVersionMetaData result) {
+            result.failed(new ModuleVersionResolveException(dependency.getRequested(), "No cached version of %s available for offline mode."));
         }
 
         public void resolve(Artifact artifact, BuildableArtifactResolveResult result, ModuleSource moduleSource) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
index 264b75a..0364650 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChain.java
@@ -17,14 +17,10 @@
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.latest.ArtifactInfo;
 import org.apache.ivy.plugins.latest.ComparatorLatestStrategy;
 import org.apache.ivy.plugins.resolver.ResolverSettings;
-import org.gradle.api.artifacts.ModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
+import org.gradle.api.artifacts.ModuleVersionSelector;
 import org.gradle.api.internal.artifacts.ivyservice.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -47,29 +43,28 @@ public class UserResolverChain implements DependencyToModuleResolver {
         moduleVersionRepositoryNames.add(repository.getName());
     }
 
-    public void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result) {
-        final ModuleRevisionId dependencyRevisionId = dependencyDescriptor.getDependencyRevisionId();
-        LOGGER.debug("Attempting to resolve module '{}' using repositories {}", dependencyRevisionId, moduleVersionRepositoryNames);
+    public void resolve(DependencyMetaData dependency, BuildableModuleVersionResolveResult result) {
+        ModuleVersionSelector requested = dependency.getRequested();
+        LOGGER.debug("Attempting to resolve module '{}' using repositories {}", requested, moduleVersionRepositoryNames);
         List<Throwable> errors = new ArrayList<Throwable>();
-        final ModuleResolution latestResolved = findLatestModule(dependencyDescriptor, errors);
+        final ModuleResolution latestResolved = findLatestModule(dependency, errors);
         if (latestResolved != null) {
-            final ModuleVersionDescriptor downloadedModule = latestResolved.module;
+            final ModuleVersionMetaData downloadedModule = latestResolved.module;
             LOGGER.debug("Using module '{}' from repository '{}'", downloadedModule.getId(), latestResolved.repository.getName());
             for (Throwable error : errors) {
                 LOGGER.debug("Discarding resolve failure.", error);
             }
-            result.resolved(latestResolved.getId(), latestResolved.getDescriptor(), new ModuleVersionRepositoryArtifactResolverAdapter(latestResolved.repository, latestResolved.moduleSource));
+            result.resolved(latestResolved.module, new ModuleVersionRepositoryArtifactResolverAdapter(latestResolved.repository, latestResolved.moduleSource));
             return;
         }
         if (!errors.isEmpty()) {
-            result.failed(new ModuleVersionResolveException(dependencyRevisionId, errors));
+            result.failed(new ModuleVersionResolveException(requested, errors));
         } else {
-            final DefaultModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(dependencyRevisionId.getOrganisation(), dependencyRevisionId.getName(), dependencyRevisionId.getRevision());
-            result.notFound(moduleVersionIdentifier);
+            result.notFound(requested);
         }
     }
 
-    private ModuleResolution findLatestModule(DependencyDescriptor dependencyDescriptor, Collection<Throwable> failures) {
+    private ModuleResolution findLatestModule(DependencyMetaData dependency, Collection<Throwable> failures) {
         LinkedList<RepositoryResolveState> queue = new LinkedList<RepositoryResolveState>();
         for (LocalAwareModuleVersionRepository repository : moduleVersionRepositories) {
             queue.add(new RepositoryResolveState(repository));
@@ -77,7 +72,7 @@ public class UserResolverChain implements DependencyToModuleResolver {
         LinkedList<RepositoryResolveState> missing = new LinkedList<RepositoryResolveState>();
 
         // A first pass to do local resolves only
-        ModuleResolution best = findLatestModule(dependencyDescriptor, queue, failures, missing);
+        ModuleResolution best = findLatestModule(dependency, queue, failures, missing);
         if (best != null) {
             return best;
         }
@@ -85,16 +80,16 @@ public class UserResolverChain implements DependencyToModuleResolver {
         // Nothing found - do a second pass
         queue.addAll(missing);
         missing.clear();
-        return findLatestModule(dependencyDescriptor, queue, failures, missing);
+        return findLatestModule(dependency, queue, failures, missing);
     }
 
-    private ModuleResolution findLatestModule(DependencyDescriptor dependencyDescriptor, LinkedList<RepositoryResolveState> queue, Collection<Throwable> failures, Collection<RepositoryResolveState> missing) {
-        boolean isStaticVersion = !settings.getVersionMatcher().isDynamic(dependencyDescriptor.getDependencyRevisionId());
+    private ModuleResolution findLatestModule(DependencyMetaData dependency, LinkedList<RepositoryResolveState> queue, Collection<Throwable> failures, Collection<RepositoryResolveState> missing) {
+        boolean isStaticVersion = !settings.getVersionMatcher().isDynamic(dependency.getDescriptor().getDependencyRevisionId());
         ModuleResolution best = null;
         while (!queue.isEmpty()) {
             RepositoryResolveState request = queue.removeFirst();
             try {
-                request.resolve(dependencyDescriptor);
+                request.resolve(dependency);
             } catch (Throwable t) {
                 failures.add(t);
                 continue;
@@ -167,7 +162,7 @@ public class UserResolverChain implements DependencyToModuleResolver {
 
     private static class RepositoryResolveState {
         final LocalAwareModuleVersionRepository repository;
-        final DefaultBuildableModuleVersionDescriptor descriptor = new DefaultBuildableModuleVersionDescriptor();
+        final DefaultBuildableModuleVersionMetaData descriptor = new DefaultBuildableModuleVersionMetaData();
 
         boolean searchedLocally;
         boolean searchedRemotely;
@@ -176,15 +171,15 @@ public class UserResolverChain implements DependencyToModuleResolver {
             this.repository = repository;
         }
 
-        void resolve(DependencyDescriptor dependencyDescriptor) {
+        void resolve(DependencyMetaData dependency) {
             if (!searchedLocally) {
                 searchedLocally = true;
-                repository.getLocalDependency(dependencyDescriptor, descriptor);
+                repository.getLocalDependency(dependency, descriptor);
             } else {
                 searchedRemotely = true;
-                repository.getDependency(dependencyDescriptor, descriptor);
+                repository.getDependency(dependency, descriptor);
             }
-            if (descriptor.getState() == BuildableModuleVersionDescriptor.State.Failed) {
+            if (descriptor.getState() == BuildableModuleVersionMetaData.State.Failed) {
                 throw descriptor.getFailure();
             }
         }
@@ -196,23 +191,15 @@ public class UserResolverChain implements DependencyToModuleResolver {
 
     private static class ModuleResolution implements ArtifactInfo {
         public final ModuleVersionRepository repository;
-        public final ModuleVersionDescriptor module;
+        public final ModuleVersionMetaData module;
         public final ModuleSource moduleSource;
 
-        public ModuleResolution(ModuleVersionRepository repository, ModuleVersionDescriptor module, ModuleSource moduleSource) {
+        public ModuleResolution(ModuleVersionRepository repository, ModuleVersionMetaData module, ModuleSource moduleSource) {
             this.repository = repository;
             this.module = module;
             this.moduleSource = moduleSource;
         }
 
-        public ModuleVersionIdentifier getId() throws ModuleVersionResolveException {
-            return module.getId();
-        }
-
-        public ModuleDescriptor getDescriptor() throws ModuleVersionResolveException {
-            return module.getDescriptor();
-        }
-
         public boolean isGeneratedModuleDescriptor() {
             return module.getDescriptor().isDefault();
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedParserSettings.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedParserSettings.java
new file mode 100644
index 0000000..67fc733
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/DisconnectedParserSettings.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
+
+import org.apache.ivy.core.RelativeUrlResolver;
+import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.status.StatusManager;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.RestrictedDependencyResolver;
+
+import java.io.File;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * An implementation of {@link ParserSettings} that is useful for parsing an ivy.xml file without attempting to download
+ * other resources from a DependencyResolver.
+ */
+public class DisconnectedParserSettings implements ParserSettings {
+    private final IvySettings ivySettings = new IvySettings();
+
+    /**
+     * This implementation will not attempt to download any parent modules.
+     * TODO:DAZ Work out how to do the actual download if we want to do more validation when publishing.
+     */
+    public DependencyResolver getResolver(final ModuleRevisionId mRevId) {
+        return new RestrictedDependencyResolver() {
+            @Override
+            public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
+                return new ResolvedModuleRevision(null, null, new DefaultModuleDescriptor(mRevId, "release", new Date()), null);
+            }
+        };
+    }
+
+    public String substitute(String value) {
+        return ivySettings.substitute(value);
+    }
+
+    public PatternMatcher getMatcher(String matcherName) {
+        return ivySettings.getMatcher(matcherName);
+    }
+
+    public StatusManager getStatusManager() {
+        return ivySettings.getStatusManager();
+    }
+
+    public ConflictManager getConflictManager(String name) {
+        return ivySettings.getConflictManager(name);
+    }
+
+    public Namespace getNamespace(String namespace) {
+        return ivySettings.getNamespace(namespace);
+    }
+
+    public Namespace getContextNamespace() {
+        return ivySettings.getContextNamespace();
+    }
+
+    // The reset of the methods are not used when parsing an ivy.xml
+    public Map substitute(Map strings) {
+        throw unsupported();
+    }
+
+    public ResolutionCacheManager getResolutionCacheManager() {
+        throw unsupported();
+    }
+
+    public RelativeUrlResolver getRelativeUrlResolver() {
+        throw unsupported();
+    }
+
+    public File resolveFile(String filename) {
+        throw unsupported();
+    }
+
+    public String getDefaultBranch(ModuleId moduleId) {
+        throw unsupported();
+    }
+
+    private UnsupportedOperationException unsupported() {
+        return new UnsupportedOperationException();
+    }
+
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
index fe8d415..f7b3517 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorBuilder.java
@@ -164,8 +164,6 @@ public class GradlePomModuleDescriptorBuilder {
 
     private ModuleRevisionId mrid;
 
-    private DefaultArtifact mainArtifact;
-
     private ParserSettings parserSettings;
 
     private static final String WRONG_NUMBER_OF_PARTS_MSG = "what seemed to be a dependency "
@@ -222,8 +220,7 @@ public class GradlePomModuleDescriptorBuilder {
                 DefaultArtifact artifact = new DefaultArtifact(mrid, new Date(), artifactId, "jar", "jar");
 
                 if (!ArtifactOrigin.isUnknown(resolver.locate(artifact))) {
-                    mainArtifact = artifact;
-                    ivyModuleDescriptor.addArtifact("master", mainArtifact);
+                    ivyModuleDescriptor.addArtifact("master", artifact);
                 }
             }
 
@@ -238,8 +235,7 @@ public class GradlePomModuleDescriptorBuilder {
                 DefaultArtifact artifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, packaging);
 
                 if (!ArtifactOrigin.isUnknown(resolver.locate(artifact))) {
-                    mainArtifact = artifact;
-                    ivyModuleDescriptor.addArtifact("master", mainArtifact);
+                    ivyModuleDescriptor.addArtifact("master", artifact);
 
                     DeprecationLogger.nagUserOfDeprecated("Relying on packaging to define the extension of the main artifact");
 
@@ -248,8 +244,7 @@ public class GradlePomModuleDescriptorBuilder {
             }
         }
 
-        mainArtifact = new DefaultArtifact(mrid, new Date(), artifactId, packaging, "jar");
-        ivyModuleDescriptor.addArtifact("master", mainArtifact);
+        ivyModuleDescriptor.addArtifact("master", new DefaultArtifact(mrid, new Date(), artifactId, packaging, "jar"));
     }
 
     private boolean isKnownJarPackaging(String packaging) {
@@ -302,8 +297,7 @@ public class GradlePomModuleDescriptorBuilder {
             if (dep.getClassifier() != null) {
                 extraAtt.put("m:classifier", dep.getClassifier());
             }
-            DefaultDependencyArtifactDescriptor depArtifact =
-                    new DefaultDependencyArtifactDescriptor(dd, dd.getDependencyId().getName(), type, ext, null, extraAtt);
+            DefaultDependencyArtifactDescriptor depArtifact = new DefaultDependencyArtifactDescriptor(dd, dd.getDependencyId().getName(), type, ext, null, extraAtt);
             // here we have to assume a type and ext for the artifact, so this is a limitation
             // compared to how m2 behave with classifiers
             String optionalizedScope = dep.isOptional() ? "optional" : scope;
@@ -549,8 +543,4 @@ public class GradlePomModuleDescriptorBuilder {
     public void addProperty(String propertyName, String value) {
         addExtraInfo(getPropertyExtraInfoKey(propertyName), value);
     }
-
-    public Artifact getMainArtifact() {
-        return mainArtifact;
-    }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
index cdfec37..e9209fd 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradlePomModuleDescriptorParser.java
@@ -32,11 +32,16 @@ import org.apache.ivy.plugins.repository.Resource;
 import org.apache.ivy.plugins.repository.url.URLResource;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.util.Message;
+import org.gradle.internal.UncheckedException;
 import org.xml.sax.SAXException;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.net.URL;
 import java.text.ParseException;
 import java.util.Date;
@@ -77,8 +82,9 @@ public final class GradlePomModuleDescriptorParser implements ModuleDescriptorPa
     }
 
     public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL,
-                                            Resource res, boolean validate) throws ParseException, IOException {
+                                            Resource resource, boolean validate) throws ParseException, IOException {
 
+        Resource res = encodedUrlResource(resource, descriptorURL);
         GradlePomModuleDescriptorBuilder mdBuilder = new GradlePomModuleDescriptorBuilder(this, res, ivySettings);
 
         try {
@@ -103,17 +109,14 @@ public final class GradlePomModuleDescriptorParser implements ModuleDescriptorPa
                         domReader.getParentGroupId(),
                         domReader.getParentArtifactId(),
                         domReader.getParentVersion());
-                ResolvedModuleRevision parentModule = parseOtherPom(ivySettings,
-                        parentModRevID);
+                ResolvedModuleRevision parentModule = parseOtherPom(ivySettings, parentModRevID);
                 if (parentModule != null) {
                     parentDescr = parentModule.getDescriptor();
                 } else {
-                    throw new IOException("Impossible to load parent for " + res.getName() + "."
-                            + " Parent=" + parentModRevID);
+                    throw new IOException("Impossible to load parent for " + res.getName() + ". Parent=" + parentModRevID);
                 }
                 if (parentDescr != null) {
-                    Map parentPomProps = GradlePomModuleDescriptorBuilder.extractPomProperties(
-                            parentDescr.getExtraInfo());
+                    Map parentPomProps = GradlePomModuleDescriptorBuilder.extractPomProperties(parentDescr.getExtraInfo());
                     for (Object o : parentPomProps.entrySet()) {
                         Map.Entry prop = (Map.Entry) o;
                         domReader.setProperty((String) prop.getKey(), (String) prop.getValue());
@@ -159,8 +162,7 @@ public final class GradlePomModuleDescriptorParser implements ModuleDescriptorPa
                             + " is relocated to " + relocation
                             + ". Please update your dependencies.");
                     Message.verbose("Relocated module will be considered as a dependency");
-                    DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(mdBuilder
-                            .getModuleDescriptor(), relocation, true, false, true);
+                    DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(mdBuilder.getModuleDescriptor(), relocation, true, false, true);
                     /* Map all public dependencies */
                     Configuration[] m2Confs = GradlePomModuleDescriptorBuilder.MAVEN2_CONFIGURATIONS;
                     for (Configuration m2Conf : m2Confs) {
@@ -191,8 +193,7 @@ public final class GradlePomModuleDescriptorParser implements ModuleDescriptorPa
                     }
 
                     // add plugins from parent
-                    List /*<PomDependencyMgt>*/ plugins =
-                            GradlePomModuleDescriptorBuilder.getPlugins(parentDescr);
+                    List /*<PomDependencyMgt>*/ plugins = GradlePomModuleDescriptorBuilder.getPlugins(parentDescr);
                     for (Object plugin : plugins) {
                         mdBuilder.addPlugin((PomDependencyMgt) plugin);
                     }
@@ -205,8 +206,7 @@ public final class GradlePomModuleDescriptorParser implements ModuleDescriptorPa
                                 dep.getGroupId(),
                                 dep.getArtifactId(),
                                 dep.getVersion());
-                        ResolvedModuleRevision importModule = parseOtherPom(ivySettings,
-                                importModRevID);
+                        ResolvedModuleRevision importModule = parseOtherPom(ivySettings, importModRevID);
                         if (importModule != null) {
                             ModuleDescriptor importDescr = importModule.getDescriptor();
 
@@ -267,8 +267,7 @@ public final class GradlePomModuleDescriptorParser implements ModuleDescriptorPa
             return null;
         } else {
             dd = NameSpaceHelper.toSystem(dd, ivySettings.getContextNamespace());
-            ResolvedModuleRevision otherModule = resolver.getDependency(dd, data);
-            return otherModule;
+            return resolver.getDependency(dd, data);
         }
     }
 
@@ -279,4 +278,19 @@ public final class GradlePomModuleDescriptorParser implements ModuleDescriptorPa
         return pe;
     }
 
+    private Resource encodedUrlResource(final Resource base, final URL url) {
+        Object proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{Resource.class}, new InvocationHandler() {
+            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                if ("getName".equals(method.getName())) {
+                    return url.toString();
+                }
+                try {
+                    return method.invoke(base, args);
+                } catch (InvocationTargetException e) {
+                    throw UncheckedException.throwAsUncheckedException(e.getTargetException());
+                }
+            }
+        });
+        return (Resource) proxy;
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
index 80e9fda..4476247 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParser.java
@@ -16,9 +16,9 @@
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser;
 
-import org.apache.ivy.Ivy;
 import org.apache.ivy.core.IvyContext;
-import org.apache.ivy.core.cache.ResolutionCacheManager;
+import org.apache.ivy.core.NormalRelativeUrlResolver;
+import org.apache.ivy.core.RelativeUrlResolver;
 import org.apache.ivy.core.module.descriptor.*;
 import org.apache.ivy.core.module.id.ArtifactId;
 import org.apache.ivy.core.module.id.ModuleId;
@@ -27,21 +27,21 @@ import org.apache.ivy.core.resolve.ResolveData;
 import org.apache.ivy.core.resolve.ResolveEngine;
 import org.apache.ivy.core.resolve.ResolveOptions;
 import org.apache.ivy.core.resolve.ResolvedModuleRevision;
-import org.apache.ivy.plugins.conflict.ConflictManager;
-import org.apache.ivy.plugins.conflict.FixedConflictManager;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
 import org.apache.ivy.plugins.namespace.NameSpaceHelper;
 import org.apache.ivy.plugins.namespace.Namespace;
 import org.apache.ivy.plugins.parser.AbstractModuleDescriptorParser;
 import org.apache.ivy.plugins.parser.ModuleDescriptorParser;
-import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry;
 import org.apache.ivy.plugins.parser.ParserSettings;
 import org.apache.ivy.plugins.repository.Resource;
 import org.apache.ivy.plugins.repository.url.URLResource;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.apache.ivy.util.Message;
 import org.apache.ivy.util.XMLHelper;
+import org.apache.ivy.util.extendable.DefaultExtendableItem;
 import org.apache.ivy.util.extendable.ExtendableItemHelper;
+import org.gradle.util.DeprecationLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 
@@ -63,54 +63,23 @@ import java.util.Map;
  * it thread-safe.
  */
 public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser {
-    static final String[] DEPENDENCY_REGULAR_ATTRIBUTES = new String[] {"org", "name", "branch",
-            "branchConstraint", "rev", "revConstraint", "force", "transitive", "changing", "conf"};
+    static final String[] DEPENDENCY_REGULAR_ATTRIBUTES =
+            new String[] {"org", "name", "branch", "branchConstraint", "rev", "revConstraint", "force", "transitive", "changing", "conf"};
 
     public static final String IVY_DATE_FORMAT_PATTERN = "yyyyMMddHHmmss";
 
-    /**
-     * @param xmlURL
-     *            the url pointing to the file to parse
-     * @param res
-     *            the real resource to parse, used for log only
-     * @param validate
-     * @return
-     * @throws java.text.ParseException
-     * @throws java.io.IOException
-     */
-    public DefaultModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res,
-            boolean validate) throws ParseException, IOException {
-        Parser parser = newParser(ivySettings);
+    private static final Logger LOGGER = LoggerFactory.getLogger(IvyXmlModuleDescriptorParser.class);
+
+    public DefaultModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL xmlURL, Resource res, boolean validate) throws ParseException, IOException {
+        Parser parser = new Parser(this, ivySettings, res);
         parser.setValidate(validate);
-        parser.setResource(res);
         parser.setInput(xmlURL);
         parser.parse();
         return (DefaultModuleDescriptor) parser.getModuleDescriptor();
     }
 
-    /** Used for test purpose */
-    ModuleDescriptor parseDescriptor(ParserSettings ivySettings, InputStream descriptor,
-            Resource res, boolean validate) throws ParseException, IOException {
-        Parser parser = newParser(ivySettings);
-        parser.setValidate(validate);
-        parser.setResource(res);
-        parser.setInput(descriptor);
-        parser.parse();
-        return parser.getModuleDescriptor();
-    }
-
-    /**
-     * Instantiates a Parser instance responsible for actual parsing of Ivy files.
-     * <p>
-     * Override this method if you want to use a custom Parser.
-     * </p>
-     *
-     * @param ivySettings
-     *            the settings to use during parsing
-     * @return the Parser instance used for parsing Ivy files
-     */
-    protected Parser newParser(ParserSettings ivySettings) {
-        return new Parser(this, ivySettings);
+    public ModuleDescriptor parseDescriptor(ParserSettings ivySettings, URL descriptorURL, boolean validate) throws ParseException, IOException {
+        return parseDescriptor(ivySettings, descriptorURL, new URLResource(descriptorURL), validate);
     }
 
     public boolean accept(Resource res) {
@@ -154,11 +123,11 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             }
         }
 
-        protected static final List ALLOWED_VERSIONS = Arrays.asList(
-                new String[]{"1.0", "1.1", "1.2", "1.3", "1.4", "2.0", "2.1", "2.2"});
+        private static final List ALLOWED_VERSIONS = Arrays.asList("1.0", "1.1", "1.2", "1.3", "1.4", "2.0", "2.1", "2.2");
 
         /* how and what do we have to parse */
-        private ParserSettings settings;
+        private ParserSettings parserSettings;
+        private final RelativeUrlResolver relativeUrlResolver = new NormalRelativeUrlResolver();
         private boolean validate = true;
         private URL descriptorURL;
         private InputStream descriptorInput;
@@ -176,9 +145,16 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
         private String descriptorVersion;
         private String[] publicationsDefaultConf;
 
-        public Parser(ModuleDescriptorParser parser, ParserSettings ivySettings) {
-            super(parser);
-            settings = ivySettings;
+        public Parser(ModuleDescriptorParser moduleDescriptorParser, ParserSettings ivySettings, Resource res) {
+            super(moduleDescriptorParser);
+            parserSettings = ivySettings;
+            setResource(res);
+        }
+        
+        public Parser newParser(Resource res) {
+            Parser parser = new Parser(getModuleDescriptorParser(), parserSettings, res);
+            parser.setValidate(validate);
+            return parser;
         }
 
         public void setInput(InputStream descriptorInput) {
@@ -204,21 +180,16 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 }
                 checkConfigurations();
                 replaceConfigurationWildcards();
-                getMd().setModuleArtifact(
-                    DefaultArtifact.newIvyArtifact(
-                        getMd().getResolvedModuleRevisionId(), getMd().getPublicationDate()));
+                getMd().setModuleArtifact(DefaultArtifact.newIvyArtifact(getMd().getResolvedModuleRevisionId(), getMd().getPublicationDate()));
                 if (!artifactsDeclared) {
-                    String[] confs = getMd().getConfigurationsNames();
-                    for (int i = 0; i < confs.length; i++) {
-                        getMd().addArtifact(confs[i],
-                            new MDArtifact(getMd(), getMd().getModuleRevisionId().getName(),
-                                "jar", "jar"));
+                    String[] configurationNames = getMd().getConfigurationsNames();
+                    for (String configurationName : configurationNames) {
+                        getMd().addArtifact(configurationName, new MDArtifact(getMd(), getMd().getModuleRevisionId().getName(), "jar", "jar"));
                     }
                 }
                 getMd().check();
             } catch (ParserConfigurationException ex) {
-                IllegalStateException ise = new IllegalStateException(ex.getMessage() + " in "
-                        + descriptorURL);
+                IllegalStateException ise = new IllegalStateException(ex.getMessage() + " in " + descriptorURL);
                 ise.initCause(ex);
                 throw ise;
             } catch (Exception ex) {
@@ -234,16 +205,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             try {
                 if (state == State.DESCRIPTION) {
                     // make sure we don't interpret any tag while in description tag
-                    getBuffer().append("<").append(qName);
-                    for (int i = 0; i < attributes.getLength(); i++) {
-                        getBuffer().append(" ");
-                        getBuffer().append(attributes.getQName(i));
-                        getBuffer().append("=\"");
-                        getBuffer().append(attributes.getValue(i));
-                        getBuffer().append("\"");
-                    }
-                    getBuffer().append(">");
-                    return;
+                    descriptionStarted(qName, attributes);
                 } else if ("ivy-module".equals(qName)) {
                     ivyModuleStarted(attributes);
                 } else if ("info".equals(qName)) {
@@ -251,10 +213,9 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 } else if (state == State.INFO && "extends".equals(qName)) {
                     extendsStarted(attributes);
                 } else if (state == State.INFO && "license".equals(qName)) {
-                    getMd().addLicense(new License(settings.substitute(attributes.getValue("name")),
-                                        settings.substitute(attributes.getValue("url"))));
+                    getMd().addLicense(new License(substitute(attributes.getValue("name")), substitute(attributes.getValue("url"))));
                 } else if (state == State.INFO && "description".equals(qName)) {
-                    getMd().setHomePage(settings.substitute(attributes.getValue("homepage")));
+                    getMd().setHomePage(substitute(attributes.getValue("homepage")));
                     state = State.DESCRIPTION;
                     buffer = new StringBuffer();
                 } else if (state == State.INFO && isOtherNamespace(qName)) {
@@ -268,8 +229,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                     dependenciesStarted(attributes);
                 } else if ("conflicts".equals(qName)) {
                     if (!descriptorVersion.startsWith("1.")) {
-                        Message.deprecated("using conflicts section is deprecated: "
-                                + "please use hints section instead. Ivy file URL: " + descriptorURL);
+                        DeprecationLogger.nagUserWith("Using conflicts section in ivy.xml is deprecated: please use hints section instead. Ivy file URL: " + descriptorURL);
                     }
                     state = State.CONFLICT;
                     checkConfigurations();
@@ -288,13 +248,11 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 } else if ("conf".equals(qName)) {
                     confStarted(attributes);
                 } else if ("mapped".equals(qName)) {
-                    dd.addDependencyConfiguration(conf, settings.substitute(attributes
-                            .getValue("name")));
-                } else if (("conflict".equals(qName) && state == State.DEPS)
-                        || "manager".equals(qName) && state == State.CONFLICT) {
-                    managerStarted(attributes, state == State.CONFLICT ? "name" : "manager");
+                    dd.addDependencyConfiguration(conf, substitute(attributes.getValue("name")));
+                } else if (("conflict".equals(qName) && state == State.DEPS) || "manager".equals(qName) && state == State.CONFLICT) {
+                    LOGGER.debug("Ivy.xml conflict managers are not supported by Gradle. Ignoring conflict manager declared in {}", getResource().getName());
                 } else if ("override".equals(qName) && state == State.DEPS) {
-                    mediationOverrideStarted(attributes);
+                    LOGGER.debug("Ivy.xml dependency overrides are not supported by Gradle. Ignoring override declared in {}", getResource().getName());
                 } else if ("include".equals(qName) && state == State.CONF) {
                     includeConfStarted(attributes);
                 } else if (validate && state != State.EXTRA_INFO && state != State.DESCRIPTION) {
@@ -311,99 +269,65 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             }
         }
 
-        protected String getDefaultParentLocation() {
-            return "../ivy.xml";
-        }
-
-        protected void extendsStarted(Attributes attributes) throws ParseException {
+        private void extendsStarted(Attributes attributes) throws ParseException {
             String parentOrganisation = attributes.getValue("organisation");
             String parentModule = attributes.getValue("module");
             String parentRevision = attributes.getValue("revision");
-            String location = attributes.getValue("location") != null ? attributes
-                    .getValue("location") : getDefaultParentLocation();
-            ModuleDescriptor parent = null;
+            String location = elvis(attributes.getValue("location"), "../ivy.xml");
 
-            String extendType = attributes.getValue("extendType") != null ? attributes.getValue(
-                "extendType").toLowerCase() : "all";
-
-            List/* <String> */extendTypes = Arrays.asList(extendType.split(","));
+            String extendType = elvis(attributes.getValue("extendType"), "all").toLowerCase();
+            List<String> extendTypes = Arrays.asList(extendType.split(","));
 
+            ModuleDescriptor parent = null;
             try {
-                Message.debug("Trying to parse included ivy file :" + location);
+                LOGGER.debug("Trying to parse included ivy file :" + location);
                 parent = parseOtherIvyFileOnFileSystem(location);
 
                 //verify that the parsed descriptor is the correct parent module.
                 ModuleId expected = new ModuleId(parentOrganisation, parentModule);
                 ModuleId pid = parent.getModuleRevisionId().getModuleId();
                 if (!expected.equals(pid)) {
-                    Message.verbose("Ignoring parent Ivy file " + location + "; expected "
-                        + expected + " but found " + pid);
+                    LOGGER.warn("Ignoring parent Ivy file " + location + "; expected " + expected + " but found " + pid);
                     parent = null;
                 }
 
             } catch (ParseException e) {
-                Message.warn("Unable to parse included ivy file " + location + ": "
-                    + e.getMessage());
+                LOGGER.debug("Unable to parse included ivy file " + location + ": " + e.getMessage());
             } catch (IOException e) {
-                Message.warn("Unable to parse included ivy file " + location + ": "
-                    + e.getMessage());
+                LOGGER.debug("Unable to parse included ivy file " + location + ": " + e.getMessage());
             }
 
             // if the included ivy file is not found on file system, tries to resolve using
             // repositories
             if (parent == null) {
                 try {
-                    Message.debug(
-                        "Trying to parse included ivy file by asking repository for module :"
-                                    + parentOrganisation
-                                    + "#"
-                                    + parentModule
-                                    + ";"
-                                    + parentRevision);
+                    LOGGER.debug("Trying to parse included ivy file by asking repository for module :"
+                                            + parentOrganisation
+                                            + "#"
+                                            + parentModule
+                                            + ";"
+                                            + parentRevision);
                     parent = parseOtherIvyFile(parentOrganisation, parentModule, parentRevision);
                 } catch (ParseException e) {
-                    Message.warn("Unable to parse included ivy file for " + parentOrganisation
-                            + "#" + parentModule + ";" + parentRevision);
+                    LOGGER.warn("Unable to parse included ivy file for " + parentOrganisation + "#" + parentModule + ";" + parentRevision);
                 }
             }
 
             if (parent == null) {
-                throw new ParseException("Unable to parse included ivy file for "
-                        + parentOrganisation + "#" + parentModule + ";" + parentRevision, 0);
-            }
-
-            ResolutionCacheManager cacheManager = settings.getResolutionCacheManager();
-
-            File ivyFileInCache = cacheManager.getResolvedIvyFileInCache(parent
-                    .getResolvedModuleRevisionId());
-            //Generate the parent cache file if necessary
-            if (parent.getResource() != null
-                    && !parent.getResource().getName().equals(ivyFileInCache.toURI().toString())) {
-                try {
-                    parent.toIvyFile(ivyFileInCache);
-                } catch (ParseException e) {
-                    throw new ParseException("Unable to create cache file for "
-                            + parentOrganisation + "#" + parentModule + ";" + parentRevision
-                            + " Reason:" + e.getLocalizedMessage(), 0);
-                } catch (IOException e) {
-                    throw new ParseException("Unable to create cache file for "
-                            + parentOrganisation + "#" + parentModule + ";" + parentRevision
-                            + " Reason :" + e.getLocalizedMessage(), 0);
-                }
+                throw new ParseException("Unable to parse included ivy file for " + parentOrganisation + "#" + parentModule + ";" + parentRevision, 0);
             }
 
             DefaultExtendsDescriptor ed = new DefaultExtendsDescriptor(
                     parent.getModuleRevisionId(),
                     parent.getResolvedModuleRevisionId(),
                     attributes.getValue("location"),
-                    (String[]) extendTypes.toArray(new String[extendTypes.size()]));
+                    extendTypes.toArray(new String[extendTypes.size()]));
             getMd().addInheritedDescriptor(ed);
 
             mergeWithOtherModuleDescriptor(extendTypes, parent);
         }
 
-        protected void mergeWithOtherModuleDescriptor(List/* <String> */extendTypes,
-                ModuleDescriptor parent) {
+        private void mergeWithOtherModuleDescriptor(List<String> extendTypes, ModuleDescriptor parent) {
 
             if (extendTypes.contains("all")) {
                 mergeAll(parent);
@@ -427,7 +351,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
 
         }
 
-        protected void mergeAll(ModuleDescriptor parent) {
+        private void mergeAll(ModuleDescriptor parent) {
             ModuleRevisionId sourceMrid = parent.getModuleRevisionId();
             mergeInfo(parent);
             mergeConfigurations(sourceMrid, parent.getConfigurations());
@@ -435,7 +359,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             mergeDescription(parent.getDescription());
         }
 
-        protected void mergeInfo(ModuleDescriptor parent) {
+        private void mergeInfo(ModuleDescriptor parent) {
             ModuleRevisionId parentMrid = parent.getModuleRevisionId();
 
             DefaultModuleDescriptor descriptor = getMd();
@@ -446,8 +370,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 currentMrid.getName(),
                 mergeValue(parentMrid.getBranch(), currentMrid.getBranch()),
                 mergeValue(parentMrid.getRevision(), currentMrid.getRevision()),
-                mergeValues(parentMrid.getQualifiedExtraAttributes(),
-                            currentMrid.getQualifiedExtraAttributes())
+                mergeValues(parentMrid.getQualifiedExtraAttributes(), currentMrid.getQualifiedExtraAttributes())
             );
 
             descriptor.setModuleRevisionId(mergedMrid);
@@ -471,68 +394,44 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             return dup;
         }
 
-        protected void mergeConfigurations(ModuleRevisionId sourceMrid, Configuration[] configurations) {
+        private void mergeConfigurations(ModuleRevisionId sourceMrid, Configuration[] configurations) {
             DefaultModuleDescriptor md = getMd();
-            for (int i = 0; i < configurations.length; i++) {
-                Configuration configuration = configurations[i];
-                Message.debug("Merging configuration with: " + configuration.getName());
+            for (Configuration configuration : configurations) {
+                LOGGER.debug("Merging configuration with: " + configuration.getName());
                 //copy configuration from parent descriptor
                 md.addConfiguration(new Configuration(configuration, sourceMrid));
             }
         }
 
-        protected void mergeDependencies(DependencyDescriptor[] dependencies) {
+        private void mergeDependencies(DependencyDescriptor[] dependencies) {
             DefaultModuleDescriptor md = getMd();
-            for (int i = 0; i < dependencies.length; i++) {
-                DependencyDescriptor dependencyDescriptor = dependencies[i];
-                Message.debug("Merging dependency with: "
-                        + dependencyDescriptor.getDependencyRevisionId().toString());
+            for (DependencyDescriptor dependencyDescriptor : dependencies) {
+                LOGGER.debug("Merging dependency with: " + dependencyDescriptor.getDependencyRevisionId().toString());
                 md.addDependency(dependencyDescriptor);
             }
         }
 
-        protected void mergeDescription(String description) {
+        private void mergeDescription(String description) {
             String current = getMd().getDescription();
             if (current == null || current.trim().length() == 0) {
                 getMd().setDescription(description);
             }
         }
 
-        protected ModuleDescriptor parseOtherIvyFileOnFileSystem(String location)
+        private ModuleDescriptor parseOtherIvyFileOnFileSystem(String location)
                 throws ParseException, IOException {
-            URL url = null;
-            ModuleDescriptor parent = null;
-            url = getSettings().getRelativeUrlResolver().getURL(descriptorURL, location);
-            Message.debug("Trying to load included ivy file from " + url.toString());
-            URLResource res = new URLResource(url);
-            ModuleDescriptorParser parser = ModuleDescriptorParserRegistry.getInstance().getParser(
-                res);
-
-            parent = parser.parseDescriptor(getSettings(), url, isValidate());
-            return parent;
+            URL url = relativeUrlResolver.getURL(descriptorURL, location);
+            LOGGER.debug("Trying to load included ivy file from " + url.toString());
+            Parser parser = newParser(new URLResource(url));
+            parser.parse();
+            return parser.getModuleDescriptor();
         }
 
-        protected ModuleDescriptor parseOtherIvyFile(String parentOrganisation,
+        private ModuleDescriptor parseOtherIvyFile(String parentOrganisation,
                 String parentModule, String parentRevision) throws ParseException {
             ModuleId parentModuleId = new ModuleId(parentOrganisation, parentModule);
             ModuleRevisionId parentMrid = new ModuleRevisionId(parentModuleId, parentRevision);
 
-            // try to load parent module in cache
-            File cacheFile = settings.getResolutionCacheManager().getResolvedIvyFileInCache(
-                ModuleRevisionId.newInstance(parentMrid, Ivy.getWorkingRevision()));
-            if (cacheFile.exists() && cacheFile.length() > 0) {
-                ModuleDescriptor md;
-                try {
-                    Message.debug("Trying to load included ivy file from cache");
-                    URL parentUrl = cacheFile.toURI().toURL();
-                    md = parseOtherIvyFileOnFileSystem(parentUrl.toString());
-                    return md;
-                } catch (IOException e) {
-                    // do nothing
-                    Message.error(e.getLocalizedMessage());
-                }
-            }
-
             DependencyDescriptor dd = new DefaultDependencyDescriptor(parentMrid, true);
             ResolveData data = IvyContext.getContext().getResolveData();
             if (data == null) {
@@ -542,159 +441,87 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 data = new ResolveData(engine, options);
             }
 
-            DependencyResolver resolver = getSettings().getResolver(parentMrid);
+            DependencyResolver resolver = parserSettings.getResolver(parentMrid);
             if (resolver == null) {
                 // TODO: Throw exception here?
                 return null;
             } else {
-                dd = NameSpaceHelper.toSystem(dd, getSettings().getContextNamespace());
+                dd = NameSpaceHelper.toSystem(dd, parserSettings.getContextNamespace());
                 ResolvedModuleRevision otherModule = resolver.getDependency(dd, data);
                 if (otherModule == null) {
                     throw new ParseException("Unable to find " + parentMrid.toString(), 0);
                 }
                 return otherModule.getDescriptor();
             }
-
         }
 
-        protected void publicationsStarted(Attributes attributes) {
+        private void publicationsStarted(Attributes attributes) {
             state = State.PUB;
             artifactsDeclared = true;
             checkConfigurations();
-            String defaultConf = settings.substitute(attributes.getValue("defaultconf"));
+            String defaultConf = substitute(attributes.getValue("defaultconf"));
             if (defaultConf != null) {
-                setPublicationsDefaultConf(defaultConf);
+                this.publicationsDefaultConf = defaultConf.split(",");
             }
         }
 
-        protected void setPublicationsDefaultConf(String defaultConf) {
-            this.publicationsDefaultConf = defaultConf == null ? null : defaultConf.split(",");
-        }
-
-        protected boolean isOtherNamespace(String qName) {
+        private boolean isOtherNamespace(String qName) {
             return qName.indexOf(':') != -1;
         }
 
-        protected void managerStarted(Attributes attributes, String managerAtt) {
-            String org = settings.substitute(attributes.getValue("org"));
-            org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
-            String mod = settings.substitute(attributes.getValue("module"));
-            mod = mod == null ? PatternMatcher.ANY_EXPRESSION : mod;
-            ConflictManager cm;
-            String name = settings.substitute(attributes.getValue(managerAtt));
-            String rev = settings.substitute(attributes.getValue("rev"));
-            if (rev != null) {
-                String[] revs = rev.split(",");
-                for (int i = 0; i < revs.length; i++) {
-                    revs[i] = revs[i].trim();
-                }
-                cm = new FixedConflictManager(revs);
-            } else if (name != null) {
-                cm = settings.getConflictManager(name);
-                if (cm == null) {
-                    addError("unknown conflict manager: " + name);
-                    return;
-                }
-            } else {
-                addError("bad conflict manager: no manager nor rev");
-                return;
-            }
-            String matcherName = settings.substitute(attributes.getValue("matcher"));
-            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
-                    .getMatcher(matcherName);
-            if (matcher == null) {
-                addError("unknown matcher: " + matcherName);
-                return;
-            }
-            getMd().addConflictManager(new ModuleId(org, mod), matcher, cm);
-        }
-
-        protected void mediationOverrideStarted(Attributes attributes) {
-            String org = settings.substitute(attributes.getValue("org"));
-            org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
-            String mod = settings.substitute(attributes.getValue("module"));
-            mod = mod == null ? PatternMatcher.ANY_EXPRESSION : mod;
-            String rev = settings.substitute(attributes.getValue("rev"));
-            String branch = settings.substitute(attributes.getValue("branch"));
-            String matcherName = settings.substitute(attributes.getValue("matcher"));
-            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
-                    .getMatcher(matcherName);
-            if (matcher == null) {
-                addError("unknown matcher: " + matcherName);
-                return;
-            }
-            getMd().addDependencyDescriptorMediator(
-                new ModuleId(org, mod), matcher,
-                new OverrideDependencyDescriptorMediator(branch, rev));
-        }
-
-        protected void includeConfStarted(Attributes attributes)
+        private void includeConfStarted(Attributes attributes)
                 throws SAXException, IOException, ParserConfigurationException, ParseException {
-            URL url = settings.getRelativeUrlResolver().getURL(descriptorURL,
-                    settings.substitute(attributes.getValue("file")),
-                    settings.substitute(attributes.getValue("url")));
-
+            URL url = relativeUrlResolver.getURL(descriptorURL, substitute(attributes.getValue("file")), substitute(attributes.getValue("url")));
             if (url == null) {
                 throw new SAXException("include tag must have a file or an url attribute");
             }
 
             // create a new temporary parser to read the configurations from
             // the specified file.
-            Parser parser = new Parser(getModuleDescriptorParser(), settings);
+            Parser parser = newParser(new URLResource(url));
             parser.setInput(url);
-            parser.setMd(new DefaultModuleDescriptor(getModuleDescriptorParser(),
-                    new URLResource(url)));
             XMLHelper.parse(url , null, parser);
 
             // add the configurations from this temporary parser to this module descriptor
             Configuration[] configs = parser.getModuleDescriptor().getConfigurations();
-            for (int i = 0; i < configs.length; i++) {
-                getMd().addConfiguration(configs[i]);
+            for (Configuration config : configs) {
+                getMd().addConfiguration(config);
             }
             if (parser.getDefaultConfMapping() != null) {
-                Message.debug("setting default conf mapping from imported configurations file: "
-                        + parser.getDefaultConfMapping());
+                LOGGER.debug("setting default conf mapping from imported configurations file: " + parser.getDefaultConfMapping());
                 setDefaultConfMapping(parser.getDefaultConfMapping());
             }
             if (parser.getDefaultConf() != null) {
-                Message.debug("setting default conf from imported configurations file: "
-                        + parser.getDefaultConf());
+                LOGGER.debug("setting default conf from imported configurations file: " + parser.getDefaultConf());
                 setDefaultConf(parser.getDefaultConf());
             }
             if (parser.getMd().isMappingOverride()) {
-                Message.debug("enabling mapping-override from imported configurations"
-                        + " file");
+                LOGGER.debug("enabling mapping-override from imported configurations file");
                 getMd().setMappingOverride(true);
             }
         }
 
-        protected void confStarted(Attributes attributes) {
-            String conf = settings.substitute(attributes.getValue("name"));
+        private void confStarted(Attributes attributes) {
+            String conf = substitute(attributes.getValue("name"));
             switch (state) {
                 case State.CONF:
-                    String visibility = settings.substitute(attributes.getValue("visibility"));
-                    String ext = settings.substitute(attributes.getValue("extends"));
+                    Configuration.Visibility visibility = Configuration.Visibility.getVisibility(elvis(substitute(attributes.getValue("visibility")), "public"));
+                    String description = substitute(attributes.getValue("description"));
+                    String[] extend = substitute(attributes.getValue("extends")) == null ? null : substitute(attributes.getValue("extends")).split(",");
                     String transitiveValue = attributes.getValue("transitive");
-                    boolean transitive = (transitiveValue == null) ? true : Boolean
-                            .valueOf(attributes.getValue("transitive")).booleanValue();
+                    boolean transitive = (transitiveValue == null) || Boolean.valueOf(attributes.getValue("transitive"));
                     String deprecated = attributes.getValue("deprecated");
-                    Configuration configuration = new Configuration(conf,
-                            Configuration.Visibility
-                                    .getVisibility(visibility == null ? "public"
-                                            : visibility), settings.substitute(attributes
-                                    .getValue("description")), ext == null ? null : ext
-                                    .split(","), transitive, deprecated);
-                    ExtendableItemHelper.fillExtraAttributes(settings, configuration, attributes,
-                            new String[]{"name", "visibility", "extends", "transitive",
-                                    "description", "deprecated"});
+                    Configuration configuration = new Configuration(conf, visibility, description, extend, transitive, deprecated);
+                    fillExtraAttributes(configuration, attributes,
+                            new String[]{"name", "visibility", "extends", "transitive", "description", "deprecated"});
                     getMd().addConfiguration(configuration);
                     break;
                 case State.PUB:
                     if ("*".equals(conf)) {
                         String[] confs = getMd().getConfigurationsNames();
-                        for (int i = 0; i < confs.length; i++) {
-                            artifact.addConfiguration(confs[i]);
-                            getMd().addArtifact(confs[i], artifact);
+                        for (String confName : confs) {
+                            artifact.addConfiguration(confName);
+                            getMd().addArtifact(confName, artifact);
                         }
                     } else {
                         artifact.addConfiguration(conf);
@@ -703,11 +530,11 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                     break;
                 case State.DEP:
                     this.conf = conf;
-                    String mappeds = settings.substitute(attributes.getValue("mapped"));
+                    String mappeds = substitute(attributes.getValue("mapped"));
                     if (mappeds != null) {
                         String[] mapped = mappeds.split(",");
-                        for (int i = 0; i < mapped.length; i++) {
-                            dd.addDependencyConfiguration(conf, mapped[i].trim());
+                        for (String depConf : mapped) {
+                            dd.addDependencyConfiguration(conf, depConf.trim());
                         }
                     }
                     break;
@@ -724,83 +551,64 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             }
         }
 
-        protected void dependencyStarted(Attributes attributes) {
+        private void dependencyStarted(Attributes attributes) {
             state = State.DEP;
-            String org = settings.substitute(attributes.getValue("org"));
+            String org = substitute(attributes.getValue("org"));
             if (org == null) {
                 org = getMd().getModuleRevisionId().getOrganisation();
             }
-            boolean force = Boolean.valueOf(settings.substitute(attributes.getValue("force")))
-                    .booleanValue();
-            boolean changing = Boolean.valueOf(
-                settings.substitute(attributes.getValue("changing"))).booleanValue();
-
-            String transitiveValue = settings.substitute(attributes.getValue("transitive"));
-            boolean transitive = (transitiveValue == null) ? true : Boolean.valueOf(
-                attributes.getValue("transitive")).booleanValue();
-
-            String name = settings.substitute(attributes.getValue("name"));
-            String branch = settings.substitute(attributes.getValue("branch"));
-            String branchConstraint = settings.substitute(attributes.getValue("branchConstraint"));
-
-//            if (branchConstraint == null) {
-//                // there was no branch constraint before, so we should
-//                // set the branchConstraint to the current default branch
-//                branchConstraint = settings.getDefaultBranch(ModuleId.newInstance(org, name));
-//            }
-
-            String rev = settings.substitute(attributes.getValue("rev"));
-            String revConstraint = settings.substitute(attributes.getValue("revConstraint"));
-
-            Map extraAttributes = ExtendableItemHelper.getExtraAttributes(
-                settings, attributes, DEPENDENCY_REGULAR_ATTRIBUTES);
-
-            ModuleRevisionId revId = ModuleRevisionId.newInstance(org, name, branch, rev,
-                extraAttributes);
-            ModuleRevisionId dynamicId = null;
+            boolean force = Boolean.valueOf(substitute(attributes.getValue("force")));
+            boolean changing = Boolean.valueOf(substitute(attributes.getValue("changing")));
+
+            String transitiveValue = substitute(attributes.getValue("transitive"));
+            boolean transitive = (transitiveValue == null) ? true : Boolean.valueOf(transitiveValue);
+
+            String name = substitute(attributes.getValue("name"));
+            String branch = substitute(attributes.getValue("branch"));
+            String branchConstraint = substitute(attributes.getValue("branchConstraint"));
+            String rev = substitute(attributes.getValue("rev"));
+            String revConstraint = substitute(attributes.getValue("revConstraint"));
+
+            String[] ignoredAttributeNames = DEPENDENCY_REGULAR_ATTRIBUTES;
+            Map extraAttributes = getExtraAttributes(attributes, ignoredAttributeNames);
+
+            ModuleRevisionId revId = ModuleRevisionId.newInstance(org, name, branch, rev, extraAttributes);
+            ModuleRevisionId dynamicId;
             if ((revConstraint == null) && (branchConstraint == null)) {
                 // no dynamic constraints defined, so dynamicId equals revId
-                dynamicId = ModuleRevisionId.newInstance(org, name, branch, rev,
-                                extraAttributes, false);
+                dynamicId = ModuleRevisionId.newInstance(org, name, branch, rev, extraAttributes, false);
             } else {
                 if (branchConstraint == null) {
                     // this situation occurs when there was no branch defined
                     // in the original dependency descriptor. So the dynamicId
                     // shouldn't contain a branch neither
-                    dynamicId = ModuleRevisionId.newInstance(org, name, null, revConstraint,
-                                    extraAttributes, false);
+                    dynamicId = ModuleRevisionId.newInstance(org, name, null, revConstraint, extraAttributes, false);
                 } else {
-                    dynamicId = ModuleRevisionId.newInstance(org, name, branchConstraint,
-                                    revConstraint, extraAttributes);
+                    dynamicId = ModuleRevisionId.newInstance(org, name, branchConstraint, revConstraint, extraAttributes);
                 }
             }
 
             dd = new DefaultDependencyDescriptor(getMd(), revId, dynamicId, force, changing, transitive);
             getMd().addDependency(dd);
-            String confs = settings.substitute(attributes.getValue("conf"));
+            String confs = substitute(attributes.getValue("conf"));
             if (confs != null && confs.length() > 0) {
                 parseDepsConfs(confs, dd);
             }
         }
 
-        protected void artifactStarted(String qName, Attributes attributes)
+        private void artifactStarted(String qName, Attributes attributes)
                 throws MalformedURLException {
             if (state == State.PUB) {
                 // this is a published artifact
-                String artName = settings.substitute(attributes.getValue("name"));
-                artName = artName == null ? getMd().getModuleRevisionId().getName() : artName;
-                String type = settings.substitute(attributes.getValue("type"));
-                type = type == null ? "jar" : type;
-                String ext = settings.substitute(attributes.getValue("ext"));
-                ext = ext != null ? ext : type;
-                String url = settings.substitute(attributes.getValue("url"));
-                artifact = new MDArtifact(getMd(), artName, type, ext, url == null ? null
-                        : new URL(url), ExtendableItemHelper.getExtraAttributes(
-                            settings, attributes, new String[] {"ext", "type", "name", "conf"}));
-                String confs = settings.substitute(attributes.getValue("conf"));
-                // only add confs if they are specified. if they aren't, endElement will
-                // handle this
-                // only if there are no conf defined in sub elements
+                String artName = elvis(substitute(attributes.getValue("name")), getMd().getModuleRevisionId().getName());
+                String type = elvis(substitute(attributes.getValue("type")), "jar");
+                String ext = elvis(substitute(attributes.getValue("ext")), type);
+                String url = substitute(attributes.getValue("url"));
+                Map extraAttributes = getExtraAttributes(attributes, new String[]{"ext", "type", "name", "conf"});
+                artifact = new MDArtifact(getMd(), artName, type, ext, url == null ? null : new URL(url), extraAttributes);
+                String confs = substitute(attributes.getValue("conf"));
+                
+                // Only add confs if they are specified. if they aren't, endElement will handle this only if there are no conf defined in sub elements
                 if (confs != null && confs.length() > 0) {
                     String[] conf;
                     if ("*".equals(confs)) {
@@ -808,9 +616,9 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                     } else {
                         conf = confs.split(",");
                     }
-                    for (int i = 0; i < conf.length; i++) {
-                        artifact.addConfiguration(conf[i].trim());
-                        getMd().addArtifact(conf[i].trim(), artifact);
+                    for (String confName : conf) {
+                        artifact.addConfiguration(confName.trim());
+                        getMd().addArtifact(confName.trim(), artifact);
                     }
                 }
             } else if (state == State.DEP) {
@@ -821,64 +629,52 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             }
         }
 
-        protected void dependenciesStarted(Attributes attributes) {
+        private void dependenciesStarted(Attributes attributes) {
             state = State.DEPS;
-            String defaultConf = settings.substitute(attributes.getValue("defaultconf"));
+            String defaultConf = substitute(attributes.getValue("defaultconf"));
             if (defaultConf != null) {
                 setDefaultConf(defaultConf);
             }
-            defaultConf = settings.substitute(attributes.getValue("defaultconfmapping"));
+            String defaultConfMapping = substitute(attributes.getValue("defaultconfmapping"));
             if (defaultConf != null) {
-                setDefaultConfMapping(defaultConf);
+                setDefaultConfMapping(defaultConfMapping);
             }
-            String confMappingOverride = settings.substitute(attributes
-                    .getValue("confmappingoverride"));
+            String confMappingOverride = substitute(attributes.getValue("confmappingoverride"));
             if (confMappingOverride != null) {
-                getMd().setMappingOverride(Boolean.valueOf(confMappingOverride).booleanValue());
+                getMd().setMappingOverride(Boolean.valueOf(confMappingOverride));
             }
             checkConfigurations();
         }
 
-        protected void configurationStarted(Attributes attributes) {
+        private void configurationStarted(Attributes attributes) {
             state = State.CONF;
-            setDefaultConfMapping(settings
-                    .substitute(attributes.getValue("defaultconfmapping")));
-            setDefaultConf(settings.substitute(attributes.getValue("defaultconf")));
-            getMd()
-                    .setMappingOverride(Boolean.valueOf(
-                        settings.substitute(attributes.getValue("confmappingoverride")))
-                            .booleanValue());
+            setDefaultConfMapping(substitute(attributes.getValue("defaultconfmapping")));
+            setDefaultConf(substitute(attributes.getValue("defaultconf")));
+            getMd().setMappingOverride(Boolean.valueOf(substitute(attributes.getValue("confmappingoverride"))));
         }
 
-        protected void infoStarted(Attributes attributes) {
+        private void infoStarted(Attributes attributes) {
             state = State.INFO;
-            String org = settings.substitute(attributes.getValue("organisation"));
-            String module = settings.substitute(attributes.getValue("module"));
-            String revision = settings.substitute(attributes.getValue("revision"));
-            String branch = settings.substitute(attributes.getValue("branch"));
-            getMd().setModuleRevisionId(ModuleRevisionId.newInstance(org, module, branch,
-                revision, ExtendableItemHelper.getExtraAttributes(settings, attributes,
-                    new String[] {
-                        "organisation", "module", "revision", "status", "publication",
-                        "branch", "namespace", "default", "resolver"})));
-
-            String namespace = settings.substitute(attributes.getValue("namespace"));
+            String org = substitute(attributes.getValue("organisation"));
+            String module = substitute(attributes.getValue("module"));
+            String revision = substitute(attributes.getValue("revision"));
+            String branch = substitute(attributes.getValue("branch"));
+            Map extraAttributes = getExtraAttributes(attributes, new String[]{"organisation", "module", "revision", "status", "publication", "branch", "namespace", "default", "resolver"});
+            getMd().setModuleRevisionId(ModuleRevisionId.newInstance(org, module, branch, revision, extraAttributes));
+
+            String namespace = substitute(attributes.getValue("namespace"));
             if (namespace != null) {
-                Namespace ns = settings.getNamespace(namespace);
+                Namespace ns = parserSettings.getNamespace(namespace);
                 if (ns == null) {
-                    Message.warn("namespace not found for " + getMd().getModuleRevisionId()
-                            + ": " + namespace);
+                    LOGGER.warn("namespace not found for " + getMd().getModuleRevisionId() + ": " + namespace);
                 } else {
                     getMd().setNamespace(ns);
                 }
             }
 
-            String status = settings.substitute(attributes.getValue("status"));
-            getMd().setStatus(status == null ? settings.getStatusManager().getDefaultStatus()
-                    : status);
-            getMd().setDefault(Boolean.valueOf(settings.substitute(attributes.getValue("default")))
-                    .booleanValue());
-            String pubDate = settings.substitute(attributes.getValue("publication"));
+            getMd().setStatus(elvis(substitute(attributes.getValue("status")), parserSettings.getStatusManager().getDefaultStatus()));
+            getMd().setDefault(Boolean.valueOf(substitute(attributes.getValue("default"))));
+            String pubDate = substitute(attributes.getValue("publication"));
             if (pubDate != null && pubDate.length() > 0) {
                 try {
                     final SimpleDateFormat ivyDateFormat = new SimpleDateFormat(IVY_DATE_FORMAT_PATTERN);
@@ -892,7 +688,7 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             }
         }
 
-        protected void ivyModuleStarted(Attributes attributes) throws SAXException {
+        private void ivyModuleStarted(Attributes attributes) throws SAXException {
             descriptorVersion = attributes.getValue("version");
             int versionIndex = ALLOWED_VERSIONS.indexOf(descriptorVersion);
             if (versionIndex == -1) {
@@ -900,85 +696,85 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 throw new SAXException("invalid version " + descriptorVersion);
             }
             if (versionIndex >= ALLOWED_VERSIONS.indexOf("1.3")) {
-                Message.debug("post 1.3 ivy file: using " + PatternMatcher.EXACT
-                        + " as default matcher");
-                defaultMatcher = settings.getMatcher(PatternMatcher.EXACT);
+                LOGGER.debug("post 1.3 ivy file: using " + PatternMatcher.EXACT + " as default matcher");
+                defaultMatcher = getMatcher(PatternMatcher.EXACT);
             } else {
-                Message.debug("pre 1.3 ivy file: using " + PatternMatcher.EXACT_OR_REGEXP
-                        + " as default matcher");
-                defaultMatcher = settings.getMatcher(PatternMatcher.EXACT_OR_REGEXP);
+                LOGGER.debug("pre 1.3 ivy file: using " + PatternMatcher.EXACT_OR_REGEXP + " as default matcher");
+                defaultMatcher = getMatcher(PatternMatcher.EXACT_OR_REGEXP);
             }
 
             for (int i = 0; i < attributes.getLength(); i++) {
                 if (attributes.getQName(i).startsWith("xmlns:")) {
-                    getMd().addExtraAttributeNamespace(
-                        attributes.getQName(i).substring("xmlns:".length()),
-                        attributes.getValue(i));
+                    getMd().addExtraAttributeNamespace(attributes.getQName(i).substring("xmlns:".length()), attributes.getValue(i));
                 }
             }
         }
 
-        protected void addDependencyArtifacts(String tag, Attributes attributes)
+        private void descriptionStarted(String qName, Attributes attributes) {
+            buffer.append("<").append(qName);
+            for (int i = 0; i < attributes.getLength(); i++) {
+                buffer.append(" ");
+                buffer.append(attributes.getQName(i));
+                buffer.append("=\"");
+                buffer.append(attributes.getValue(i));
+                buffer.append("\"");
+            }
+            buffer.append(">");
+        }
+
+        private void addDependencyArtifacts(String tag, Attributes attributes)
                 throws MalformedURLException {
             state = State.DEP_ARTIFACT;
             parseRule(tag, attributes);
         }
 
-        protected void addIncludeRule(String tag, Attributes attributes)
+        private void addIncludeRule(String tag, Attributes attributes)
                 throws MalformedURLException {
             state = State.ARTIFACT_INCLUDE;
             parseRule(tag, attributes);
         }
 
-        protected void addExcludeRule(String tag, Attributes attributes)
+        private void addExcludeRule(String tag, Attributes attributes)
                 throws MalformedURLException {
             state = State.ARTIFACT_EXCLUDE;
             parseRule(tag, attributes);
         }
 
-        protected void parseRule(String tag, Attributes attributes) throws MalformedURLException {
-            String name = settings.substitute(attributes.getValue("name"));
+        private void parseRule(String tag, Attributes attributes) throws MalformedURLException {
+            String name = substitute(attributes.getValue("name"));
             if (name == null) {
-                name = settings.substitute(attributes.getValue("artifact"));
+                name = substitute(attributes.getValue("artifact"));
                 if (name == null) {
                     name = "artifact".equals(tag) ? dd.getDependencyId().getName()
                             : PatternMatcher.ANY_EXPRESSION;
                 }
             }
-            String type = settings.substitute(attributes.getValue("type"));
+            String type = substitute(attributes.getValue("type"));
             if (type == null) {
                 type = "artifact".equals(tag) ? "jar" : PatternMatcher.ANY_EXPRESSION;
             }
-            String ext = settings.substitute(attributes.getValue("ext"));
+            String ext = substitute(attributes.getValue("ext"));
             ext = ext != null ? ext : type;
             if (state == State.DEP_ARTIFACT) {
-                String url = settings.substitute(attributes.getValue("url"));
-                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
-                    new String[] {"name", "type", "ext", "url", "conf"});
-                confAware = new DefaultDependencyArtifactDescriptor(dd, name, type, ext,
-                        url == null ? null : new URL(url), extraAtt);
+                String url = substitute(attributes.getValue("url"));
+                Map extraAttributes = getExtraAttributes(attributes, new String[]{"name", "type", "ext", "url", "conf"});
+                confAware = new DefaultDependencyArtifactDescriptor(dd, name, type, ext, url == null ? null : new URL(url), extraAttributes);
             } else if (state == State.ARTIFACT_INCLUDE) {
                 PatternMatcher matcher = getPatternMatcher(attributes.getValue("matcher"));
-                String org = settings.substitute(attributes.getValue("org"));
-                org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
-                String module = settings.substitute(attributes.getValue("module"));
-                module = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+                String org = elvis(substitute(attributes.getValue("org")), PatternMatcher.ANY_EXPRESSION);
+                String module = elvis(substitute(attributes.getValue("module")), PatternMatcher.ANY_EXPRESSION);
                 ArtifactId aid = new ArtifactId(new ModuleId(org, module), name, type, ext);
-                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
-                    new String[] {"org", "module", "name", "type", "ext", "matcher", "conf"});
-                confAware = new DefaultIncludeRule(aid, matcher, extraAtt);
+                Map extraAttributes = getExtraAttributes(attributes, new String[]{"org", "module", "name", "type", "ext", "matcher", "conf"});
+                confAware = new DefaultIncludeRule(aid, matcher, extraAttributes);
             } else { // _state == ARTIFACT_EXCLUDE || EXCLUDE
                 PatternMatcher matcher = getPatternMatcher(attributes.getValue("matcher"));
-                String org = settings.substitute(attributes.getValue("org"));
-                org = org == null ? PatternMatcher.ANY_EXPRESSION : org;
-                String module = settings.substitute(attributes.getValue("module"));
-                module = module == null ? PatternMatcher.ANY_EXPRESSION : module;
+                String org = elvis(substitute(attributes.getValue("org")), PatternMatcher.ANY_EXPRESSION);
+                String module = elvis(substitute(attributes.getValue("module")), PatternMatcher.ANY_EXPRESSION);
                 ArtifactId aid = new ArtifactId(new ModuleId(org, module), name, type, ext);
-                Map extraAtt = ExtendableItemHelper.getExtraAttributes(settings, attributes,
-                    new String[] {"org", "module", "name", "type", "ext", "matcher", "conf"});
-                confAware = new DefaultExcludeRule(aid, matcher, extraAtt);
+                Map extraAttributes = getExtraAttributes(attributes, new String[]{"org", "module", "name", "type", "ext", "matcher", "conf"});
+                confAware = new DefaultExcludeRule(aid, matcher, extraAttributes);
             }
-            String confs = settings.substitute(attributes.getValue("conf"));
+            String confs = substitute(attributes.getValue("conf"));
             // only add confs if they are specified. if they aren't, endElement will handle this
             // only if there are no conf defined in sub elements
             if (confs != null && confs.length() > 0) {
@@ -988,13 +784,13 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 } else {
                     conf = confs.split(",");
                 }
-                for (int i = 0; i < conf.length; i++) {
-                    addConfiguration(conf[i].trim());
+                for (String confName : conf) {
+                    addConfiguration(confName.trim());
                 }
             }
         }
 
-        protected void addConfiguration(String c) {
+        private void addConfiguration(String c) {
             confAware.addConfiguration(c);
             if (state != State.EXCLUDE) {
                 // we are currently adding a configuration to either an include, exclude or artifact
@@ -1012,33 +808,27 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
             }
         }
 
-        protected PatternMatcher getPatternMatcher(String m) {
-            String matcherName = settings.substitute(m);
-            PatternMatcher matcher = matcherName == null ? defaultMatcher : settings
-                    .getMatcher(matcherName);
+        private PatternMatcher getPatternMatcher(String m) {
+            String matcherName = substitute(m);
+            PatternMatcher matcher = matcherName == null ? defaultMatcher : getMatcher(matcherName);
             if (matcher == null) {
                 throw new IllegalArgumentException("unknown matcher " + matcherName);
             }
             return matcher;
         }
 
-
         public void characters(char[] ch, int start, int length) throws SAXException {
             if (buffer != null) {
                 buffer.append(ch, start, length);
             }
         }
 
-
         public void endElement(String uri, String localName, String qName) throws SAXException {
-            if (state == State.PUB && "artifact".equals(qName)
-                    && artifact.getConfigurations().length == 0) {
-                String[] confs = publicationsDefaultConf == null
-                    ? getMd().getConfigurationsNames()
-                    : publicationsDefaultConf;
-                for (int i = 0; i < confs.length; i++) {
-                    artifact.addConfiguration(confs[i].trim());
-                    getMd().addArtifact(confs[i].trim(), artifact);
+            if (state == State.PUB && "artifact".equals(qName) && artifact.getConfigurations().length == 0) {
+                String[] confs = publicationsDefaultConf == null ? getMd().getConfigurationsNames() : publicationsDefaultConf;
+                for (String confName : confs) {
+                    artifact.addConfiguration(confName.trim());
+                    getMd().addArtifact(confName.trim(), artifact);
                 }
             } else if ("configurations".equals(qName)) {
                 checkConfigurations();
@@ -1048,16 +838,16 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                 state = State.DEP;
                 if (confAware.getConfigurations().length == 0) {
                     String[] confs = getMd().getConfigurationsNames();
-                    for (int i = 0; i < confs.length; i++) {
-                        addConfiguration(confs[i]);
+                    for (String confName : confs) {
+                        addConfiguration(confName);
                     }
                 }
                 confAware = null;
             } else if ("exclude".equals(qName) && state == State.EXCLUDE) {
                 if (confAware.getConfigurations().length == 0) {
                     String[] confs = getMd().getConfigurationsNames();
-                    for (int i = 0; i < confs.length; i++) {
-                        addConfiguration(confs[i]);
+                    for (String confName : confs) {
+                        addConfiguration(confName);
                     }
                 }
                 confAware = null;
@@ -1084,120 +874,51 @@ public class IvyXmlModuleDescriptorParser extends AbstractModuleDescriptorParser
                     buffer.deleteCharAt(buffer.length() - 1);
                     buffer.append("/>");
                 } else {
-                    buffer.append("</" + qName + ">");
+                    buffer.append("</").append(qName).append(">");
                 }
             }
         }
 
-        protected void checkConfigurations() {
+        private void checkConfigurations() {
             if (getMd().getConfigurations().length == 0) {
                 getMd().addConfiguration(new Configuration("default"));
             }
         }
 
-        protected void replaceConfigurationWildcards() {
+        private void replaceConfigurationWildcards() {
             Configuration[] configs = getMd().getConfigurations();
-            for (int i = 0; i < configs.length; i++) {
-                configs[i].replaceWildcards(getMd());
+            for (Configuration config : configs) {
+                config.replaceWildcards(getMd());
             }
         }
 
-        /* getters and setters available for extension only */
-        protected ParserSettings getSettings() {
-            return settings;
-        }
-
-        protected URL getDescriptorURL() {
-            return descriptorURL;
-        }
-
-        protected InputStream getDescriptorInput() {
-            return descriptorInput;
-        }
-
-        protected int getState() {
-            return state;
-        }
-
-        protected void setState(int state) {
-            this.state = state;
-        }
-
-        protected PatternMatcher getDefaultMatcher() {
-            return defaultMatcher;
-        }
-
-        protected DefaultDependencyDescriptor getDd() {
-            return dd;
-        }
-
-        protected void setDd(DefaultDependencyDescriptor dd) {
-            this.dd = dd;
-        }
-
-        protected ConfigurationAware getConfAware() {
-            return confAware;
-        }
-
-        protected void setConfAware(ConfigurationAware confAware) {
-            this.confAware = confAware;
-        }
-
-        protected MDArtifact getArtifact() {
-            return artifact;
-        }
-
-        protected void setArtifact(MDArtifact artifact) {
-            this.artifact = artifact;
-        }
-
-        protected String getConf() {
-            return conf;
-        }
-
-        protected void setConf(String conf) {
-            this.conf = conf;
-        }
-
-        protected boolean isArtifactsDeclared() {
-            return artifactsDeclared;
-        }
-
-        protected void setArtifactsDeclared(boolean artifactsDeclared) {
-            this.artifactsDeclared = artifactsDeclared;
-        }
-
-        protected StringBuffer getBuffer() {
-            return buffer;
-        }
-
-        protected void setBuffer(StringBuffer buffer) {
-            this.buffer = buffer;
+        private URL getSchemaURL() {
+            return getClass().getResource("ivy.xsd");
         }
 
-        protected String getDescriptorVersion() {
-            return descriptorVersion;
+        private String elvis(String value, String defaultValue) {
+            return value != null ? value : defaultValue;
         }
 
-        protected void setDescriptorVersion(String descriptorVersion) {
-            this.descriptorVersion = descriptorVersion;
+        private String substitute(String name) {
+            return parserSettings.substitute(name);
         }
 
-        protected String[] getPublicationsDefaultConf() {
-            return publicationsDefaultConf;
+        private Map getExtraAttributes(Attributes attributes, String[] ignoredAttributeNames) {
+            return ExtendableItemHelper.getExtraAttributes(parserSettings, attributes, ignoredAttributeNames);
         }
 
-        protected void setPublicationsDefaultConf(String[] publicationsDefaultConf) {
-            this.publicationsDefaultConf = publicationsDefaultConf;
+        private void fillExtraAttributes(DefaultExtendableItem item, Attributes attributes, String[] ignoredAttNames) {
+            Map extraAttributes = getExtraAttributes(attributes, ignoredAttNames);
+            for (Object name : extraAttributes.keySet()) {
+                item.setExtraAttribute((String) name, (String) extraAttributes.get(name));
+            }
         }
 
-        protected boolean isValidate() {
-            return validate;
+        private PatternMatcher getMatcher(String matcherName) {
+            return parserSettings.getMatcher(matcherName);
         }
 
-        protected URL getSchemaURL() {
-            return getClass().getResource("ivy.xsd");
-        }
     }
 
     public String toString() {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
index e220920..715c70c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/DefaultModuleDescriptorCache.java
@@ -52,7 +52,6 @@ public class DefaultModuleDescriptorCache implements ModuleDescriptorCache {
         this.cacheLockingManager = cacheLockingManager;
         this.cacheMetadata = cacheMetadata;
 
-        // TODO:DAZ inject this
         moduleDescriptorStore = new ModuleDescriptorStore(new PathKeyFileStore(cacheMetadata.getCacheDir()), new IvyXmlModuleDescriptorWriter(), new IvyXmlModuleDescriptorParser());
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
index c2b9f4b..9f1f656 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
@@ -20,6 +20,7 @@ import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
 
 import java.util.Set;
@@ -47,4 +48,12 @@ public class PublishModuleDescriptorConverter implements ModuleDescriptorConvert
         artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, configurations);
         return moduleDescriptor;
     }
+
+    public ModuleDescriptor createModuleDescriptor(Module module) {
+        return resolveModuleDescriptorConverter.createModuleDescriptor(module);
+    }
+
+    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
+        resolveModuleDescriptorConverter.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
index be6527e..c29ccb0 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
@@ -20,8 +20,10 @@ import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
 
 import java.util.Set;
 
@@ -30,13 +32,16 @@ import java.util.Set;
  */
 public class ResolveModuleDescriptorConverter implements ModuleDescriptorConverter {
     private final ModuleDescriptorFactory moduleDescriptorFactory;
+    private final DependencyDescriptorFactory dependencyDescriptorFactory;
     private final ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter;
     private final DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter;
 
     public ResolveModuleDescriptorConverter(ModuleDescriptorFactory moduleDescriptorFactory,
+                                            DependencyDescriptorFactory dependencyDescriptorFactory,
                                             ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
                                             DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter) {
         this.moduleDescriptorFactory = moduleDescriptorFactory;
+        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
         this.configurationsToModuleDescriptorConverter = configurationsToModuleDescriptorConverter;
         this.dependenciesToModuleDescriptorConverter = dependenciesToModuleDescriptorConverter;
     }
@@ -48,4 +53,12 @@ public class ResolveModuleDescriptorConverter implements ModuleDescriptorConvert
         dependenciesToModuleDescriptorConverter.addDependencyDescriptors(moduleDescriptor, configurations);
         return moduleDescriptor;
     }
+
+    public ModuleDescriptor createModuleDescriptor(Module module) {
+        return moduleDescriptorFactory.createModuleDescriptor(module);
+    }
+
+    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
+        dependencyDescriptorFactory.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java
deleted file mode 100644
index 1806791..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.util.WrapUtil;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public abstract class AbstractDependencyDescriptorFactoryInternal implements DependencyDescriptorFactoryInternal {
-    private ExcludeRuleConverter excludeRuleConverter;
-
-    public AbstractDependencyDescriptorFactoryInternal(ExcludeRuleConverter excludeRuleConverter) {
-        this.excludeRuleConverter = excludeRuleConverter;
-    }
-
-    public DefaultDependencyDescriptor addDependencyDescriptor(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
-        return addDependencyDescriptor(configuration.getName(), moduleDescriptor, dependency);
-    }
-
-    public EnhancedDependencyDescriptor addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
-        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
-        EnhancedDependencyDescriptor newDescriptor = createDependencyDescriptor(dependency, configuration, moduleDescriptor, moduleRevisionId);
-        moduleDescriptor.addDependency(newDescriptor);
-        return newDescriptor;
-    }
-
-    protected abstract EnhancedDependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration,
-                                                                              ModuleDescriptor moduleDescriptor, ModuleRevisionId moduleRevisionId);
-
-    protected void addExcludesArtifactsAndDependencies(String configuration, ModuleDependency dependency,
-                                                       EnhancedDependencyDescriptor dependencyDescriptor) {
-        addArtifacts(configuration, dependency.getArtifacts(), dependencyDescriptor);
-        addExcludes(configuration, dependency.getExcludeRules(), dependencyDescriptor);
-        addDependencyConfiguration(configuration, dependency, dependencyDescriptor);
-    }
-
-    private void addArtifacts(String configuration, Set<DependencyArtifact> artifacts,
-                              EnhancedDependencyDescriptor dependencyDescriptor) {
-        for (DependencyArtifact artifact : artifacts) {
-            DefaultDependencyArtifactDescriptor artifactDescriptor;
-            try {
-                artifactDescriptor = new DefaultDependencyArtifactDescriptor(dependencyDescriptor, artifact.getName(),
-                        artifact.getType(),
-                        artifact.getExtension() != null ? artifact.getExtension() : artifact.getType(),
-                        artifact.getUrl() != null ? new URL(artifact.getUrl()) : null,
-                        artifact.getClassifier() != null ? WrapUtil.toMap(Dependency.CLASSIFIER,
-                                artifact.getClassifier()) : null);
-            } catch (MalformedURLException e) {
-                throw new InvalidUserDataException("URL for artifact can't be parsed: " + artifact.getUrl(), e);
-            }
-            dependencyDescriptor.addDependencyArtifact(configuration, artifactDescriptor);
-        }
-    }
-
-    private void addDependencyConfiguration(String configuration, ModuleDependency dependency,
-                                            DefaultDependencyDescriptor dependencyDescriptor) {
-        dependencyDescriptor.addDependencyConfiguration(configuration, dependency.getConfiguration());
-    }
-
-    private void addExcludes(String configuration, Set<ExcludeRule> excludeRules,
-                             DefaultDependencyDescriptor dependencyDescriptor) {
-        for (ExcludeRule excludeRule : excludeRules) {
-            dependencyDescriptor.addExcludeRule(configuration, excludeRuleConverter.createExcludeRule(configuration,
-                    excludeRule));
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java
new file mode 100644
index 0000000..4e8d20e
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractIvyDependencyDescriptorFactory.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.util.WrapUtil;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractIvyDependencyDescriptorFactory implements IvyDependencyDescriptorFactory {
+    private ExcludeRuleConverter excludeRuleConverter;
+
+    public AbstractIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+
+    protected void addExcludesArtifactsAndDependencies(String configuration, ModuleDependency dependency,
+                                                       EnhancedDependencyDescriptor dependencyDescriptor) {
+        addArtifacts(configuration, dependency.getArtifacts(), dependencyDescriptor);
+        addExcludes(configuration, dependency.getExcludeRules(), dependencyDescriptor);
+        addDependencyConfiguration(configuration, dependency, dependencyDescriptor);
+    }
+
+    private void addArtifacts(String configuration, Set<DependencyArtifact> artifacts,
+                              EnhancedDependencyDescriptor dependencyDescriptor) {
+        for (DependencyArtifact artifact : artifacts) {
+            DefaultDependencyArtifactDescriptor artifactDescriptor;
+            try {
+                artifactDescriptor = new DefaultDependencyArtifactDescriptor(dependencyDescriptor, artifact.getName(),
+                        artifact.getType(),
+                        artifact.getExtension() != null ? artifact.getExtension() : artifact.getType(),
+                        artifact.getUrl() != null ? new URL(artifact.getUrl()) : null,
+                        artifact.getClassifier() != null ? WrapUtil.toMap(Dependency.CLASSIFIER,
+                                artifact.getClassifier()) : null);
+            } catch (MalformedURLException e) {
+                throw new InvalidUserDataException("URL for artifact can't be parsed: " + artifact.getUrl(), e);
+            }
+            dependencyDescriptor.addDependencyArtifact(configuration, artifactDescriptor);
+        }
+    }
+
+    private void addDependencyConfiguration(String configuration, ModuleDependency dependency,
+                                            DefaultDependencyDescriptor dependencyDescriptor) {
+        dependencyDescriptor.addDependencyConfiguration(configuration, dependency.getConfiguration());
+    }
+
+    private void addExcludes(String configuration, Set<ExcludeRule> excludeRules,
+                             DefaultDependencyDescriptor dependencyDescriptor) {
+        for (ExcludeRule excludeRule : excludeRules) {
+            dependencyDescriptor.addExcludeRule(configuration, excludeRuleConverter.createExcludeRule(configuration,
+                    excludeRule));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java
deleted file mode 100644
index 12468ff..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ClientModule;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-
-/**
- * @author Hans Dockter
-*/
-public class ClientModuleDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
-    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule;
-
-    public ClientModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule) {
-        super(excludeRuleConverter);
-        this.moduleDescriptorFactoryForClientModule = moduleDescriptorFactoryForClientModule;
-    }
-
-    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        return IvyUtil.createModuleRevisionId(dependency);
-    }
-
-    public EnhancedDependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
-                                                           ModuleRevisionId moduleRevisionId) {
-        ClientModule clientModule = getClientModule(dependency);
-        ModuleDescriptor moduleDescriptor = moduleDescriptorFactoryForClientModule.createModuleDescriptor(
-                moduleRevisionId, clientModule.getDependencies());
-
-        EnhancedDependencyDescriptor dependencyDescriptor = new ClientModuleDependencyDescriptor(
-                clientModule,
-                parent,
-                moduleDescriptor,
-                moduleRevisionId,
-                clientModule.isForce(),
-                false,
-                clientModule.isTransitive());
-        addExcludesArtifactsAndDependencies(configuration, clientModule, dependencyDescriptor);
-        return dependencyDescriptor;
-    }
-
-    private ClientModule getClientModule(ModuleDependency dependency) {
-        return (ClientModule) dependency;
-    }
-
-    public boolean canConvert(ModuleDependency dependency) {
-        return dependency instanceof ClientModule;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactory.java
new file mode 100644
index 0000000..c4677cc
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleIvyDependencyDescriptorFactory.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+
+/**
+ * @author Hans Dockter
+*/
+public class ClientModuleIvyDependencyDescriptorFactory extends AbstractIvyDependencyDescriptorFactory {
+    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule;
+
+    public ClientModuleIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule) {
+        super(excludeRuleConverter);
+        this.moduleDescriptorFactoryForClientModule = moduleDescriptorFactoryForClientModule;
+    }
+
+    private ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        return IvyUtil.createModuleRevisionId(dependency);
+    }
+
+    public EnhancedDependencyDescriptor createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor parent) {
+        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
+        ClientModule clientModule = getClientModule(dependency);
+        ModuleDescriptor moduleDescriptor = moduleDescriptorFactoryForClientModule.createModuleDescriptor(
+                moduleRevisionId, clientModule.getDependencies());
+
+        EnhancedDependencyDescriptor dependencyDescriptor = new ClientModuleDependencyDescriptor(
+                clientModule,
+                parent,
+                moduleDescriptor,
+                moduleRevisionId,
+                clientModule.isForce(),
+                false,
+                clientModule.isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, clientModule, dependencyDescriptor);
+        return dependencyDescriptor;
+    }
+
+    private ClientModule getClientModule(ModuleDependency dependency) {
+        return (ClientModule) dependency;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ClientModule;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
index cad3eba..625accc 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
@@ -45,7 +45,7 @@ public class DefaultDependenciesToModuleDescriptorConverter implements Dependenc
     private void addDependencies(DefaultModuleDescriptor moduleDescriptor, Collection<? extends Configuration> configurations) {
         for (Configuration configuration : configurations) {
             for (ModuleDependency dependency : configuration.getDependencies().withType(ModuleDependency.class)) {
-                dependencyDescriptorFactory.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
+                dependencyDescriptorFactory.addDependencyDescriptor(configuration.getName(), moduleDescriptor, dependency);
             }
         }
     }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java
new file mode 100644
index 0000000..23bba49
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.util.WrapUtil;
+
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDependencyDescriptorFactory implements DependencyDescriptorFactory {
+    private List<IvyDependencyDescriptorFactory> dependencyDescriptorFactories;
+
+    public DefaultDependencyDescriptorFactory(IvyDependencyDescriptorFactory... dependencyDescriptorFactories) {
+        this.dependencyDescriptorFactories = WrapUtil.toList(dependencyDescriptorFactories);
+    }
+
+    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
+                                        ModuleDependency dependency) {
+        IvyDependencyDescriptorFactory factoryInternal = findFactoryForDependency(dependency);
+        EnhancedDependencyDescriptor dependencyDescriptor = factoryInternal.createDependencyDescriptor(configuration, dependency, moduleDescriptor);
+        moduleDescriptor.addDependency(dependencyDescriptor);
+    }
+
+    private IvyDependencyDescriptorFactory findFactoryForDependency(ModuleDependency dependency) {
+        for (IvyDependencyDescriptorFactory ivyDependencyDescriptorFactory : dependencyDescriptorFactories) {
+            if (ivyDependencyDescriptorFactory.canConvert(dependency)) {
+                return ivyDependencyDescriptorFactory;
+            }
+        }
+        throw new InvalidUserDataException("Can't map dependency of type: " + dependency.getClass());
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
index 75570ec..c5dbf33 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007-2008 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,21 +15,12 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
 
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ModuleDependency;
 
 /**
  * @author Hans Dockter
  */
 public interface DependencyDescriptorFactory {
-    /** Adds a dependency descriptor, using information in the configuration object to work around ivy limitations */
-    DefaultDependencyDescriptor addDependencyDescriptor(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency);
-
-    /** Adds a dependency descriptor where no configuration object is available */
-    DefaultDependencyDescriptor addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency);
-
-    ModuleRevisionId createModuleRevisionId(ModuleDependency dependency);
+    void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java
deleted file mode 100644
index 4f377a8..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.util.WrapUtil;
-
-import java.util.Set;
-
-/**
- * @author Hans Dockter
- */
-public class DependencyDescriptorFactoryDelegate implements DependencyDescriptorFactory {
-    private Set<DependencyDescriptorFactoryInternal> dependencyDescriptorFactories;
-
-    public DependencyDescriptorFactoryDelegate(DependencyDescriptorFactoryInternal... dependencyDescriptorFactories) {
-        this.dependencyDescriptorFactories = WrapUtil.toSet(dependencyDescriptorFactories);
-    }
-
-    public DefaultDependencyDescriptor addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
-                                        ModuleDependency dependency) {
-        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
-        return factoryInternal.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
-    }
-
-    public DefaultDependencyDescriptor addDependencyDescriptor(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
-        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
-        return factoryInternal.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
-    }
-
-    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
-        return factoryInternal.createModuleRevisionId(dependency);
-    }
-
-    private DependencyDescriptorFactoryInternal findFactoryForDependency(ModuleDependency dependency) {
-        for (DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal : dependencyDescriptorFactories) {
-            if (dependencyDescriptorFactoryInternal.canConvert(dependency)) {
-                return dependencyDescriptorFactoryInternal;
-            }
-        }
-        throw new InvalidUserDataException("Can't map dependency of type: " + dependency.getClass());
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java
deleted file mode 100644
index 0f1b531..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2007-2008 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.gradle.api.artifacts.ModuleDependency;
-
-/**
- * @author Hans Dockter
- */
-public interface DependencyDescriptorFactoryInternal extends DependencyDescriptorFactory {
-    boolean canConvert(ModuleDependency dependency);
-}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java
deleted file mode 100644
index 2e6e4af..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ExternalModuleDependency;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-
-/**
- * @author Hans Dockter
-*/
-public class ExternalModuleDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
-    public ExternalModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
-        super(excludeRuleConverter);
-    }
-
-    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        return IvyUtil.createModuleRevisionId(dependency);
-    }
-
-    public EnhancedDependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
-                                                           ModuleRevisionId moduleRevisionId) {
-        EnhancedDependencyDescriptor dependencyDescriptor = new EnhancedDependencyDescriptor(
-                dependency,
-                parent,
-                moduleRevisionId,
-                getExternalModuleDependency(dependency).isForce(),
-                getExternalModuleDependency(dependency).isChanging(),
-                getExternalModuleDependency(dependency).isTransitive());
-        addExcludesArtifactsAndDependencies(configuration, getExternalModuleDependency(dependency), dependencyDescriptor);
-        return dependencyDescriptor;
-    }
-
-    private ExternalModuleDependency getExternalModuleDependency(ModuleDependency dependency) {
-        return (ExternalModuleDependency) dependency;
-    }
-
-    public boolean canConvert(ModuleDependency dependency) {
-        return dependency instanceof ExternalModuleDependency;
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java
new file mode 100644
index 0000000..f885084
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleIvyDependencyDescriptorFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+
+/**
+ * @author Hans Dockter
+*/
+public class ExternalModuleIvyDependencyDescriptorFactory extends AbstractIvyDependencyDescriptorFactory {
+    public ExternalModuleIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
+        super(excludeRuleConverter);
+    }
+
+    private ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        return IvyUtil.createModuleRevisionId(dependency);
+    }
+
+    public EnhancedDependencyDescriptor createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor parent) {
+        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
+        EnhancedDependencyDescriptor dependencyDescriptor = new EnhancedDependencyDescriptor(
+                dependency,
+                parent,
+                moduleRevisionId,
+                getExternalModuleDependency(dependency).isForce(),
+                getExternalModuleDependency(dependency).isChanging(),
+                getExternalModuleDependency(dependency).isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, getExternalModuleDependency(dependency), dependencyDescriptor);
+        return dependencyDescriptor;
+    }
+
+    private ExternalModuleDependency getExternalModuleDependency(ModuleDependency dependency) {
+        return (ExternalModuleDependency) dependency;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ExternalModuleDependency;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/IvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/IvyDependencyDescriptorFactory.java
new file mode 100644
index 0000000..a8ef3dd
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/IvyDependencyDescriptorFactory.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2007-2008 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.ModuleDependency;
+
+public interface IvyDependencyDescriptorFactory {
+    EnhancedDependencyDescriptor createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor moduleDescriptor);
+
+    boolean canConvert(ModuleDependency dependency);
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java
deleted file mode 100644
index 2fe1c20..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.ModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
-import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
-import org.gradle.api.internal.project.ProjectInternal;
-
-/**
- * @author Hans Dockter
- */
-public class ProjectDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
-    public ProjectDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
-        super(excludeRuleConverter);
-    }
-
-    public EnhancedDependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
-                                                           ModuleRevisionId moduleRevisionId) {
-        ProjectDependency projectDependency = (ProjectDependency) dependency;
-        ProjectDependencyDescriptor dependencyDescriptor = new ProjectDependencyDescriptor(projectDependency, parent, moduleRevisionId, false, false, dependency.isTransitive());
-        addExcludesArtifactsAndDependencies(configuration, dependency, dependencyDescriptor);
-        return dependencyDescriptor;
-    }
-
-    public boolean canConvert(ModuleDependency dependency) {
-        return dependency instanceof ProjectDependency;
-    }
-
-    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-        ProjectDependency projectDependency = (ProjectDependency) dependency;
-        Module module = ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
-        return IvyUtil.createModuleRevisionId(module);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java
new file mode 100644
index 0000000..33e755b
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectIvyDependencyDescriptorFactory.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectIvyDependencyDescriptorFactory extends AbstractIvyDependencyDescriptorFactory {
+    public ProjectIvyDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
+        super(excludeRuleConverter);
+    }
+
+    public EnhancedDependencyDescriptor createDependencyDescriptor(String configuration, ModuleDependency dependency, ModuleDescriptor parent) {
+        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
+        ProjectDependency projectDependency = (ProjectDependency) dependency;
+        ProjectDependencyDescriptor dependencyDescriptor = new ProjectDependencyDescriptor(projectDependency, parent, moduleRevisionId, false, false, dependency.isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, dependency, dependencyDescriptor);
+        return dependencyDescriptor;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ProjectDependency;
+    }
+
+    private ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        ProjectDependency projectDependency = (ProjectDependency) dependency;
+        Module module = ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
+        return IvyUtil.createModuleRevisionId(module);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactory.java
new file mode 100644
index 0000000..9fa49b4
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactory.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+import java.lang.reflect.Field;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.gradle.internal.UncheckedException.throwAsUncheckedException;
+
+/**
+ * Ivy's dependency descriptor that is a copy of provided source descriptor with modified org, name and version.
+ */
+public class ReflectiveDependencyDescriptorFactory {
+
+    public DependencyDescriptor create(DependencyDescriptor source, ModuleRevisionId targetId) {
+        if (!(source instanceof DefaultDependencyDescriptor)) {
+            throw new IllegalArgumentException("I can only create descriptors out of DefaultDependencyDescriptor");
+        }
+        DefaultDependencyDescriptor out = new DefaultDependencyDescriptor(moduleDescriptor(source), targetId, source.getDynamicConstraintDependencyRevisionId(), source.isForce(), source.isChanging(), source.isTransitive());
+
+        setProperty(out, "parentId", getProperty(source, "parentId"));
+        setProperty(out, "namespace", source.getNamespace());
+        ((Map) getProperty(out, "confs")).putAll((Map) getProperty(source, "confs"));
+
+        Map sourceExcludeRules = (Map) getProperty(source, "excludeRules");
+        setProperty(out, "excludeRules", sourceExcludeRules == null? null: new LinkedHashMap(sourceExcludeRules));
+
+        Map sourceIncludeRules = (Map) getProperty(source, "includeRules");
+        setProperty(out, "includeRules", sourceIncludeRules == null ? null : new LinkedHashMap(sourceIncludeRules));
+
+        Map dependencyArtifacts = (Map) getProperty(source, "dependencyArtifacts");
+        setProperty(out, "dependencyArtifacts", dependencyArtifacts == null? null: new LinkedHashMap(dependencyArtifacts));
+
+        setProperty(out, "sourceModule", source.getSourceModule());
+
+        return out;
+    }
+
+    private static ModuleDescriptor moduleDescriptor(DependencyDescriptor source) {
+        return (ModuleDescriptor) getProperty(source, "md");
+    }
+
+    private static Object getProperty(Object object, String property) {
+        try {
+            Field field = DefaultDependencyDescriptor.class.getDeclaredField(property);
+            field.setAccessible(true);
+            return field.get(object);
+        } catch (Exception e) {
+            throw throwAsUncheckedException(e);
+        }
+    }
+
+    private static void setProperty(Object object, String property, Object value) {
+        try {
+            Field field = DefaultDependencyDescriptor.class.getDeclaredField(property);
+            field.setAccessible(true);
+            field.set(object, value);
+        } catch (Exception e) {
+            throw throwAsUncheckedException(e);
+        }
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
index 9e928b6..0b6b02c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/DefaultProjectModuleRegistry.java
@@ -17,7 +17,6 @@ package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
 
 import org.apache.ivy.core.module.descriptor.Artifact;
 import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
@@ -33,12 +32,8 @@ public class DefaultProjectModuleRegistry implements ProjectModuleRegistry {
         this.moduleDescriptorConverter = moduleDescriptorConverter;
     }
 
-    public ModuleDescriptor findProject(DependencyDescriptor descriptor) {
-        if (!(descriptor instanceof ProjectDependencyDescriptor)) {
-            return null;
-        }
-        ProjectDependencyDescriptor projectDependencyDescriptor = (ProjectDependencyDescriptor) descriptor;
-        ProjectInternal project = projectDependencyDescriptor.getTargetProject();
+    public ModuleDescriptor findProject(ProjectDependencyDescriptor descriptor) {
+        ProjectInternal project = descriptor.getTargetProject();
         Module projectModule = project.getModule();
         ModuleDescriptor projectDescriptor = moduleDescriptorConverter.convert(project.getConfigurations(), projectModule);
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
index 03f6c9e..54d67c2 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java
@@ -21,6 +21,9 @@ import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
 import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor;
+import org.gradle.initialization.ProjectAccessListener;
 
 import java.io.File;
 
@@ -28,21 +31,26 @@ public class ProjectDependencyResolver implements DependencyToModuleResolver {
     private final ProjectModuleRegistry projectModuleRegistry;
     private final DependencyToModuleResolver resolver;
     private final ProjectArtifactResolver artifactResolver;
+    private final ProjectAccessListener projectAccessListener;
 
-    public ProjectDependencyResolver(ProjectModuleRegistry projectModuleRegistry, DependencyToModuleResolver resolver) {
+    public ProjectDependencyResolver(ProjectModuleRegistry projectModuleRegistry, DependencyToModuleResolver resolver, ProjectAccessListener projectAccessListener) {
         this.projectModuleRegistry = projectModuleRegistry;
         this.resolver = resolver;
+        this.projectAccessListener = projectAccessListener;
         artifactResolver = new ProjectArtifactResolver();
     }
 
-    public void resolve(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionResolveResult result) {
-        ModuleDescriptor moduleDescriptor = projectModuleRegistry.findProject(dependencyDescriptor);
-        if (moduleDescriptor != null) {
+    public void resolve(DependencyMetaData dependency, BuildableModuleVersionResolveResult result) {
+        DependencyDescriptor descriptor = dependency.getDescriptor();
+        if (descriptor instanceof ProjectDependencyDescriptor) {
+            ProjectDependencyDescriptor desc = (ProjectDependencyDescriptor) descriptor;
+            projectAccessListener.beforeResolvingProjectDependency(desc.getTargetProject());
+            ModuleDescriptor moduleDescriptor = projectModuleRegistry.findProject(desc);
             final ModuleRevisionId moduleRevisionId = moduleDescriptor.getModuleRevisionId();
             final DefaultModuleVersionIdentifier moduleVersionIdentifier = new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision());
             result.resolved(moduleVersionIdentifier, moduleDescriptor, artifactResolver);
         } else {
-            resolver.resolve(dependencyDescriptor, result);
+            resolver.resolve(dependency, result);
         }
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java
index 5e413f0..6ffc502 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectModuleRegistry.java
@@ -15,12 +15,12 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.projectmodule;
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor;
 
 /**
  * TODO - this probably should lookup a project by id, much like ClientModuleRegistry. Then, there would be no dependency on ivy DependencyDescriptor.
  */
 public interface ProjectModuleRegistry {
-    ModuleDescriptor findProject(DependencyDescriptor descriptor);
+    ModuleDescriptor findProject(ProjectDependencyDescriptor descriptor);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
index 3be653c..740b51a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategy.java
@@ -25,7 +25,9 @@ import org.gradle.api.artifacts.cache.ResolutionRules;
 import org.gradle.api.internal.Actions;
 import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal;
 import org.gradle.api.internal.artifacts.configurations.ResolutionStrategyInternal;
-import org.gradle.api.internal.artifacts.dsl.ForcedModuleNotationParser;
+import org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers;
+import org.gradle.api.internal.notations.parsers.NormalizedTimeUnit;
+import org.gradle.api.internal.notations.parsers.TimeUnitsParser;
 
 import java.util.Collection;
 import java.util.LinkedHashSet;
@@ -71,9 +73,8 @@ public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
         return cachePolicy;
     }
 
-    public DefaultResolutionStrategy force(Object... forcedModuleNotations) {
-        assert forcedModuleNotations != null : "forcedModuleNotations cannot be null";
-        Set<ModuleVersionSelector> modules = new ForcedModuleNotationParser().parseNotation(forcedModuleNotations);
+    public DefaultResolutionStrategy force(Object... moduleVersionSelectorNotations) {
+        Set<ModuleVersionSelector> modules = ModuleVersionSelectorParsers.multiParser().parseNotation(moduleVersionSelectorNotations);
         this.forcedModules.addAll(modules);
         return this;
     }
@@ -88,8 +89,8 @@ public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
         return Actions.composite(allRules);
     }
 
-    public DefaultResolutionStrategy setForcedModules(Object ... forcedModuleNotations) {
-        Set<ModuleVersionSelector> forcedModules = new ForcedModuleNotationParser().parseNotation(forcedModuleNotations);
+    public DefaultResolutionStrategy setForcedModules(Object ... moduleVersionSelectorNotations) {
+        Set<ModuleVersionSelector> forcedModules = ModuleVersionSelectorParsers.multiParser().parseNotation(moduleVersionSelectorNotations);
         this.forcedModules = forcedModules;
         return this;
     }
@@ -99,8 +100,8 @@ public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
     }
 
     public void cacheDynamicVersionsFor(int value, String units) {
-        TimeUnit timeUnit = TimeUnit.valueOf(units.toUpperCase());
-        cacheDynamicVersionsFor(value, timeUnit);
+        NormalizedTimeUnit timeUnit = new TimeUnitsParser().parseNotation(units, value);
+        cacheDynamicVersionsFor(timeUnit.getValue(), timeUnit.getTimeUnit());
     }
 
     public void cacheDynamicVersionsFor(int value, TimeUnit units) {
@@ -108,8 +109,8 @@ public class DefaultResolutionStrategy implements ResolutionStrategyInternal {
     }
 
     public void cacheChangingModulesFor(int value, String units) {
-        TimeUnit timeUnit = TimeUnit.valueOf(units.toUpperCase());
-        cacheChangingModulesFor(value, timeUnit);
+        NormalizedTimeUnit timeUnit = new TimeUnitsParser().parseNotation(units, value);
+        cacheChangingModulesFor(timeUnit.getValue(), timeUnit.getTimeUnit());
     }
 
     public void cacheChangingModulesFor(int value, TimeUnit units) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
index 73fad93..e572e6b 100755
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DefaultDependencyResolver.java
@@ -28,32 +28,40 @@ import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDepende
 import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectModuleRegistry;
 import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.StrictConflictResolution;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolutionResultBuilder;
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository;
+import org.gradle.initialization.ProjectAccessListener;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+
 public class DefaultDependencyResolver implements ArtifactDependencyResolver {
     private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDependencyResolver.class);
     private final ModuleDescriptorConverter moduleDescriptorConverter;
     private final ResolvedArtifactFactory resolvedArtifactFactory;
     private final ResolveIvyFactory ivyFactory;
     private final ProjectModuleRegistry projectModuleRegistry;
+    private final ProjectAccessListener projectAccessListener;
+    private final CacheLockingManager cacheLockingManager;
 
     public DefaultDependencyResolver(ResolveIvyFactory ivyFactory, ModuleDescriptorConverter moduleDescriptorConverter, ResolvedArtifactFactory resolvedArtifactFactory,
-                                     ProjectModuleRegistry projectModuleRegistry) {
+                                     ProjectModuleRegistry projectModuleRegistry, ProjectAccessListener projectAccessListener, CacheLockingManager cacheLockingManager) {
         this.ivyFactory = ivyFactory;
         this.moduleDescriptorConverter = moduleDescriptorConverter;
         this.resolvedArtifactFactory = resolvedArtifactFactory;
         this.projectModuleRegistry = projectModuleRegistry;
+        this.projectAccessListener = projectAccessListener;
+        this.cacheLockingManager = cacheLockingManager;
     }
 
-    public ResolverResults resolve(ConfigurationInternal configuration) throws ResolveException {
+    public ResolverResults resolve(ConfigurationInternal configuration, List<? extends ResolutionAwareRepository> repositories) throws ResolveException {
         LOGGER.debug("Resolving {}", configuration);
 
-        IvyAdapter ivyAdapter = ivyFactory.create(configuration);
+        IvyAdapter ivyAdapter = ivyFactory.create(configuration, repositories);
 
         DependencyToModuleResolver dependencyResolver = ivyAdapter.getDependencyToModuleResolver();
         dependencyResolver = new ClientModuleResolver(dependencyResolver);
-        dependencyResolver = new ProjectDependencyResolver(projectModuleRegistry, dependencyResolver);
+        dependencyResolver = new ProjectDependencyResolver(projectModuleRegistry, dependencyResolver, projectAccessListener);
         DependencyToModuleVersionIdResolver idResolver = new LazyDependencyToModuleResolver(dependencyResolver, ivyAdapter.getResolveData().getSettings().getVersionMatcher());
         idResolver = new VersionForcingDependencyToModuleResolver(idResolver, configuration.getResolutionStrategy().getDependencyResolveRule());
 
@@ -63,10 +71,11 @@ public class DefaultDependencyResolver implements ArtifactDependencyResolver {
         } else {
             conflictResolver = new LatestModuleConflictResolver();
         }
+        ModuleConflictResolver actualResolver = new VersionSelectionReasonResolver(conflictResolver);
 
-        DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, idResolver, conflictResolver);
+        DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, idResolver, actualResolver);
         ResolutionResultBuilder resultBuilder = new ResolutionResultBuilder();
         DefaultLenientConfiguration result = builder.resolve(configuration, ivyAdapter.getResolveData(), resultBuilder);
-        return new ResolverResults(new DefaultResolvedConfiguration(result), resultBuilder.getResult());
+        return new ResolverResults(new DefaultResolvedConfiguration(result, cacheLockingManager), resultBuilder.getResult());
     }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
index e007e03..f01fe53 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilder.java
@@ -26,11 +26,14 @@ import org.gradle.api.artifacts.ResolveException;
 import org.gradle.api.artifacts.ResolvedArtifact;
 import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier;
-import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector;
 import org.gradle.api.internal.artifacts.DefaultResolvedDependency;
 import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DefaultBuildableModuleVersionMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.EnhancedDependencyDescriptor;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.InternalDependencyResult;
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ModuleVersionSelection;
@@ -58,9 +61,11 @@ public class DependencyGraphBuilder {
     }
 
     public DefaultLenientConfiguration resolve(ConfigurationInternal configuration, ResolveData resolveData, ResolvedConfigurationListener listener) throws ResolveException {
-        ModuleDescriptor moduleDescriptor = moduleDescriptorConverter.convert(configuration.getAll(), configuration.getModule());
+        ModuleDescriptor rootModuleDescriptor = moduleDescriptorConverter.convert(configuration.getAll(), configuration.getModule());
+        BuildableModuleVersionMetaData rootMetaData = new DefaultBuildableModuleVersionMetaData();
+        rootMetaData.resolved(rootModuleDescriptor, false, null);
 
-        ResolveState resolveState = new ResolveState(moduleDescriptor, configuration.getName(), dependencyResolver, resolveData);
+        ResolveState resolveState = new ResolveState(rootMetaData, configuration.getName(), dependencyResolver, resolveData);
         traverseGraph(resolveState);
 
         DefaultLenientConfiguration result = new DefaultLenientConfiguration(configuration, resolveState.root.getResult());
@@ -166,7 +171,7 @@ public class DependencyGraphBuilder {
     }
 
     private static class FailureState {
-        final Map<ModuleRevisionId, BrokenDependency> failuresByRevisionId = new LinkedHashMap<ModuleRevisionId, BrokenDependency>();
+        final Map<ModuleVersionSelector, BrokenDependency> failuresByRevisionId = new LinkedHashMap<ModuleVersionSelector, BrokenDependency>();
         final ConfigurationNode root;
 
         private FailureState(ConfigurationNode root) {
@@ -174,13 +179,13 @@ public class DependencyGraphBuilder {
         }
 
         public void attachFailures(ResolvedConfigurationBuilder result) {
-            for (Map.Entry<ModuleRevisionId, BrokenDependency> entry : failuresByRevisionId.entrySet()) {
-                Collection<List<ModuleRevisionId>> paths = calculatePaths(entry);
+            for (Map.Entry<ModuleVersionSelector, BrokenDependency> entry : failuresByRevisionId.entrySet()) {
+                Collection<List<ModuleRevisionId>> paths = calculatePaths(entry.getValue());
                 result.addUnresolvedDependency(new DefaultUnresolvedDependency(entry.getKey(), entry.getValue().failure.withIncomingPaths(paths)));
             }
         }
 
-        private Collection<List<ModuleRevisionId>> calculatePaths(Map.Entry<ModuleRevisionId, BrokenDependency> entry) {
+        private Collection<List<ModuleRevisionId>> calculatePaths(BrokenDependency brokenDependency) {
             // Include the shortest path from each version that has a direct dependency on the broken dependency, back to the root
             
             Map<DefaultModuleRevisionResolveState, List<ModuleRevisionId>> shortestPaths = new LinkedHashMap<DefaultModuleRevisionResolveState, List<ModuleRevisionId>>();
@@ -189,7 +194,7 @@ public class DependencyGraphBuilder {
             shortestPaths.put(root.moduleRevision, rootPath);
 
             Set<DefaultModuleRevisionResolveState> directDependees = new LinkedHashSet<DefaultModuleRevisionResolveState>();
-            for (ConfigurationNode node : entry.getValue().requiredBy) {
+            for (ConfigurationNode node : brokenDependency.requiredBy) {
                 directDependees.add(node.moduleRevision);
             }
 
@@ -240,11 +245,11 @@ public class DependencyGraphBuilder {
             return paths;
         }
 
-        public void addUnresolvedDependency(DependencyEdge dependency, ModuleRevisionId revisionId, ModuleVersionResolveException failure) {
-            BrokenDependency breakage = failuresByRevisionId.get(revisionId);
+        public void addUnresolvedDependency(DependencyEdge dependency, ModuleVersionSelector requested, ModuleVersionResolveException failure) {
+            BrokenDependency breakage = failuresByRevisionId.get(requested);
             if (breakage == null) {
                 breakage = new BrokenDependency(failure);
-                failuresByRevisionId.put(revisionId, breakage);
+                failuresByRevisionId.put(requested, breakage);
             }
             breakage.requiredBy.add(dependency.from);
         }
@@ -262,6 +267,7 @@ public class DependencyGraphBuilder {
     private static class DependencyEdge implements InternalDependencyResult {
         private final ConfigurationNode from;
         private final DependencyDescriptor dependencyDescriptor;
+        private final DependencyMetaData dependencyMetaData;
         private final Set<String> targetConfigurationRules;
         private final ResolveState resolveState;
         private final ModuleVersionSpec selectorSpec;
@@ -269,9 +275,10 @@ public class DependencyGraphBuilder {
         private ModuleVersionSelectorResolveState selector;
         private DefaultModuleRevisionResolveState targetModuleRevision;
 
-        public DependencyEdge(ConfigurationNode from, DependencyDescriptor dependencyDescriptor, Set<String> targetConfigurationRules, ModuleVersionSpec selectorSpec, ResolveState resolveState) {
+        public DependencyEdge(ConfigurationNode from, DependencyMetaData dependencyMetaData, Set<String> targetConfigurationRules, ModuleVersionSpec selectorSpec, ResolveState resolveState) {
             this.from = from;
-            this.dependencyDescriptor = dependencyDescriptor;
+            this.dependencyMetaData = dependencyMetaData;
+            this.dependencyDescriptor = dependencyMetaData.getDescriptor();
             this.targetConfigurationRules = targetConfigurationRules;
             this.selectorSpec = selectorSpec;
             this.resolveState = resolveState;
@@ -279,7 +286,7 @@ public class DependencyGraphBuilder {
 
         @Override
         public String toString() {
-            return String.format("%s -> %s(%s)", from.toString(), dependencyDescriptor.getDependencyRevisionId(), targetConfigurationRules);
+            return String.format("%s -> %s(%s)", from.toString(), dependencyMetaData.getRequested(), targetConfigurationRules);
         }
 
         public DefaultModuleRevisionResolveState getTargetModuleRevision() {
@@ -288,7 +295,7 @@ public class DependencyGraphBuilder {
 
         public void resolveModuleRevisionId() {
             if (targetModuleRevision == null) {
-                selector = resolveState.getSelector(dependencyDescriptor);
+                selector = resolveState.getSelector(dependencyMetaData, dependencyDescriptor.getDependencyRevisionId());
                 targetModuleRevision = selector.resolveModuleRevisionId();
                 selector.module.addUnattachedDependency(this);
             }
@@ -326,12 +333,13 @@ public class DependencyGraphBuilder {
 
         private void calculateTargetConfigurations() {
             targetConfigurations.clear();
-            ModuleDescriptor targetDescriptor = targetModuleRevision.getDescriptor();
-            if (targetDescriptor == null) {
+            ModuleVersionMetaData targetModuleVersion = targetModuleRevision.getMetaData();
+            if (targetModuleVersion == null) {
                 // Broken version
                 return;
             }
 
+            ModuleDescriptor targetDescriptor = targetModuleVersion.getDescriptor();
             IvyNode node = new IvyNode(resolveState.resolveData, targetDescriptor);
             Set<String> targets = new LinkedHashSet<String>();
             for (String targetConfiguration : targetConfigurationRules) {
@@ -398,27 +406,28 @@ public class DependencyGraphBuilder {
         }
 
         public ModuleVersionSelector getRequested() {
-            return new DefaultModuleVersionSelector(
-                    dependencyDescriptor.getDependencyRevisionId().getOrganisation(),
-                    dependencyDescriptor.getDependencyRevisionId().getName(),
-                    dependencyDescriptor.getDependencyRevisionId().getRevision());
+            return dependencyMetaData.getRequested();
         }
 
         public ModuleVersionResolveException getFailure() {
-            return selector.failure;
+            //see also getSelected(). For evicted targetModuleRevisions, we need to reach out to the failure of selected module
+            //covered in VersionConflictResolutionIntegrationTest
+            return selector.failure != null ? selector.failure : getSelected().resolver.failure;
         }
 
-        public ModuleVersionSelection getSelected() {
+        public DefaultModuleRevisionResolveState getSelected() {
+            //we cannot use the targetModuleRevision field because it may have been evicted
+            //covered in VersionConflictResolutionIntegrationTest
             return selector.module.selected;
         }
 
         public ModuleVersionSelectionReason getReason() {
-            return selector.module.selected == null ? selector.idSelectionReason : selector.module.selected.selectionReason;
+            return getSelected() == null ? selector.idSelectionReason : getSelected().selectionReason;
         }
 
         public void collectFailures(FailureState failureState) {
             if (isFailed()) {
-                failureState.addUnresolvedDependency(this, selector.descriptor.getDependencyRevisionId(), getFailure());
+                failureState.addUnresolvedDependency(this, selector.dependencyMetaData.getRequested(), selector.failure);
             }
         }
 
@@ -434,12 +443,11 @@ public class DependencyGraphBuilder {
         private final Set<ConfigurationNode> queued = new HashSet<ConfigurationNode>();
         private final LinkedList<ConfigurationNode> queue = new LinkedList<ConfigurationNode>();
 
-        public ResolveState(ModuleDescriptor rootModule, String rootConfigurationName, DependencyToModuleVersionIdResolver resolver, ResolveData resolveData) {
+        public ResolveState(BuildableModuleVersionMetaData rootModule, String rootConfigurationName, DependencyToModuleVersionIdResolver resolver, ResolveData resolveData) {
             this.resolver = resolver;
             this.resolveData = resolveData;
-            final ModuleRevisionId moduleRevisionId = rootModule.getModuleRevisionId();
-            DefaultModuleRevisionResolveState rootVersion = getRevision(new DefaultModuleVersionIdentifier(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision()));
-            rootVersion.setDescriptor(rootModule);
+            DefaultModuleRevisionResolveState rootVersion = getRevision(rootModule.getId());
+            rootVersion.setMetaData(rootModule);
             root = getConfigurationNode(rootVersion, rootConfigurationName);
             root.moduleRevision.module.select(root.moduleRevision);
         }
@@ -468,18 +476,17 @@ public class DependencyGraphBuilder {
             ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(original.getOrganisation(), original.getName(), original.getRevision(), configurationName);
             ConfigurationNode configuration = nodes.get(id);
             if (configuration == null) {
-                configuration = new ConfigurationNode(module, module.descriptor, configurationName, this);
+                configuration = new ConfigurationNode(module, module.metaData, configurationName, this);
                 nodes.put(id, configuration);
             }
             return configuration;
         }
 
-        public ModuleVersionSelectorResolveState getSelector(DependencyDescriptor dependencyDescriptor) {
-            ModuleRevisionId original = dependencyDescriptor.getDependencyRevisionId();
+        public ModuleVersionSelectorResolveState getSelector(DependencyMetaData dependencyMetaData, ModuleRevisionId original) {
             ModuleRevisionId selectorId = ModuleRevisionId.newInstance(original.getOrganisation(), original.getName(), original.getRevision());
             ModuleVersionSelectorResolveState resolveState = selectors.get(selectorId);
             if (resolveState == null) {
-                resolveState = new ModuleVersionSelectorResolveState(dependencyDescriptor, getModule(selectorId.getModuleId()), resolver, this);
+                resolveState = new ModuleVersionSelectorResolveState(dependencyMetaData, getModule(selectorId.getModuleId()), resolver, this);
                 selectors.put(selectorId, resolveState);
             }
             return resolveState;
@@ -593,8 +600,8 @@ public class DependencyGraphBuilder {
         final ModuleRevisionId id;
         final ResolveState resolveState;
         final Set<ConfigurationNode> configurations = new LinkedHashSet<ConfigurationNode>();
-        List<DependencyDescriptor> dependencies;
-        ModuleDescriptor descriptor;
+        List<DependencyMetaData> dependencies;
+        ModuleVersionMetaData metaData;
         ModuleState state = ModuleState.New;
         ModuleVersionSelectorResolveState resolver;
         ModuleVersionSelectionReason selectionReason = VersionSelectionReasons.REQUESTED;
@@ -614,9 +621,9 @@ public class DependencyGraphBuilder {
             return id.getRevision();
         }
 
-        public Iterable<DependencyDescriptor> getDependencies() {
+        public Iterable<DependencyMetaData> getDependencies() {
             if (dependencies == null) {
-                dependencies = Arrays.asList(getDescriptor().getDependencies());
+                dependencies = getMetaData().getDependencies();
             }
             return dependencies;
         }
@@ -637,24 +644,24 @@ public class DependencyGraphBuilder {
             }
         }
 
-        public ModuleDescriptor getDescriptor() {
-            if (descriptor == null) {
+        public ModuleVersionMetaData getMetaData() {
+            if (metaData == null) {
                 if (resolver == null) {
                     throw new IllegalStateException(String.format("No resolver for %s.", this));
                 }
                 resolver.resolve();
             }
-            return descriptor;
+            return metaData;
         }
 
-        public void addConfiguration(ConfigurationNode configurationNode) {
-            configurations.add(configurationNode);
+        public void setMetaData(ModuleVersionMetaData metaData) {
+            if (this.metaData == null) {
+                this.metaData = metaData;
+            }
         }
 
-        public void setDescriptor(ModuleDescriptor descriptor) {
-            if (this.descriptor == null) {
-                this.descriptor = descriptor;
-            }
+        public void addConfiguration(ConfigurationNode configurationNode) {
+            configurations.add(configurationNode);
         }
 
         public ModuleVersionIdentifier getSelectedId() {
@@ -667,6 +674,10 @@ public class DependencyGraphBuilder {
         public ModuleVersionSelectionReason getSelectionReason() {
             return selectionReason;
         }
+
+        public void setSelectionReason(ModuleVersionSelectionReason reason) {
+            this.selectionReason = reason;
+        }
     }
 
     private static class ConfigurationNode {
@@ -681,10 +692,10 @@ public class DependencyGraphBuilder {
         ModuleVersionSpec previousTraversal;
         Set<ResolvedArtifact> artifacts;
 
-        private ConfigurationNode(DefaultModuleRevisionResolveState moduleRevision, ModuleDescriptor descriptor, String configurationName, ResolveState resolveState) {
+        private ConfigurationNode(DefaultModuleRevisionResolveState moduleRevision, ModuleVersionMetaData moduleVersionMetaData, String configurationName, ResolveState resolveState) {
             this.moduleRevision = moduleRevision;
             this.resolveState = resolveState;
-            this.descriptor = (DefaultModuleDescriptor) descriptor;
+            this.descriptor = (DefaultModuleDescriptor) moduleVersionMetaData.getDescriptor();
             this.configurationName = configurationName;
             findAncestors(configurationName, resolveState, heirarchy);
             moduleRevision.addConfiguration(this);
@@ -771,9 +782,10 @@ public class DependencyGraphBuilder {
                 removeOutgoingEdges();
             }
 
-            for (DependencyDescriptor dependency : moduleRevision.getDependencies()) {
-                ModuleId targetModuleId = dependency.getDependencyRevisionId().getModuleId();
-                Set<String> targetConfigurations = getTargetConfigurations(dependency);
+            for (DependencyMetaData dependency : moduleRevision.getDependencies()) {
+                DependencyDescriptor dependencyDescriptor = dependency.getDescriptor();
+                ModuleId targetModuleId = dependencyDescriptor.getDependencyRevisionId().getModuleId();
+                Set<String> targetConfigurations = getTargetConfigurations(dependencyDescriptor);
                 if (!targetConfigurations.isEmpty()) {
                     if (!selectorSpec.isSatisfiedBy(targetModuleId)) {
                         LOGGER.debug("{} is excluded from {}.", targetModuleId, this);
@@ -792,11 +804,7 @@ public class DependencyGraphBuilder {
             for (String moduleConfiguration : dependencyDescriptor.getModuleConfigurations()) {
                 if (moduleConfiguration.equals("*") || heirarchy.contains(moduleConfiguration)) {
                     for (String targetConfiguration : dependencyDescriptor.getDependencyConfigurations(moduleConfiguration)) {
-                        if (targetConfiguration.equals("*")) {
-                            Collections.addAll(targetConfigurations, descriptor.getPublicConfigurationsNames());
-                        } else {
-                            targetConfigurations.add(targetConfiguration);
-                        }
+                        targetConfigurations.add(targetConfiguration);
                     }
                 }
             }
@@ -833,7 +841,7 @@ public class DependencyGraphBuilder {
         private ModuleVersionSpec getSelector(List<DependencyEdge> transitiveEdges) {
             ModuleVersionSpec selector;
             if (transitiveEdges.isEmpty()) {
-                selector = ModuleVersionSpec.forExcludes();
+                selector = ModuleVersionSpec.forExcludes(); //includes all
             } else {
                 selector = transitiveEdges.get(0).getSelector();
                 for (int i = 1; i < transitiveEdges.size(); i++) {
@@ -876,18 +884,18 @@ public class DependencyGraphBuilder {
     }
 
     private static class ModuleVersionSelectorResolveState {
-        final DependencyDescriptor descriptor;
         final DependencyToModuleVersionIdResolver resolver;
         final ResolveState resolveState;
-        final ModuleResolveState module;
+        final DependencyMetaData dependencyMetaData;
+        ModuleResolveState module;
         ModuleVersionResolveException failure;
         ModuleVersionSelectionReason idSelectionReason;
         DefaultModuleRevisionResolveState targetModuleRevision;
         ModuleVersionIdResolveResult idResolveResult;
         ModuleVersionResolveResult resolveResult;
 
-        private ModuleVersionSelectorResolveState(DependencyDescriptor descriptor, ModuleResolveState module, DependencyToModuleVersionIdResolver resolver, ResolveState resolveState) {
-            this.descriptor = descriptor;
+        private ModuleVersionSelectorResolveState(DependencyMetaData dependencyMetaData, ModuleResolveState module, DependencyToModuleVersionIdResolver resolver, ResolveState resolveState) {
+            this.dependencyMetaData = dependencyMetaData;
             this.module = module;
             this.resolver = resolver;
             this.resolveState = resolveState;
@@ -895,7 +903,7 @@ public class DependencyGraphBuilder {
 
         @Override
         public String toString() {
-            return descriptor.toString();
+            return dependencyMetaData.toString();
         }
 
         /**
@@ -903,17 +911,13 @@ public class DependencyGraphBuilder {
          */
         public DefaultModuleRevisionResolveState resolveModuleRevisionId() {
             if (targetModuleRevision != null) {
-                //(SF) this might not be quite right
-                //this.targetModuleRevision might have been evicted in an earlier pass of conflict resolution
-                //and the module.selected has the actual target module.
-                //I'm not sure how big deal is it.
                 return targetModuleRevision;
             }
             if (failure != null) {
                 return null;
             }
 
-            idResolveResult = resolver.resolve(descriptor);
+            idResolveResult = resolver.resolve(dependencyMetaData);
             idSelectionReason = idResolveResult.getSelectionReason();
             if (idResolveResult.getFailure() != null) {
                 failure = idResolveResult.getFailure();
@@ -924,6 +928,10 @@ public class DependencyGraphBuilder {
             targetModuleRevision.addResolver(this);
             targetModuleRevision.selectionReason = idResolveResult.getSelectionReason();
 
+            //the target module details might have been substituted / forced when resolving ID
+            //so update the module:
+            this.module = targetModuleRevision.module;
+
             return targetModuleRevision;
         }
 
@@ -937,7 +945,7 @@ public class DependencyGraphBuilder {
 
             try {
                 resolveResult = idResolveResult.resolve();
-                resolveState.getRevision(resolveResult.getId()).setDescriptor(resolveResult.getDescriptor());
+                resolveState.getRevision(resolveResult.getId()).setMetaData(resolveResult.getMetaData());
             } catch (ModuleVersionResolveException e) {
                 failure = e;
             }
@@ -945,7 +953,7 @@ public class DependencyGraphBuilder {
         }
 
         public ModuleVersionSelectorResolveState restart(DefaultModuleRevisionResolveState moduleRevision) {
-            return resolveState.getSelector(descriptor.clone(moduleRevision.id));
+            return resolveState.getSelector(dependencyMetaData.withRequestedVersion(moduleRevision.id.getRevision()), moduleRevision.id);
         }
     }
 
@@ -965,14 +973,7 @@ public class DependencyGraphBuilder {
                     }
                 }
             }
-            //TODO SF unit test
-            DefaultModuleRevisionResolveState out = (DefaultModuleRevisionResolveState) resolver.select(candidates, root);
-            if (out.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE) {
-                out.selectionReason = VersionSelectionReasons.CONFLICT_RESOLUTION_BY_RULE;
-            } else {
-                out.selectionReason = VersionSelectionReasons.CONFLICT_RESOLUTION;
-            }
-            return out;
+            return (DefaultModuleRevisionResolveState) resolver.select(candidates, root);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java
index 84871a5..6decc2c 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleRevisionResolveState.java
@@ -15,8 +15,14 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
 
+import org.gradle.api.artifacts.result.ModuleVersionSelectionReason;
+
 interface ModuleRevisionResolveState {
     String getId();
 
     String getRevision();
+
+    ModuleVersionSelectionReason getSelectionReason();
+
+    void setSelectionReason(ModuleVersionSelectionReason moduleVersionSelectionReason);
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java
index 533c3fe..f8c0471 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/ModuleVersionSpec.java
@@ -29,7 +29,9 @@ import java.util.*;
  *
  * <p>This class attempts to reduce execution time, by flattening union and intersection specs, at the cost of more analysis at construction time. This is taken advantage of by {@link
  * DependencyGraphBuilder}, on the assumption that there are many more edges in the dependency graph than there are exclude rules (ie we evaluate the rules much more often that we construct them).
- * </p> <p>Also, this class attempts to be quite accurate in determining if 2 specs will match exactly the same set of modules. {@link DependencyGraphBuilder} uses this to avoid traversing the
+ * </p>
+ *
+ * <p>Also, this class attempts to be quite accurate in determining if 2 specs will match exactly the same set of modules. {@link DependencyGraphBuilder} uses this to avoid traversing the
  * dependency graph of a particular version that has already been traversed when a new incoming edge is added (eg a newly discovered dependency) and when an incoming edge is removed (eg a conflict
  * evicts a version that depends on the given version). </p>
  */
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolver.java
new file mode 100644
index 0000000..cb0d3c2
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine;
+
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons;
+
+import java.util.Collection;
+
+/**
+* by Szczepan Faber, created at: 1/29/13
+*/
+public class VersionSelectionReasonResolver implements ModuleConflictResolver {
+
+    private final ModuleConflictResolver delegate;
+
+    public VersionSelectionReasonResolver(ModuleConflictResolver delegate) {
+        this.delegate = delegate;
+    }
+
+    public ModuleRevisionResolveState select(Collection<? extends ModuleRevisionResolveState> candidates, ModuleRevisionResolveState root) {
+        ModuleRevisionResolveState selected = delegate.select(candidates, root);
+        selected.setSelectionReason(VersionSelectionReasons.withConflictResolution(selected.getSelectionReason()));
+        return selected;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java
index 9a221f4..9b9e45a 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasons.java
@@ -27,7 +27,18 @@ public class VersionSelectionReasons {
     public static final ModuleVersionSelectionReason FORCED = new DefaultModuleVersionSelectionReason(true, false, false, "forced");
     public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION = new DefaultModuleVersionSelectionReason(false, true, false, "conflict resolution");
     public static final ModuleVersionSelectionReason SELECTED_BY_RULE = new DefaultModuleVersionSelectionReason(false, false, true, "selected by rule");
-    public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION_BY_RULE = new DefaultModuleVersionSelectionReason(false, true, true, "conflict resolution by rule");
+    public static final ModuleVersionSelectionReason CONFLICT_RESOLUTION_BY_RULE = new DefaultModuleVersionSelectionReason(false, true, true, "selected by rule and conflict resolution");
+
+    public static ModuleVersionSelectionReason withConflictResolution(ModuleVersionSelectionReason reason) {
+        if (reason.isConflictResolution()) {
+            return reason;
+        } else if (reason == SELECTED_BY_RULE) {
+            return CONFLICT_RESOLUTION_BY_RULE;
+        } else if (reason == REQUESTED) {
+            return CONFLICT_RESOLUTION;
+        }
+        throw new IllegalArgumentException("Cannot create conflict resolution selection reason for input: " + reason);
+    }
 
     private static class DefaultModuleVersionSelectionReason implements ModuleVersionSelectionReason {
 
@@ -60,7 +71,8 @@ public class VersionSelectionReasons {
             return description;
         }
 
-        //TODO At some point we want to provide information if version was requested in the graph.
-        //Perhaps a method like isRequested(). Not requested means that some particular version was forced but no dependency have requested this version.
+        public String toString() {
+            return description;
+        }
     }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository.java
new file mode 100644
index 0000000..84bb36f
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.repositories;
+
+import org.gradle.api.NamedDomainObjectCollection;
+import org.gradle.api.artifacts.repositories.ArtifactRepository;
+import org.gradle.util.DeprecationLogger;
+
+public abstract class AbstractArtifactRepository implements ArtifactRepositoryInternal, ResolutionAwareRepository, PublicationAwareRepository {
+
+    private String name;
+    private boolean isPartOfContainer;
+
+    public void onAddToContainer(NamedDomainObjectCollection<ArtifactRepository> container) {
+        isPartOfContainer = true;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        if (isPartOfContainer) {
+            DeprecationLogger.nagUserOfDeprecated("Changing the name of an ArtifactRepository that is part of a container", "Set the name when creating the repository");
+        }
+        this.name = name;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
index 84fdf55..c776957 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactory.java
@@ -120,7 +120,7 @@ public class DefaultBaseRepositoryFactory implements BaseRepositoryFactory {
     }
 
     public DependencyResolver toResolver(ArtifactRepository repository) {
-        return ((ArtifactRepositoryInternal) repository).createResolver();
+        return ((ArtifactRepositoryInternal) repository).createLegacyDslObject();
     }
 
     public FixedResolverArtifactRepository createResolverBackedRepository(DependencyResolver resolver) {
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepository.java
index d37651c..9ba4a83 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepository.java
@@ -21,6 +21,8 @@ import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.apache.ivy.plugins.resolver.FileSystemResolver;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyDependencyResolverAdapter;
 import org.gradle.api.internal.file.FileResolver;
 
 import java.io.File;
@@ -29,7 +31,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
-public class DefaultFlatDirArtifactRepository extends AbstractArtifactRepository implements FlatDirectoryArtifactRepository, ArtifactRepositoryInternal {
+public class DefaultFlatDirArtifactRepository extends AbstractArtifactRepository implements FlatDirectoryArtifactRepository {
     private final FileResolver fileResolver;
     private List<Object> dirs = new ArrayList<Object>();
     private final RepositoryCacheManager localCacheManager;
@@ -55,7 +57,15 @@ public class DefaultFlatDirArtifactRepository extends AbstractArtifactRepository
         this.dirs.addAll(Arrays.asList(dirs));
     }
 
-    public DependencyResolver createResolver() {
+    public DependencyResolver createPublisher() {
+        return createLegacyDslObject();
+    }
+
+    public IvyAwareModuleVersionRepository createResolver() {
+        return new IvyDependencyResolverAdapter(createLegacyDslObject());
+    }
+
+    public DependencyResolver createLegacyDslObject() {
         Set<File> dirs = getDirs();
         if (dirs.isEmpty()) {
             throw new InvalidUserDataException("You must specify at least one directory for a flat directory repository.");
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
index c356d64..0955405 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java
@@ -20,7 +20,10 @@ import org.apache.ivy.core.module.id.ArtifactRevisionId;
 import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepositoryMetaDataProvider;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ExternalResourceResolverAdapter;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
 import org.gradle.api.internal.artifacts.repositories.layout.*;
 import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver;
 import org.gradle.api.internal.artifacts.repositories.resolver.PatternBasedResolver;
@@ -35,13 +38,14 @@ import java.net.URI;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
-public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupportedRepository implements IvyArtifactRepository, ArtifactRepositoryInternal {
+public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupportedRepository implements IvyArtifactRepository {
     private Object baseUrl;
     private RepositoryLayout layout;
     private final AdditionalPatternsRepositoryLayout additionalPatternsLayout;
     private final FileResolver fileResolver;
     private final RepositoryTransportFactory transportFactory;
     private final LocallyAvailableResourceFinder<ArtifactRevisionId> locallyAvailableResourceFinder;
+    private final MetaDataProvider metaDataProvider;
     private final Instantiator instantiator;
 
     public DefaultIvyArtifactRepository(FileResolver fileResolver, PasswordCredentials credentials, RepositoryTransportFactory transportFactory,
@@ -52,17 +56,35 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
         this.locallyAvailableResourceFinder = locallyAvailableResourceFinder;
         this.additionalPatternsLayout = new AdditionalPatternsRepositoryLayout(fileResolver);
         this.layout = new GradleRepositoryLayout();
+        this.metaDataProvider = new MetaDataProvider();
         this.instantiator = instantiator;
     }
 
-    public DependencyResolver createResolver() {
+    public DependencyResolver createLegacyDslObject() {
+        IvyResolver resolver = createRealResolver();
+        return new LegacyDependencyResolver(resolver, wrapResolver(resolver));
+    }
+
+    public DependencyResolver createPublisher() {
+        return createRealResolver();
+    }
+
+    public IvyAwareModuleVersionRepository createResolver() {
+        return wrapResolver(createRealResolver());
+    }
+
+    private ExternalResourceResolverAdapter wrapResolver(IvyResolver resolver) {
+        return new ExternalResourceResolverAdapter(resolver, metaDataProvider.dynamicResolve);
+    }
+
+    protected IvyResolver createRealResolver() {
         URI uri = getUrl();
 
         Set<String> schemes = new LinkedHashSet<String>();
         layout.addSchemes(uri, schemes);
         additionalPatternsLayout.addSchemes(uri, schemes);
 
-        PatternBasedResolver resolver = createResolver(schemes);
+        IvyResolver resolver = createResolver(schemes);
 
         layout.apply(uri, resolver);
         additionalPatternsLayout.apply(uri, resolver);
@@ -70,12 +92,12 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
         return resolver;
     }
 
-    private PatternBasedResolver createResolver(Set<String> schemes) {
+    private IvyResolver createResolver(Set<String> schemes) {
         if (schemes.isEmpty()) {
             throw new InvalidUserDataException("You must specify a base url or at least one artifact pattern for an Ivy repository.");
         }
         if (!WrapUtil.toSet("http", "https", "file").containsAll(schemes)) {
-            throw new InvalidUserDataException("You may only specify 'file', 'http' and 'https' urls for an ivy repository.");
+            throw new InvalidUserDataException("You may only specify 'file', 'http' and 'https' urls for an Ivy repository.");
         }
         if (WrapUtil.toSet("http", "https").containsAll(schemes)) {
             return new IvyResolver(getName(), transportFactory.createHttpTransport(getName(), getCredentials()), locallyAvailableResourceFinder);
@@ -83,7 +105,7 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
         if (WrapUtil.toSet("file").containsAll(schemes)) {
             return new IvyResolver(getName(), transportFactory.createFileTransport(getName()), locallyAvailableResourceFinder);
         }
-        throw new InvalidUserDataException("You cannot mix file and http(s) urls for a single ivy repository. Please declare 2 separate repositories.");
+        throw new InvalidUserDataException("You cannot mix file and http(s) urls for a single Ivy repository. Please declare 2 separate repositories.");
     }
 
     public URI getUrl() {
@@ -117,6 +139,10 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
         ConfigureUtil.configure(config, layout);
     }
 
+    public IvyArtifactRepositoryMetaDataProvider getResolve() {
+        return metaDataProvider;
+    }
+
     /**
      * Layout for applying additional patterns added via {@link #artifactPatterns} and {@link #ivyPatterns}.
      */
@@ -153,4 +179,15 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte
         }
     }
 
+    private static class MetaDataProvider implements IvyArtifactRepositoryMetaDataProvider {
+        boolean dynamicResolve;
+
+        public boolean isDynamicMode() {
+            return dynamicResolve;
+        }
+
+        public void setDynamicMode(boolean mode) {
+            this.dynamicResolve = mode;
+        }
+    }
 }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java
index a57152c..60fe1b5 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java
@@ -21,6 +21,8 @@ import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
 import org.gradle.api.artifacts.repositories.PasswordCredentials;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ExternalResourceResolverAdapter;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
 import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory;
@@ -33,7 +35,7 @@ import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
-public class DefaultMavenArtifactRepository extends AbstractAuthenticationSupportedRepository implements MavenArtifactRepository, ArtifactRepositoryInternal {
+public class DefaultMavenArtifactRepository extends AbstractAuthenticationSupportedRepository implements MavenArtifactRepository {
     private final FileResolver fileResolver;
     private final RepositoryTransportFactory transportFactory;
     private Object url;
@@ -72,7 +74,24 @@ public class DefaultMavenArtifactRepository extends AbstractAuthenticationSuppor
         additionalUrls = Lists.newArrayList(urls);
     }
 
-    public DependencyResolver createResolver() {
+    public DependencyResolver createPublisher() {
+        return createRealResolver();
+    }
+
+    public DependencyResolver createLegacyDslObject() {
+        MavenResolver resolver = createRealResolver();
+        return new LegacyMavenResolver(resolver, wrapResolver(resolver));
+    }
+
+    public IvyAwareModuleVersionRepository createResolver() {
+        return wrapResolver(createRealResolver());
+    }
+
+    private ExternalResourceResolverAdapter wrapResolver(MavenResolver resolver) {
+        return new ExternalResourceResolverAdapter(resolver, false);
+    }
+
+    protected MavenResolver createRealResolver() {
         URI rootUri = getUrl();
         if (rootUri == null) {
             throw new InvalidUserDataException("You must specify a URL for a Maven repository.");
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java
new file mode 100644
index 0000000..d5ff8b0
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.repositories;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyDependencyResolverAdapter;
+
+public class FixedResolverArtifactRepository extends AbstractArtifactRepository {
+    protected final DependencyResolver resolver;
+
+    public FixedResolverArtifactRepository(DependencyResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public String getName() {
+        return resolver.getName();
+    }
+
+    public void setName(String name) {
+        resolver.setName(name);
+
+        // We are doing this because we are relying on the deprecation warning that
+        // AbstractArtifactRepository (super) issues. This is a bit awkward.
+        super.setName(name);
+    }
+
+    public DependencyResolver createPublisher() {
+        return resolver;
+    }
+
+    public IvyAwareModuleVersionRepository createResolver() {
+        // Handle a repository wrapped in a resolver for backwards compatibility
+        if (resolver instanceof ResolutionAwareRepository) {
+            ResolutionAwareRepository resolutionAwareRepository = (ResolutionAwareRepository) resolver;
+            return resolutionAwareRepository.createResolver();
+        }
+        return new IvyDependencyResolverAdapter(resolver);
+    }
+
+    public DependencyResolver createLegacyDslObject() {
+        return resolver;
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyDependencyResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyDependencyResolver.java
new file mode 100644
index 0000000..afca9f7
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyDependencyResolver.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.repositories;
+
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.search.ModuleEntry;
+import org.apache.ivy.core.search.OrganisationEntry;
+import org.apache.ivy.core.search.RevisionEntry;
+import org.apache.ivy.plugins.latest.LatestStrategy;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A wrapper over a {@link org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver} that is exposed through the DSL,
+ * for backwards compatibility.
+ */
+public class LegacyDependencyResolver implements DependencyResolver, ResolutionAwareRepository {
+    private final ExternalResourceResolver resolver;
+    private final IvyAwareModuleVersionRepository repository;
+
+    public LegacyDependencyResolver(ExternalResourceResolver resolver, IvyAwareModuleVersionRepository repository) {
+        this.resolver = resolver;
+        this.repository = repository;
+    }
+
+    public String getName() {
+        return resolver.getName();
+    }
+
+    public void setName(String name) {
+        resolver.setName(name);
+    }
+
+    public String toString() {
+        return resolver.toString();
+    }
+
+    public IvyAwareModuleVersionRepository createResolver() {
+        return repository;
+    }
+
+    public void setSettings(ResolverSettings ivy) {
+        resolver.setSettings(ivy);
+    }
+
+    public ResolverSettings getSettings() {
+        return resolver.getSettings();
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
+        // This is not used
+        throw new UnsupportedOperationException();
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        // This is not used
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean exists(Artifact artifact) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public ArtifactDownloadReport download(ArtifactOrigin origin, DownloadOptions options) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public void reportFailure() {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public void reportFailure(Artifact art) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry mod) {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public Namespace getNamespace() {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+
+    public void addIvyPattern(String pattern) {
+        resolver.addIvyPattern(pattern);
+    }
+
+    public void addArtifactPattern(String pattern) {
+        resolver.addArtifactPattern(pattern);
+    }
+
+    public List<String> getIvyPatterns() {
+        return resolver.getIvyPatterns();
+    }
+
+    public List<String> getArtifactPatterns() {
+        return resolver.getArtifactPatterns();
+    }
+
+    public void dumpSettings() {
+        // this is not used
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean isM2compatible() {
+        return resolver.isM2compatible();
+    }
+
+    public void setM2compatible(boolean compatible) {
+        resolver.setM2compatible(compatible);
+    }
+
+    public boolean isCheckconsistency() {
+        return resolver.isCheckconsistency();
+    }
+
+    public void setCheckconsistency(boolean checkConsistency) {
+        resolver.setCheckconsistency(checkConsistency);
+    }
+
+    public void setForce(boolean force) {
+        resolver.setForce(force);
+    }
+
+    public boolean isForce() {
+        return resolver.isForce();
+    }
+
+    public boolean isAllownomd() {
+        return resolver.isAllownomd();
+    }
+
+    public void setAllownomd(boolean b) {
+        resolver.setAllownomd(b);
+    }
+
+    /**
+     * Sets the module descriptor presence rule.
+     * Should be one of {@link org.apache.ivy.plugins.resolver.BasicResolver#DESCRIPTOR_REQUIRED} or {@link org.apache.ivy.plugins.resolver.BasicResolver#DESCRIPTOR_OPTIONAL}.
+     *
+     * @param descriptorRule the descriptor rule to use with this resolver.
+     */
+    public void setDescriptor(String descriptorRule) {
+        resolver.setDescriptor(descriptorRule);
+    }
+
+    public String[] getChecksumAlgorithms() {
+        return resolver.getChecksumAlgorithms();
+    }
+
+    public void setChecksums(String checksums) {
+        resolver.setChecksums(checksums);
+    }
+
+    public LatestStrategy getLatestStrategy() {
+        return resolver.getLatestStrategy();
+    }
+
+    public void setLatestStrategy(LatestStrategy latestStrategy) {
+        resolver.setLatestStrategy(latestStrategy);
+    }
+
+    public void setLatest(String strategyName) {
+        resolver.setLatest(strategyName);
+    }
+
+    public String getLatest() {
+        return resolver.getLatest();
+    }
+
+    public void setChangingMatcher(String changingMatcherName) {
+        resolver.setChangingMatcher(changingMatcherName);
+    }
+
+    protected String getChangingMatcherName() {
+        return resolver.getChangingMatcherName();
+    }
+
+    public void setChangingPattern(String changingPattern) {
+        resolver.setChangingPattern(changingPattern);
+    }
+
+    protected String getChangingPattern() {
+        return resolver.getChangingPattern();
+    }
+
+    public void setCheckmodified(boolean check) {
+        resolver.setCheckmodified(check);
+    }
+
+    public RepositoryCacheManager getRepositoryCacheManager() {
+        // This is never used
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyMavenResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyMavenResolver.java
new file mode 100644
index 0000000..f409088
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/LegacyMavenResolver.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.repositories;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver;
+
+public class LegacyMavenResolver extends LegacyDependencyResolver {
+    private final MavenResolver resolver;
+
+    public LegacyMavenResolver(MavenResolver resolver, IvyAwareModuleVersionRepository repository) {
+        super(resolver, repository);
+        this.resolver = resolver;
+    }
+
+    // A bunch of configuration properties that we don't (yet) support in our model via the DSL. Users can still tweak these on the resolver using mavenRepo().
+    public boolean isUsepoms() {
+        return resolver.isUsepoms();
+    }
+
+    public void setUsepoms(boolean usepoms) {
+        resolver.setUsepoms(usepoms);
+    }
+
+    public boolean isUseMavenMetadata() {
+        return resolver.isUseMavenMetadata();
+    }
+
+    public void setUseMavenMetadata(boolean useMavenMetadata) {
+        resolver.setUseMavenMetadata(useMavenMetadata);
+    }
+
+    public String getPattern() {
+        return resolver.getPattern();
+    }
+
+    public void setPattern(String pattern) {
+        resolver.setPattern(pattern);
+    }
+
+    public String getRoot() {
+        return resolver.getRoot();
+    }
+
+    public void setRoot(String root) {
+        resolver.setRoot(root);
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ResolutionAwareRepository.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ResolutionAwareRepository.java
new file mode 100644
index 0000000..ceb2598
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ResolutionAwareRepository.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.repositories;
+
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
+
+public interface ResolutionAwareRepository {
+    IvyAwareModuleVersionRepository createResolver();
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java
index 85268b6..d87c553 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/cachemanager/AbstractRepositoryCacheManager.java
@@ -37,6 +37,7 @@ import org.gradle.internal.UncheckedException;
 
 import java.io.File;
 import java.io.IOException;
+import java.net.URL;
 import java.text.ParseException;
 
 abstract class AbstractRepositoryCacheManager implements RepositoryCacheManager {
@@ -77,7 +78,7 @@ abstract class AbstractRepositoryCacheManager implements RepositoryCacheManager
             IvySettings ivySettings = IvyContextualiser.getIvyContext().getSettings();
             ParserSettings parserSettings = new ModuleScopedParserSettings(ivySettings, resolver, moduleRevisionId);
             ModuleDescriptorParser parser = parserRegistry.forResource(resource);
-            return parser.parseDescriptor(parserSettings, artifactFile.toURI().toURL(), resource, options.isValidate());
+            return parser.parseDescriptor(parserSettings, new URL(artifactFile.toURI().toASCIIString()), resource, options.isValidate());
         } catch (IOException e) {
             throw UncheckedException.throwAsUncheckedException(e);
         }
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
index 6be0379..3dfdda8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/ExternalResourceResolver.java
@@ -54,7 +54,7 @@ import org.apache.ivy.util.Message;
 import org.gradle.api.internal.artifacts.ivyservice.BuildableArtifactResolveResult;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ArtifactResolveException;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
 import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
 import org.gradle.api.internal.externalresource.ExternalResource;
@@ -148,7 +148,7 @@ public class ExternalResourceResolver implements DependencyResolver {
         throw new UnsupportedOperationException();
     }
 
-    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionDescriptor result) {
+    public void getDependency(DependencyDescriptor dependencyDescriptor, BuildableModuleVersionMetaData result) {
         ModuleRevisionId nsMrid = dependencyDescriptor.getDependencyRevisionId();
 
         boolean isDynamic = getVersionMatcher().isDynamic(nsMrid);
@@ -817,7 +817,7 @@ public class ExternalResourceResolver implements DependencyResolver {
         this.changingMatcherName = changingMatcherName;
     }
 
-    protected String getChangingMatcherName() {
+    public String getChangingMatcherName() {
         return changingMatcherName;
     }
 
@@ -825,7 +825,7 @@ public class ExternalResourceResolver implements DependencyResolver {
         this.changingPattern = changingPattern;
     }
 
-    protected String getChangingPattern() {
+    public String getChangingPattern() {
         return changingPattern;
     }
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
index b80d0d3..71eb83f 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/artifacts/repositories/resolver/MavenResolver.java
@@ -23,7 +23,7 @@ import org.apache.ivy.core.module.id.ModuleRevisionId;
 import org.apache.ivy.plugins.matcher.PatternMatcher;
 import org.apache.ivy.plugins.resolver.util.ResolvedResource;
 import org.apache.ivy.plugins.resolver.util.ResourceMDParser;
-import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionDescriptor;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.BuildableModuleVersionMetaData;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleSource;
 import org.gradle.api.internal.artifacts.repositories.cachemanager.EnhancedArtifactDownloadReport;
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport;
@@ -69,7 +69,7 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         updatePatterns();
     }
 
-    public void getDependency(DependencyDescriptor dd, BuildableModuleVersionDescriptor result) {
+    public void getDependency(DependencyDescriptor dd, BuildableModuleVersionMetaData result) {
         if (isSnapshotVersion(dd)) {
             getSnapshotDependency(dd, result);
         } else {
@@ -77,14 +77,14 @@ public class MavenResolver extends ExternalResourceResolver implements PatternBa
         }
     }
 
-    private void getSnapshotDependency(DependencyDescriptor dd, BuildableModuleVersionDescriptor result) {
+    private void getSnapshotDependency(DependencyDescriptor dd, BuildableModuleVersionMetaData result) {
         final ModuleRevisionId dependencyRevisionId = dd.getDependencyRevisionId();
         final String uniqueSnapshotVersion = findUniqueSnapshotVersion(dependencyRevisionId);
         if (uniqueSnapshotVersion != null) {
             DependencyDescriptor enrichedDependencyDescriptor = enrichDependencyDescriptorWithSnapshotVersionInfo(dd, dependencyRevisionId, uniqueSnapshotVersion);
             super.getDependency(enrichedDependencyDescriptor, result);
-            if (result.getState() == BuildableModuleVersionDescriptor.State.Resolved) {
-                result.resolved(result.getDescriptor(), result.isChanging(), new TimestampedModuleSource(uniqueSnapshotVersion));
+            if (result.getState() == BuildableModuleVersionMetaData.State.Resolved) {
+                result.setModuleSource(new TimestampedModuleSource(uniqueSnapshotVersion));
             }
         } else {
             super.getDependency(dd, result);
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java
deleted file mode 100644
index d8027e2..0000000
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParser.java
+++ /dev/null
@@ -1,48 +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.notations;
-
-import org.gradle.api.artifacts.ClientModule;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
-import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.TopLevelNotationParser;
-
-import java.util.Collection;
-
-/**
- * @author Hans Dockter
- */
-public class ClientModuleNotationParser implements TopLevelNotationParser, NotationParser<ClientModule> {
-
-    private final NotationParser<ClientModule> delegate;
-
-    public ClientModuleNotationParser(Instantiator instantiator) {
-        delegate = new NotationParserBuilder<ClientModule>()
-                .resultingType(ClientModule.class)
-                .parser(new DependencyStringNotationParser<DefaultClientModule>(instantiator, DefaultClientModule.class))
-                .parser(new DependencyMapNotationParser<DefaultClientModule>(instantiator, DefaultClientModule.class))
-                .toComposite();
-    }
-
-    public void describe(Collection<String> candidateFormats) {
-        delegate.describe(candidateFormats);
-    }
-
-    public ClientModule parseNotation(Object notation) {
-        return delegate.parseNotation(notation);
-    }
-}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParserFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParserFactory.java
new file mode 100644
index 0000000..a4451a1
--- /dev/null
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ClientModuleNotationParserFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.notations;
+
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * @author Hans Dockter
+ */
+public class ClientModuleNotationParserFactory implements Factory<NotationParser<ClientModule>> {
+
+    private final Instantiator instantiator;
+
+    public ClientModuleNotationParserFactory(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    public NotationParser<ClientModule> create() {
+        return new NotationParserBuilder<ClientModule>()
+                .resultingType(ClientModule.class)
+                .parser(new DependencyStringNotationParser<DefaultClientModule>(instantiator, DefaultClientModule.class))
+                .parser(new DependencyMapNotationParser<DefaultClientModule>(instantiator, DefaultClientModule.class))
+                .toComposite();
+
+    }
+}
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java
index 591d8f8..3f019dd 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyNotationParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,14 +19,13 @@ package org.gradle.api.internal.notations;
 import org.gradle.api.GradleException;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.internal.notations.api.NotationParser;
-import org.gradle.api.internal.notations.api.TopLevelNotationParser;
 
 import java.util.Collection;
 
 /**
  * by Szczepan Faber, created at: 11/8/11
  */
-public class DependencyNotationParser implements TopLevelNotationParser, NotationParser<Dependency> {
+public class DependencyNotationParser implements NotationParser<Dependency> {
 
     private final NotationParser<Dependency> delegate;
 
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
index 9b1d318..f4e0cd8 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/DependencyProjectNotationParser.java
@@ -18,9 +18,7 @@ package org.gradle.api.internal.notations;
 
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
-import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.artifacts.DefaultProjectDependencyFactory;
 import org.gradle.api.internal.notations.parsers.TypedNotationParser;
 
 import java.util.Collection;
@@ -30,13 +28,11 @@ import java.util.Collection;
  */
 public class DependencyProjectNotationParser extends TypedNotationParser<Project, ProjectDependency> {
 
-    private final ProjectDependenciesBuildInstruction instruction;
-    private final Instantiator instantiator;
+    private final DefaultProjectDependencyFactory factory;
 
-    public DependencyProjectNotationParser(ProjectDependenciesBuildInstruction instruction, Instantiator instantiator) {
+    public DependencyProjectNotationParser(DefaultProjectDependencyFactory factory) {
         super(Project.class);
-        this.instruction = instruction;
-        this.instantiator = instantiator;
+        this.factory = factory;
     }
 
     @Override
@@ -45,6 +41,6 @@ public class DependencyProjectNotationParser extends TypedNotationParser<Project
     }
 
     public ProjectDependency parseType(Project notation) {
-        return instantiator.newInstance(DefaultProjectDependency.class, notation, instruction);
+        return factory.create(notation);
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
index 0c9b153..9995133 100644
--- a/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
+++ b/subprojects/core-impl/src/main/groovy/org/gradle/api/internal/notations/ProjectDependencyFactory.java
@@ -16,40 +16,46 @@
 package org.gradle.api.internal.notations;
 
 import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
-import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.artifacts.DefaultProjectDependencyFactory;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
-import org.gradle.util.ConfigureUtil;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.tasks.Optional;
 
-import java.util.HashMap;
+import java.util.Collection;
 import java.util.Map;
 
 /**
  * @author Hans Dockter
  */
 public class ProjectDependencyFactory {
-    private final ProjectDependenciesBuildInstruction instruction;
-    private final Instantiator instantiator;
+    private final DefaultProjectDependencyFactory factory;
 
-    public ProjectDependencyFactory(ProjectDependenciesBuildInstruction instruction, Instantiator instantiator) {
-        this.instruction = instruction;
-        this.instantiator = instantiator;
+    public ProjectDependencyFactory(DefaultProjectDependencyFactory factory) {
+        this.factory = factory;
     }
 
     public ProjectDependency createFromMap(ProjectFinder projectFinder,
                                            Map<? extends String, ? extends Object> map) {
-        Map<String, Object> args = new HashMap<String, Object>(map);
-        String path = getAndRemove(args, "path");
-        String configuration = getAndRemove(args, "configuration");
-        ProjectDependency dependency = instantiator.newInstance(DefaultProjectDependency.class, projectFinder.getProject(path), configuration, instruction);
-        ConfigureUtil.configureByMap(args, dependency);
-        return dependency;
+        return new ProjectDependencyMapNotationParser(projectFinder, factory).parseNotation(map);
     }
 
-    private String getAndRemove(Map<String, Object> args, String key) {
-        Object value = args.get(key);
-        args.remove(key);
-        return value != null ? value.toString() : null;
+    static class ProjectDependencyMapNotationParser extends MapNotationParser<ProjectDependency> {
+
+        private final ProjectFinder projectFinder;
+        private final DefaultProjectDependencyFactory factory;
+
+        public ProjectDependencyMapNotationParser(ProjectFinder projectFinder, DefaultProjectDependencyFactory factory) {
+            this.projectFinder = projectFinder;
+            this.factory = factory;
+        }
+
+        protected ProjectDependency parseMap(@MapKey("path") String path, @Optional @MapKey("configuration") String configuration) {
+            return factory.create(projectFinder.getProject(path), configuration);
+        }
+
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Map with mandatory 'path' and optional 'configuration' key, e.g. [path: ':someProj', configuration: 'someConf']");
+        }
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
index 82361c4..9c33caf 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultDependencyManagementServicesTest.groovy
@@ -30,6 +30,7 @@ import org.gradle.cache.CacheRepository
 import org.gradle.cache.DirectoryCacheBuilder
 import org.gradle.cache.PersistentCache
 import org.gradle.cache.internal.FileLockManager
+import org.gradle.initialization.ProjectAccessListener
 import org.gradle.internal.Factory
 import org.gradle.internal.TimeProvider
 import org.gradle.internal.reflect.Instantiator
@@ -65,6 +66,7 @@ class DefaultDependencyManagementServicesTest extends Specification {
         _ * parent.get(FileLockManager) >> Mock(FileLockManager)
         _ * parent.get(TimeProvider) >> Mock(TimeProvider)
         _ * parent.get(TemporaryFileProvider) >> Mock(TemporaryFileProvider)
+        _ * parent.get(ProjectAccessListener) >> Mock(ProjectAccessListener)
     }
 
     private CacheRepository initCacheRepository() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelectorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelectorTest.groovy
new file mode 100644
index 0000000..bf1032d
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/DefaultModuleVersionSelectorTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+/**
+ * by Szczepan Faber, created at: 2/11/13
+ */
+class DefaultModuleVersionSelectorTest extends Specification {
+
+    def "equality"() {
+        def selector = newSelector("org", "util", "1.0")
+
+        def same = newSelector("org", "util", "1.0")
+        def diffGroup = newSelector("foo", "util", "1.0")
+        def diffName = newSelector("org", "foo", "1.0")
+        def diffVersion = newSelector("org", "util", "2.0")
+
+        expect:
+        selector == same
+        selector != diffGroup
+        selector != diffName
+        selector != diffVersion
+    }
+
+    def "knows if matches the id"() {
+        def selector = newSelector("org", "util", "1.0")
+        def matching = newId("org", "util", "1.0")
+
+        def differentGroup = newId("xorg", "util", "1.0")
+        def differentName = newId("org", "xutil", "1.0")
+        def differentVersion = newId("org", "xutil", "x1.0")
+
+        expect:
+        selector.matchesStrictly(matching)
+
+        !selector.matchesStrictly(differentGroup)
+        !selector.matchesStrictly(differentName)
+        !selector.matchesStrictly(differentVersion)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
index 22e889c..7c63a99 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerSpec.groovy
@@ -13,21 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.artifacts.configurations;
-
+package org.gradle.api.internal.artifacts.configurations
 
 import org.gradle.api.artifacts.UnknownConfigurationException
 import org.gradle.api.internal.DomainObjectContext
-import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.ConfigurationResolver
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
 import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.listener.ListenerManager
-import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
 public class DefaultConfigurationContainerSpec extends Specification {
 
-    private ArtifactDependencyResolver dependencyResolver = Mock()
+    private ConfigurationResolver resolver = Mock()
     private Instantiator instantiator = Mock()
     private DomainObjectContext domainObjectContext = Mock()
     private ListenerManager listenerManager = Mock()
@@ -36,7 +35,7 @@ public class DefaultConfigurationContainerSpec extends Specification {
     def ConfigurationInternal conf = Mock()
 
     private DefaultConfigurationContainer configurationContainer = new DefaultConfigurationContainer(
-            dependencyResolver, instantiator, domainObjectContext,
+            resolver, instantiator, domainObjectContext,
             listenerManager, metaDataProvider);
 
     def "adds and gets"() {
@@ -44,7 +43,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,
-                dependencyResolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal) >> conf
+                resolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal) >> conf
 
         when:
         def compile = configurationContainer.add("compile")
@@ -64,7 +63,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,
-                dependencyResolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal) >> conf
+                resolver, listenerManager, metaDataProvider, _ as ResolutionStrategyInternal) >> conf
 
         when:
         def compile = configurationContainer.add("compile") {
@@ -77,17 +76,17 @@ public class DefaultConfigurationContainerSpec extends Specification {
 
         //finds configurations
         configurationContainer.findByName("compile") == compile
-        configurationContainer.findByName("fooo") == null
+        configurationContainer.findByName("foo") == null
         configurationContainer.findAll { it.name == "compile" } as Set == [compile] as Set
-        configurationContainer.findAll { it.name == "fooo" } as Set == [] as Set
+        configurationContainer.findAll { it.name == "foo" } as Set == [] as Set
 
         configurationContainer as List == [compile] as List
     }
 
     def "creates detached"() {
         given:
-        def dependency1 = HelperUtil.createDependency("group1", "name1", "version1");
-        def dependency2 = HelperUtil.createDependency("group2", "name2", "version2");
+        def dependency1 = new DefaultExternalModuleDependency("group", "name", "version")
+        def dependency2 = new DefaultExternalModuleDependency("group", "name2", "version")
 
         when:
         def detached = configurationContainer.detachedConfiguration(dependency1, dependency2);
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
index ad10616..1cd233a 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.api.internal.AsmBackedClassGenerator
 import org.gradle.api.internal.ClassGeneratorBackedInstantiator
 import org.gradle.api.internal.DomainObjectContext
 import org.gradle.api.internal.MissingMethodException
-import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.ConfigurationResolver
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.listener.ListenerManager
@@ -43,12 +43,12 @@ import static org.junit.Assert.assertThat
 class DefaultConfigurationContainerTest {
     private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
 
-    private ArtifactDependencyResolver dependencyResolver = context.mock(ArtifactDependencyResolver)
+    private ConfigurationResolver resolver = context.mock(ConfigurationResolver)
     private ListenerManager listenerManager = context.mock(ListenerManager.class)
     private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class)
     private Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
     private DefaultConfigurationContainer configurationHandler = instantiator.newInstance(DefaultConfigurationContainer.class,
-            dependencyResolver, instantiator, { name -> name } as DomainObjectContext,
+            resolver, instantiator, { name -> name } as DomainObjectContext,
             listenerManager, metaDataProvider)
 
     @Before
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
index e71010d..abe706e 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationSpec.groovy
@@ -18,7 +18,7 @@ package org.gradle.api.internal.artifacts.configurations
 import org.gradle.api.Action
 import org.gradle.api.Task
 import org.gradle.api.artifacts.result.ResolutionResult
-import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
+import org.gradle.api.internal.artifacts.ConfigurationResolver
 import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
 import org.gradle.api.tasks.TaskDependency
@@ -30,13 +30,13 @@ import org.gradle.api.artifacts.*
 class DefaultConfigurationSpec extends Specification {
 
     ConfigurationsProvider configurationsProvider = Mock()
-    ArtifactDependencyResolver dependencyResolver = Mock()
+    ConfigurationResolver resolver = Mock()
     ListenerManager listenerManager = Mock()
     DependencyMetaDataProvider metaDataProvider = Mock()
     ResolutionStrategyInternal resolutionStrategy = Mock()
 
     DefaultConfiguration conf(String confName = "conf", String path = ":conf") {
-        new DefaultConfiguration(path, confName, configurationsProvider, dependencyResolver, listenerManager, metaDataProvider, resolutionStrategy)
+        new DefaultConfiguration(path, confName, configurationsProvider, resolver, listenerManager, metaDataProvider, resolutionStrategy)
     }
 
     DefaultPublishArtifact artifact(String name) {
@@ -56,7 +56,7 @@ class DefaultConfigurationSpec extends Specification {
     }
 
     // You need to wrap this in an interaction {} block when calling it
-    ResolvedConfiguration resolvedConfiguration(Configuration config, ArtifactDependencyResolver dependencyResolver = dependencyResolver) {
+    ResolvedConfiguration resolvedConfiguration(Configuration config, ConfigurationResolver dependencyResolver = resolver) {
         ResolvedConfiguration resolvedConfiguration = Mock()
         1 * dependencyResolver.resolve(config) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
         resolvedConfiguration
@@ -180,7 +180,7 @@ class DefaultConfigurationSpec extends Specification {
 
         then:
         interaction { resolvedConfiguration(config) }
-        0 * dependencyResolver._
+        0 * resolver._
     }
 
     def "incoming dependencies set depends on all self resolving dependencies"() {
@@ -301,4 +301,16 @@ class DefaultConfigurationSpec extends Specification {
         conf.resolutionStrategy != copy.resolutionStrategy
         copy.resolutionStrategy == strategy
     }
+
+    def "provides resolution result"() {
+        def config = conf("conf")
+        def result = Mock(ResolutionResult)
+
+        when:
+        def out = config.incoming.resolutionResult
+
+        then:
+        1 * resolver.resolve(config) >> new ResolverResults(Mock(ResolvedConfiguration), result)
+        out == result
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
index 9ad59ca..5384568 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
@@ -24,6 +24,7 @@ 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.publish.DefaultPublishArtifact;
 import org.gradle.api.specs.Spec;
@@ -52,7 +53,7 @@ import static org.junit.Assert.*;
 @RunWith(JMock.class)
 public class DefaultConfigurationTest {
     private JUnit4Mockery context = new JUnit4GroovyMockery();
-    private ArtifactDependencyResolver dependencyResolver = context.mock(ArtifactDependencyResolver.class);
+    private ConfigurationResolver dependencyResolver = context.mock(ConfigurationResolver.class);
     private ConfigurationsProvider configurationContainer;
     private ListenerManager listenerManager = context.mock(ListenerManager.class);
     private DependencyMetaDataProvider metaDataProvider = context.mock(DependencyMetaDataProvider.class);
@@ -239,8 +240,8 @@ public class DefaultConfigurationTest {
 
     @Test
     public void fileCollectionWithDependencies() {
-        Dependency dependency1 = HelperUtil.createDependency("group1", "name", "version");
-        Dependency dependency2 = HelperUtil.createDependency("group2", "name", "version");
+        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),
@@ -258,7 +259,6 @@ public class DefaultConfigurationTest {
         assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
     }
 
-
     @Test
     public void fileCollectionWithSpec() {
         @SuppressWarnings("unchecked")
@@ -282,9 +282,9 @@ public class DefaultConfigurationTest {
         Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
         DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
                 configuration.fileCollection(closure);
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(HelperUtil.createDependency("group1", "name", "version")),
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(createDependency("group1", "name", "version")),
                 equalTo(true));
-        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(HelperUtil.createDependency("group2", "name", "version")),
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(createDependency("group2", "name", "version")),
                 equalTo(false));
     }
 
@@ -369,7 +369,7 @@ public class DefaultConfigurationTest {
         final TaskDependency otherArtifactTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
         final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
         final PublishArtifactSet inheritedArtifacts = new DefaultPublishArtifactSet("artifacts", toDomainObjectSet(PublishArtifact.class, otherArtifact));
-        DefaultPublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        DefaultPublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
         artifact.builtBy(artifactTaskMock);
         configuration.getArtifacts().add(artifact);
 
@@ -623,8 +623,8 @@ public class DefaultConfigurationTest {
 
     @Test
     public void getAllDependencies() {
-        Dependency dependencyConf = HelperUtil.createDependency("group1", "name1", "version1");
-        Dependency dependencyOtherConf1 = HelperUtil.createDependency("group1", "name1", "version1");
+        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);
@@ -662,8 +662,8 @@ public class DefaultConfigurationTest {
 
     @Test
     public void getAllArtifacts() {
-        PublishArtifact artifactConf = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
-        PublishArtifact artifactOtherConf2 = HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2");
+        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);
@@ -674,7 +674,7 @@ public class DefaultConfigurationTest {
     @Test
     public void artifactAddedAction() {
         final TestClosure closure = context.mock(TestClosure.class);
-        final PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        final PublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
 
         context.checking(new Expectations() {{
             one(closure).call(artifact);
@@ -687,7 +687,7 @@ public class DefaultConfigurationTest {
     @Test
     public void artifactRemovedAction() {
         final TestClosure closure = context.mock(TestClosure.class);
-        final PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        final PublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
 
         configuration.getArtifacts().add(artifact);
 
@@ -702,7 +702,7 @@ public class DefaultConfigurationTest {
 
     @Test
     public void removeArtifact() {
-        PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        PublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
         configuration.getArtifacts().add(artifact);
         configuration.getArtifacts().remove(artifact);
         assertThat(configuration.getAllArtifacts(), Matchers.isEmpty());
@@ -710,9 +710,9 @@ public class DefaultConfigurationTest {
 
     @Test
     public void removeArtifactWithUnknownArtifact() {
-        PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        PublishArtifact artifact = createPublishArtifact("name1", "ext1", "type1", "classifier1");
         configuration.getArtifacts().add(artifact);
-        configuration.getArtifacts().remove(HelperUtil.createPublishArtifact("name2", "ext1", "type1", "classifier1"));
+        configuration.getArtifacts().remove(createPublishArtifact("name2", "ext1", "type1", "classifier1"));
         assertThat(configuration.getAllArtifacts(), hasSameItems(toSet(artifact)));
     }
 
@@ -738,7 +738,7 @@ public class DefaultConfigurationTest {
     public void copyWithSpec() {
         prepareConfigurationForCopyTest();
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
-        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
+        configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
 
         Configuration copiedConfiguration = configuration.copy(new Spec<Dependency>() {
             public boolean isSatisfiedBy(Dependency element) {
@@ -753,7 +753,7 @@ public class DefaultConfigurationTest {
     public void copyWithClosure() {
         prepareConfigurationForCopyTest();
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
-        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
+        configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
 
         Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
         Configuration copiedConfiguration = configuration.copy(specClosure);
@@ -767,10 +767,10 @@ public class DefaultConfigurationTest {
         configuration.setDescription("descript");
         configuration.exclude(toMap("group", "value"));
         configuration.exclude(toMap("group", "value2"));
-        configuration.getArtifacts().add(HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1"));
-        configuration.getArtifacts().add(HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2"));
-        configuration.getDependencies().add(HelperUtil.createDependency("group1", "name1", "version1"));
-        configuration.getDependencies().add(HelperUtil.createDependency("group2", "name2", "version2"));
+        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) {
@@ -798,7 +798,7 @@ public class DefaultConfigurationTest {
     public void copyRecursiveWithSpec() {
         prepareConfigurationForCopyRecursiveTest();
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
-        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
+        configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
 
         Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
         Configuration copiedConfiguration = configuration.copyRecursive(specClosure);
@@ -810,7 +810,7 @@ public class DefaultConfigurationTest {
     public void copyRecursiveWithClosure() {
         prepareConfigurationForCopyRecursiveTest();
         Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
-        configuration.getDependencies().add(HelperUtil.createDependency("group3", "name3", "version3"));
+        configuration.getDependencies().add(createDependency("group3", "name3", "version3"));
 
         Configuration copiedConfiguration = configuration.copyRecursive(new Spec<Dependency>() {
             public boolean isSatisfiedBy(Dependency element) {
@@ -823,8 +823,8 @@ public class DefaultConfigurationTest {
 
     private void prepareConfigurationForCopyRecursiveTest() {
         prepareConfigurationForCopyTest();
-        Dependency similarDependency2InOtherConf = HelperUtil.createDependency("group2", "name2", "version2");
-        Dependency otherConfDependency = HelperUtil.createDependency("group4", "name4", "version4");
+        Dependency similarDependency2InOtherConf = createDependency("group2", "name2", "version2");
+        Dependency otherConfDependency = createDependency("group4", "name4", "version4");
         Configuration otherConf = createNamedConfiguration("otherConf");
         otherConf.getDependencies().add(similarDependency2InOtherConf);
         otherConf.getDependencies().add(otherConfDependency);
@@ -914,9 +914,9 @@ public class DefaultConfigurationTest {
     
     @Test
     public void dumpString() {
-        Dependency configurationDependency = HelperUtil.createDependency("dumpgroup1", "dumpname1", "dumpversion1");
-        Dependency otherConfSimilarDependency = HelperUtil.createDependency("dumpgroup1", "dumpname1", "dumpversion1");
-        Dependency otherConfDependency = HelperUtil.createDependency("dumpgroup2", "dumpname2", "dumpversion2");
+        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);
@@ -940,6 +940,14 @@ public class DefaultConfigurationTest {
                 + "\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();
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFileTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFileTest.groovy
new file mode 100644
index 0000000..878e386
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ArtifactFileTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.dsl
+
+import spock.lang.Specification
+/**
+ * @author Hans Dockter
+ */
+public class ArtifactFileTest extends Specification {
+    final String module = '1.2'
+
+    def "determines name and extension from file name"() {
+        when:
+        def artifactFile = new ArtifactFile(new File(inputFileName), module)
+
+        then:
+        artifactFile.name == name
+        artifactFile.extension == extension
+        artifactFile.classifier == null
+
+        where:
+        inputFileName       | name              | extension
+        "some-file.zip"     | "some-file"       | "zip"
+        "some-file.zip.zip" | "some-file.zip"   | "zip"
+        ".zip"              | ""                | "zip"
+        "some-file"         | "some-file"       | null
+    }
+
+    def "removes module version from file name"() {
+        when:
+        def artifactFile = new ArtifactFile(new File(inputFileName), module)
+
+        then:
+        artifactFile.name == name
+        artifactFile.extension == extension
+        artifactFile.classifier == null
+
+        where:
+        inputFileName            | name                 | extension
+        "some-file-1.2.zip"      | "some-file"          | "zip"
+        "some-file-1.2-1.2.zip"  | "some-file-1.2"      | "zip"
+        "some-file-1.2"          | "some-file"          | null
+        "some-file-1.22.zip"     | "some-file-1.22"     | "zip"
+        "some-file-1.22.zip.zip" | "some-file-1.22.zip" | "zip"
+    }
+    
+    def "determines classifier from file name"() {
+        when:
+        def artifactFile = new ArtifactFile(new File(inputFileName), module)
+
+        then:
+        artifactFile.name == name
+        artifactFile.extension == extension
+        artifactFile.classifier == classifier
+
+        where:
+        inputFileName                      | name                       | classifier   | extension
+        "some-file-1.2-classifier.jar"     | "some-file"                | "classifier" | "jar"
+        "some-file-1.2-classifier-1.2.jar" | "some-file-1.2-classifier" | null         | "jar"
+        "-1.2-classifier.jar"              | ""                         | "classifier" | "jar"
+        "some-file-1.2-classifier"         | "some-file"                | "classifier" | null
+        "some-file-1.2-.jar"               | "some-file"                | null         | "jar"
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
deleted file mode 100644
index 4d50f9a..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.dsl;
-
-
-import java.awt.Point
-import org.gradle.api.InvalidUserDataException
-import org.gradle.api.Task
-import org.gradle.api.artifacts.Module
-import org.gradle.api.artifacts.PublishArtifact
-import org.gradle.internal.reflect.Instantiator
-import org.gradle.api.internal.ThreadGlobalInstantiator
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
-import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
-import org.gradle.api.tasks.bundling.AbstractArchiveTask
-import spock.lang.Specification
-
-/**
- * @author Hans Dockter
- */
-public class DefaultPublishArtifactFactoryTest extends Specification {
-    final DependencyMetaDataProvider provider = Mock()
-    final Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
-    final DefaultPublishArtifactFactory publishArtifactFactory = new DefaultPublishArtifactFactory(instantiator, provider)
-
-    def setup() {
-        Module module = Mock()
-        _ * provider.module >> module
-        _ * module.version >> '1.2'
-    }
-
-    def createArtifactFromPublishArtifactInstance() {
-        PublishArtifact original = Mock()
-
-        when:
-        def publishArtifact = publishArtifactFactory.parseNotation(original)
-
-        then:
-        publishArtifact == original
-    }
-
-    def createArtifactFromArchiveTask() {
-        AbstractArchiveTask archiveTask = Mock()
-        archiveTask.getArchivePath() >> new File("")
-
-        when:
-        def publishArtifact = publishArtifactFactory.parseNotation(archiveTask)
-
-        then:
-        publishArtifact instanceof ArchivePublishArtifact
-        publishArtifact.archiveTask == archiveTask
-    }
-
-    def createArtifactFromFile() {
-        def file = new File("some.zip")
-
-        when:
-        def publishArtifact = publishArtifactFactory.parseNotation(file)
-
-        then:
-        publishArtifact instanceof DefaultPublishArtifact
-        publishArtifact.file == file
-    }
-
-    def createArtifactFromFileInMap() {
-        Task task = Mock()
-        def file = new File("some.zip")
-
-        when:
-        def publishArtifact = publishArtifactFactory.parseNotation(file: file, type: 'someType', builtBy: task)
-
-        then:
-        publishArtifact instanceof DefaultPublishArtifact
-        publishArtifact.file == file
-        publishArtifact.type == 'someType'
-        publishArtifact.buildDependencies.getDependencies(null) == [task] as Set
-    }
-
-    def determinesArtifactPropertiesFromFileName() {
-        def file1 = new File("some.zip")
-        def file2 = new File("some.zip.zip")
-        def file3 = new File(".zip")
-
-        when:
-        def publishArtifact = publishArtifactFactory.parseNotation(file1)
-
-        then:
-        publishArtifact.name == 'some'
-        publishArtifact.type == 'zip'
-        publishArtifact.extension == 'zip'
-        publishArtifact.classifier == null
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file2)
-
-        then:
-        publishArtifact.name == 'some.zip'
-        publishArtifact.type == 'zip'
-        publishArtifact.extension == 'zip'
-        publishArtifact.classifier == null
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file3)
-
-        then:
-        publishArtifact.name == ''
-        publishArtifact.type == 'zip'
-        publishArtifact.extension == 'zip'
-        publishArtifact.classifier == null
-    }
-
-    def handlesFileWithNoExtension() {
-        def file = new File("some-file")
-
-        when:
-        def publishArtifact = publishArtifactFactory.parseNotation(file)
-
-        then:
-        publishArtifact.file == file
-        publishArtifact.name == 'some-file'
-        publishArtifact.type == null
-        publishArtifact.extension == null
-        publishArtifact.classifier == null
-    }
-
-    def removesProjectVersionFromFileName() {
-        def file1 = new File("some-file-1.2.jar")
-        def file2 = new File("some-file-1.2-1.2.jar")
-        def file3 = new File("some-file-1.22.jar")
-        def file4 = new File("some-file-1.2.jar.jar")
-
-        when:
-        def publishArtifact = publishArtifactFactory.parseNotation(file1)
-
-        then:
-        publishArtifact.name == 'some-file'
-        publishArtifact.type == 'jar'
-        publishArtifact.extension == 'jar'
-        publishArtifact.classifier == null
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file2)
-
-        then:
-        publishArtifact.name == 'some-file-1.2'
-        publishArtifact.type == 'jar'
-        publishArtifact.extension == 'jar'
-        publishArtifact.classifier == null
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file3)
-
-        then:
-        publishArtifact.name == 'some-file-1.22'
-        publishArtifact.type == 'jar'
-        publishArtifact.extension == 'jar'
-        publishArtifact.classifier == null
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file4)
-
-        then:
-        publishArtifact.name == 'some-file-1.2.jar'
-        publishArtifact.type == 'jar'
-        publishArtifact.extension == 'jar'
-        publishArtifact.classifier == null
-    }
-
-    def determinesClassifierFromFileName() {
-        def file1 = new File("some-file-1.2-classifier.jar")
-        def file2 = new File("some-file-1.2-classifier-1.2.jar")
-        def file3 = new File("-1.2-classifier.jar")
-        def file4 = new File("some-file-1.2-classifier")
-        def file5 = new File("some-file-1.2-.jar")
-
-        when:
-        def publishArtifact = publishArtifactFactory.parseNotation(file1)
-
-        then:
-        publishArtifact.name == 'some-file'
-        publishArtifact.type == 'jar'
-        publishArtifact.extension == 'jar'
-        publishArtifact.classifier == 'classifier'
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file2)
-
-        then:
-        publishArtifact.name == 'some-file-1.2-classifier'
-        publishArtifact.type == 'jar'
-        publishArtifact.extension == 'jar'
-        publishArtifact.classifier == null
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file3)
-
-        then:
-        publishArtifact.name == ''
-        publishArtifact.type == 'jar'
-        publishArtifact.extension == 'jar'
-        publishArtifact.classifier == 'classifier'
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file4)
-
-        then:
-        publishArtifact.name == 'some-file'
-        publishArtifact.type == null
-        publishArtifact.extension == null
-        publishArtifact.classifier == 'classifier'
-
-        when:
-        publishArtifact = publishArtifactFactory.parseNotation(file5)
-
-        then:
-        publishArtifact.name == 'some-file'
-        publishArtifact.type == 'jar'
-        publishArtifact.extension == 'jar'
-        publishArtifact.classifier == null
-    }
-
-    public void createArtifactWithNullNotationShouldThrowInvalidUserDataEx() {
-        when:
-        publishArtifactFactory.parseNotation(null)
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    public void createArtifactWithUnknownNotationShouldThrowInvalidUserDataEx() {
-        when:
-        publishArtifactFactory.parseNotation(new Point(1, 2))
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParserSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParserSpec.groovy
deleted file mode 100644
index 5f5ef9c..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ForcedModuleNotationParserSpec.groovy
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dsl;
-
-
-import org.gradle.api.InvalidUserDataException
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 10/14/11
- */
-public class ForcedModuleNotationParserSpec extends Specification {
-
-    def "understands group:name:version notation"() {
-        when:
-        def v = new ForcedModuleNotationParser().parseNotation("org.foo:bar:1.0") as List
-
-        then:
-        v.size() == 1
-        v[0].group == 'org.foo'
-        v[0].name  == 'bar'
-        v[0].version  == '1.0'
-    }
-
-    def "works with CharSequences"() {
-        when:
-        def sb = new StringBuilder().append("org.foo:charsequence:1.0")
-        def v = new ForcedModuleNotationParser().parseNotation(sb) as List
-
-        then:
-        v.size() == 1
-        v[0].name  == 'charsequence'
-    }
-
-    def "allows exact type on input"() {
-        def id = ForcedModuleNotationParser.selector("org.foo", "bar", "2.0")
-
-        when:
-        def v = new ForcedModuleNotationParser().parseNotation(id) as List
-
-        then:
-        v.size() == 1
-        v[0].group == 'org.foo'
-        v[0].name  == 'bar'
-        v[0].version  == '2.0'
-    }
-
-    def "allows list of objects on input"() {
-        def id = ForcedModuleNotationParser.selector("org.foo", "bar", "2.0")
-
-        when:
-        def v = new ForcedModuleNotationParser().parseNotation([id, ["hey:man:1.0"], [group:'i', name:'like', version:'maps']]) as List
-
-        then:
-        v.size() == 3
-        v[0].name == 'bar'
-        v[1].name == 'man'
-        v[2].name == 'like'
-    }
-
-    def "allows map on input"() {
-        when:
-        def v = new ForcedModuleNotationParser().parseNotation([group: 'org.foo', name: 'bar', version:'1.0']) as List
-
-        then:
-        v.size() == 1
-        v[0].group == 'org.foo'
-        v[0].name  == 'bar'
-        v[0].version  == '1.0'
-    }
-
-    def "fails for unknown types"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation(new Object())
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def "reports missing keys for map notation"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation([name: "bar", version: "1.0"])
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def "reports wrong keys for map notation"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation([groop: 'groop', name: "bar", version: "1.0"])
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def "reports invalid format for string notation"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation(["blahblah"])
-
-        then:
-        thrown(InvalidUserDataException)
-    }
-
-    def "reports invalid missing data for string notation"() {
-        when:
-        new ForcedModuleNotationParser().parseNotation([":foo:"])
-
-        then:
-        def ex = thrown(InvalidUserDataException)
-        ex.message.contains 'cannot be empty'
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsersTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsersTest.groovy
new file mode 100644
index 0000000..f42522c
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/ModuleVersionSelectorParsersTest.groovy
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dsl;
+
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+import static org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers.multiParser
+import static org.gradle.api.internal.artifacts.dsl.ModuleVersionSelectorParsers.parser
+
+/**
+ * by Szczepan Faber, created at: 10/14/11
+ */
+public class ModuleVersionSelectorParsersTest extends Specification {
+
+    def "understands group:name:version notation"() {
+        when:
+        def v = multiParser().parseNotation("org.foo:bar:1.0") as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '1.0'
+    }
+
+    def "works with CharSequences"() {
+        when:
+        def sb = new StringBuilder().append("org.foo:charsequence:1.0")
+        def v = multiParser().parseNotation(sb) as List
+
+        then:
+        v.size() == 1
+        v[0].name  == 'charsequence'
+    }
+
+    def "allows exact type on input"() {
+        def id = newSelector("org.foo", "bar", "2.0")
+
+        when:
+        def v = multiParser().parseNotation(id) as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '2.0'
+    }
+
+    def "allows list of objects on input"() {
+        def id = newSelector("org.foo", "bar", "2.0")
+
+        when:
+        def v = multiParser().parseNotation([id, ["hey:man:1.0"], [group:'i', name:'like', version:'maps']]) as List
+
+        then:
+        v.size() == 3
+        v[0].name == 'bar'
+        v[1].name == 'man'
+        v[2].name == 'like'
+    }
+
+    def "allows map on input"() {
+        when:
+        def v = multiParser().parseNotation([group: 'org.foo', name: 'bar', version:'1.0']) as List
+
+        then:
+        v.size() == 1
+        v[0].group == 'org.foo'
+        v[0].name  == 'bar'
+        v[0].version  == '1.0'
+    }
+
+    def "fails for unknown types"() {
+        when:
+        multiParser().parseNotation(new Object())
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports missing keys for map notation"() {
+        when:
+        multiParser().parseNotation([name: "bar", version: "1.0"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports wrong keys for map notation"() {
+        when:
+        multiParser().parseNotation([groop: 'groop', name: "bar", version: "1.0"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports invalid format for string notation"() {
+        when:
+        multiParser().parseNotation(["blahblah"])
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "reports invalid missing data for string notation"() {
+        when:
+        multiParser().parseNotation([":foo:"])
+
+        then:
+        def ex = thrown(InvalidUserDataException)
+        ex.message.contains 'cannot be empty'
+    }
+
+    def "null is an invalid input"() {
+        when:
+        multiParser().parseNotation(null)
+
+        then:
+        thrown(InvalidUserDataException)
+
+        when:
+        parser().parseNotation(null)
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def "single parser understands String notation"() {
+        //just smoke testing the single parser, it is covered in multiParser, too.
+        when:
+        def v = parser().parseNotation("org.foo:bar:1.0")
+
+        then:
+        v.group == 'org.foo'
+        v.name  == 'bar'
+        v.version  == '1.0'
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactoryTest.groovy
new file mode 100644
index 0000000..4d2bf8f
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactNotationParserFactoryTest.groovy
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.dsl
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Module
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.ThreadGlobalInstantiator
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.gradle.internal.reflect.Instantiator
+import spock.lang.Specification
+
+import java.awt.*
+
+/**
+ * @author Hans Dockter
+ */
+public class PublishArtifactNotationParserFactoryTest extends Specification {
+    final DependencyMetaDataProvider provider = Mock()
+    final Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
+    final PublishArtifactNotationParserFactory publishArtifactNotationParserFactory = new PublishArtifactNotationParserFactory(instantiator, provider)
+    final NotationParser<PublishArtifact> publishArtifactNotationParser = publishArtifactNotationParserFactory.create();
+
+    def setup() {
+        Module module = Mock()
+        _ * provider.module >> module
+        _ * module.version >> '1.2'
+    }
+
+    def createArtifactFromPublishArtifactInstance() {
+        PublishArtifact original = Mock()
+
+        when:
+        def publishArtifact = publishArtifactNotationParser.parseNotation(original)
+
+        then:
+        publishArtifact == original
+    }
+
+    def createArtifactFromArchiveTask() {
+        AbstractArchiveTask archiveTask = Mock()
+        archiveTask.getArchivePath() >> new File("")
+
+        when:
+        def publishArtifact = publishArtifactNotationParser.parseNotation(archiveTask)
+
+        then:
+        publishArtifact instanceof ArchivePublishArtifact
+        publishArtifact.archiveTask == archiveTask
+    }
+
+    def createArtifactFromFile() {
+        def file = new File("some.zip")
+
+        when:
+        def publishArtifact = publishArtifactNotationParser.parseNotation(file)
+
+        then:
+        publishArtifact instanceof DefaultPublishArtifact
+        publishArtifact.file == file
+    }
+
+    def createArtifactFromFileInMap() {
+        Task task = Mock()
+        def file = new File("some-file-1.2-classifier.zip")
+
+        when:
+        def publishArtifact = publishArtifactNotationParser.parseNotation(file: file, type: 'someType', builtBy: task)
+
+        then:
+        publishArtifact instanceof DefaultPublishArtifact
+        publishArtifact.file == file
+        publishArtifact.type == 'someType'
+        publishArtifact.name == 'some-file'
+        publishArtifact.extension == 'zip'
+        publishArtifact.classifier == 'classifier'
+        publishArtifact.buildDependencies.getDependencies(null) == [task] as Set
+    }
+
+    public void createArtifactWithNullNotationShouldThrowInvalidUserDataEx() {
+        when:
+        publishArtifactNotationParser.parseNotation(null)
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    public void createArtifactWithUnknownNotationShouldThrowInvalidUserDataEx() {
+        when:
+        publishArtifactNotationParser.parseNotation(new Point(1, 2))
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolverTest.groovy
index d8643ed..bdded94 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/CacheLockingArtifactDependencyResolverTest.groovy
@@ -18,11 +18,13 @@ package org.gradle.api.internal.artifacts.ivyservice
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
 import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
 import spock.lang.Specification
 
 class CacheLockingArtifactDependencyResolverTest extends Specification {
     final CacheLockingManager lockingManager = Mock()
     final ArtifactDependencyResolver target = Mock()
+    final List<ResolutionAwareRepository> repositories = [Mock(ResolutionAwareRepository)]
     final CacheLockingArtifactDependencyResolver resolver = new CacheLockingArtifactDependencyResolver(lockingManager, target)
 
     def "resolves while holding a lock on the cache"() {
@@ -30,7 +32,7 @@ class CacheLockingArtifactDependencyResolverTest extends Specification {
         ResolverResults resolverResults = Mock()
 
         when:
-        def results = resolver.resolve(configuration)
+        def results = resolver.resolve(configuration, repositories)
 
         then:
         results == resolverResults
@@ -39,6 +41,6 @@ class CacheLockingArtifactDependencyResolverTest extends Specification {
         1 * lockingManager.useCache("resolve $configuration", !null) >> {
             it[1].create()
         }
-        1 * target.resolve(configuration) >> resolverResults
+        1 * target.resolve(configuration, repositories) >> resolverResults
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
index bdf48e0..88adc16 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultBuildableModuleVersionResolveResultTest.groovy
@@ -18,12 +18,46 @@ package org.gradle.api.internal.artifacts.ivyservice
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData
 import spock.lang.Specification
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 
 class DefaultBuildableModuleVersionResolveResultTest extends Specification {
     def result = new DefaultBuildableModuleVersionResolveResult()
 
+    def "can query id and meta-data when resolved"() {
+        ModuleVersionIdentifier id = Stub()
+        ModuleVersionMetaData metaData = Stub() {
+            getId() >> id
+        }
+        ArtifactResolver resolver = Stub()
+
+        when:
+        result.resolved(metaData, resolver)
+
+        then:
+        result.id == id
+        result.artifactResolver == resolver
+        result.metaData == metaData
+    }
+
+    def "can resolve using id and ivy descriptor"() {
+        ModuleVersionIdentifier id = Mock()
+        ModuleDescriptor descriptor = Mock()
+        ArtifactResolver resolver = Mock()
+
+        when:
+        result.resolved(id, descriptor, resolver)
+
+        then:
+        result.id == id
+        result.artifactResolver == resolver
+        result.metaData.id == id
+        result.metaData.descriptor == descriptor
+        !result.metaData.changing
+    }
+
     def "cannot get id when no result has been specified"() {
         when:
         result.id
@@ -33,9 +67,9 @@ class DefaultBuildableModuleVersionResolveResultTest extends Specification {
         e.message == 'No result has been specified.'
     }
 
-    def "cannot get descriptor when no result has been specified"() {
+    def "cannot get meta-data when no result has been specified"() {
         when:
-        result.descriptor
+        result.metaData
 
         then:
         IllegalStateException e = thrown()
@@ -72,12 +106,12 @@ class DefaultBuildableModuleVersionResolveResultTest extends Specification {
         e == failure
     }
 
-    def "cannot get descriptor when resolve failed"() {
+    def "cannot get meta-data when resolve failed"() {
         def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
 
         when:
         result.failed(failure)
-        result.descriptor
+        result.metaData
 
         then:
         ModuleVersionResolveException e = thrown()
@@ -106,7 +140,7 @@ class DefaultBuildableModuleVersionResolveResultTest extends Specification {
 
     def "fails with a not found exception when not found"() {
         when:
-        result.notFound(Mock(ModuleVersionIdentifier))
+        result.notFound(Mock(ModuleVersionSelector))
 
         then:
         result.failure instanceof ModuleVersionNotFoundException
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
index 65cf1b8..3cb3df0 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultDependencyResolveDetailsSpec.groovy
@@ -118,4 +118,38 @@ class DefaultDependencyResolveDetailsSpec extends Specification {
         then:
         thrown(IllegalArgumentException)
     }
+
+    def "can specify target module"() {
+        def details = new DefaultDependencyResolveDetails(newSelector("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 = new DefaultDependencyResolveDetails(newSelector("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'
+    }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
index 2a93d19..aed2959 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
@@ -27,8 +27,6 @@ class DefaultSettingsConverterTest extends Specification {
     final IBiblioResolver testResolver = new IBiblioResolver()
     final IBiblioResolver testResolver2 = new IBiblioResolver()
 
-    File testGradleUserHome = new File('gradleUserHome')
-
     final Factory<IvySettings> ivySettingsFactory = Mock()
     final IvySettings ivySettings = new IvySettings()
 
@@ -40,7 +38,7 @@ class DefaultSettingsConverterTest extends Specification {
 
     public void testConvertForResolve() {
         when:
-        IvySettings settings = converter.convertForResolve(defaultResolver, [testResolver, testResolver2])
+        IvySettings settings = converter.convertForResolve(defaultResolver)
 
         then:
         1 * ivySettingsFactory.create() >> ivySettings
@@ -51,45 +49,25 @@ class DefaultSettingsConverterTest extends Specification {
         assert settings.is(ivySettings)
 
         assert settings.defaultResolver == defaultResolver
-        assert settings.resolvers.size() == 3
-        [testResolver, testResolver2].each { resolver ->
-            assert settings.resolvers.any { it == resolver }
-            assert settings.getResolver(resolver.name) == resolver
-            assert settings == resolver.settings
-            assert settings == resolver.repositoryCacheManager.settings
-        }
+        assert settings.resolvers.size() == 1
     }
 
     public void shouldReuseResolveSettings() {
-        when:
-        IvySettings settings = converter.convertForResolve(defaultResolver, [testResolver, testResolver2])
-
-        then:
+        given:
         1 * ivySettingsFactory.create() >> ivySettings
-        1 * defaultResolver.setSettings(ivySettings)
         _ * defaultResolver.getName() >> 'default'
-        0 * _._
-
-        assert settings.is(ivySettings)
-
-        [testResolver, testResolver2].each { resolver ->
-            assert settings.resolvers.any { it == resolver }
-        }
+        IvySettings settings = converter.convertForResolve(defaultResolver)
+        settings.addResolver(testResolver)
+        settings.addResolver(testResolver2)
 
         when:
-        settings = converter.convertForResolve(defaultResolver, [testResolver])
+        settings = converter.convertForResolve(defaultResolver)
 
         then:
         assert settings.is(ivySettings)
 
         assert settings.defaultResolver == defaultResolver
-        assert settings.resolvers.size() == 2
-        [testResolver].each { resolver ->
-             assert settings.resolvers.any { it == resolver }
-             assert settings.getResolver(resolver.name) == resolver
-             assert settings == resolver.settings
-             assert settings == resolver.repositoryCacheManager.settings
-         }
+        assert settings.resolvers.size() == 1
     }
 
     public void testConvertForPublish() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy
index 1558c93..585e5c3 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultUnresolvedDependencySpec.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.api.internal.artifacts.ivyservice
 
-import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
 import spock.lang.Specification
 
 /**
@@ -26,7 +26,7 @@ class DefaultUnresolvedDependencySpec extends Specification {
 
     def "provides module details"() {
         when:
-        def dep = new DefaultUnresolvedDependency(ModuleRevisionId.newInstance('org.foo', "foo", '1.0'), new RuntimeException("boo!"))
+        def dep = new DefaultUnresolvedDependency(DefaultModuleVersionSelector.newSelector('org.foo', "foo", '1.0'), new RuntimeException("boo!"))
 
         then:
         dep.selector.group == 'org.foo'
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy
index 8a2d7cb..d587aa7 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingArtifactDependencyResolverTest.groovy
@@ -22,6 +22,7 @@ import org.gradle.api.artifacts.result.ResolutionResult
 import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
 import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
 import org.gradle.api.specs.Specs
 import spock.lang.Specification
 
@@ -33,14 +34,15 @@ public class ErrorHandlingArtifactDependencyResolverTest extends Specification {
     private resolvedConfiguration = Mock(ResolvedConfiguration)
     private resolutionResult = Mock(ResolutionResult)
     private configuration = Mock(ConfigurationInternal.class, name: 'coolConf')
+    private repositories = [Mock(ResolutionAwareRepository)]
     private resolver = new ErrorHandlingArtifactDependencyResolver(delegate);
 
     void "delegates to backing service"() {
         given:
-        delegate.resolve(configuration) >> new ResolverResults(resolvedConfiguration, resolutionResult)
+        delegate.resolve(configuration, repositories) >> new ResolverResults(resolvedConfiguration, resolutionResult)
 
         when:
-        ResolverResults outerResults = resolver.resolve(configuration);
+        ResolverResults outerResults = resolver.resolve(configuration, repositories);
         outerResults.resolvedConfiguration.hasError()
         outerResults.resolvedConfiguration.rethrowFailure()
         outerResults.resolvedConfiguration.getFiles(Specs.satisfyAll())
@@ -55,10 +57,10 @@ public class ErrorHandlingArtifactDependencyResolverTest extends Specification {
     void "wraps operations with the failure"() {
         given:
         def failure = new RuntimeException()
-        delegate.resolve(configuration) >> { throw failure }
+        delegate.resolve(configuration, repositories) >> { throw failure }
 
         when:
-        ResolverResults results = resolver.resolve(configuration);
+        ResolverResults results = resolver.resolve(configuration, repositories);
 
         then:
         results.resolvedConfiguration.hasError()
@@ -79,10 +81,10 @@ public class ErrorHandlingArtifactDependencyResolverTest extends Specification {
         resolvedConfiguration.getFirstLevelModuleDependencies() >> { throw failure }
         resolvedConfiguration.getResolvedArtifacts() >> { throw failure }
 
-        delegate.resolve(configuration) >> { new ResolverResults(resolvedConfiguration, resolutionResult) }
+        delegate.resolve(configuration, repositories) >> { new ResolverResults(resolvedConfiguration, resolutionResult) }
 
         when:
-        ResolverResults results = resolver.resolve(configuration);
+        ResolverResults results = resolver.resolve(configuration, repositories);
 
         then:
         failsWith(failure)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
index b042cab..272474e 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyBackedArtifactPublisherTest.java
@@ -26,6 +26,7 @@ import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.configurations.Configurations;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.artifacts.ivyservice.resolutionstrategy.DefaultResolutionStrategy;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 import org.gradle.util.JUnit4GroovyMockery;
 import org.gradle.util.WrapUtil;
 import org.jmock.Expectations;
@@ -57,8 +58,12 @@ public class IvyBackedArtifactPublisherTest {
     private IvyDependencyPublisher ivyDependencyPublisherMock = context.mock(IvyDependencyPublisher.class);
     private ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publishConverter");
     private ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "fileConverter");
-    private IvyModuleDescriptorWriter ivyModuleDescriptorWriterMock = context.mock(IvyModuleDescriptorWriter.class);
-    final List<DependencyResolver> publishResolversDummy = createPublishResolversDummy();
+    private DependencyResolver resolver1 = context.mock(DependencyResolver.class);
+    private DependencyResolver resolver2 = context.mock(DependencyResolver.class);
+    private PublicationAwareRepository repo1 = repo(resolver1);
+    private PublicationAwareRepository repo2 = repo(resolver2);
+    final List<DependencyResolver> publishResolversDummy = WrapUtil.toList(resolver1, resolver2);
+    final List<PublicationAwareRepository> publishRepositoriesDummy = WrapUtil.toList(repo1, repo2);
 
     @Test
     public void testPublish() throws IOException, ParseException {
@@ -83,10 +88,9 @@ public class IvyBackedArtifactPublisherTest {
             will(returnValue(new DefaultResolutionStrategy()));
             one(ivyDependencyPublisherMock).publish(expectedConfigurations,
                     publishResolversDummy, publishModuleDescriptorDummy, someDescriptorDestination, ivyEventManagerDummy);
-            allowing(ivyModuleDescriptorWriterMock).write(fileModuleDescriptorMock, someDescriptorDestination, null);
         }});
 
-        ivyService.publish(publishResolversDummy, configuration.getModule(), configuration.getHierarchy(), someDescriptorDestination);
+        ivyService.publish(publishRepositoriesDummy, configuration.getModule(), configuration.getHierarchy(), someDescriptorDestination);
     }
 
     private IvyBackedArtifactPublisher createIvyService() {
@@ -97,10 +101,6 @@ public class IvyBackedArtifactPublisherTest {
                 ivyDependencyPublisherMock);
     }
 
-    private List<DependencyResolver> createPublishResolversDummy() {
-        return WrapUtil.toList(context.mock(DependencyResolver.class, "publish"));
-    }
-
     private Set<Configuration> createConfiguration() {
         final Configuration configurationStub1 = context.mock(Configuration.class, "confStub1");
         final Configuration configurationStub2 = context.mock(Configuration.class, "confStub2");
@@ -161,4 +161,13 @@ public class IvyBackedArtifactPublisherTest {
         }});
         return ivyStub;
     }
+
+    private PublicationAwareRepository repo(final DependencyResolver resolver) {
+        final PublicationAwareRepository repository = context.mock(PublicationAwareRepository.class);
+        context.checking(new Expectations() {{
+            one(repository).createPublisher();
+            will(returnValue(resolver));
+        }});
+        return repository;
+    }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy
index 345e62a..0208cb7 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyXmlModuleDescriptorWriterTest.groovy
@@ -21,9 +21,6 @@ import org.apache.ivy.core.module.descriptor.Configuration
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.gradle.api.Action
-import org.gradle.api.XmlProvider
-import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
@@ -36,7 +33,6 @@ class IvyXmlModuleDescriptorWriterTest extends Specification {
     private ModuleDescriptor md = Mock();
     private ModuleRevisionId moduleRevisionId = Mock()
     private ModuleRevisionId resolvedModuleRevisionId = Mock()
-    private PrintWriter printWriter = Mock()
     def ivyXmlModuleDescriptorWriter = new IvyXmlModuleDescriptorWriter()
 
     def setup() {
@@ -85,30 +81,6 @@ class IvyXmlModuleDescriptorWriterTest extends Specification {
         assert ivyModule.dependencies.dependency.collect { "${it. at org}:${it. at name}:${it. at rev}" } == ["org.test:Dep1:1.0", "org.test:Dep2:1.0"]
     }
 
-    def "can create ivy (modified) descriptor"() {
-        setup:
-        def dependency1 = mockDependencyDescriptor("Dep1")
-        def dependency2 = mockDependencyDescriptor("Dep2")
-        1 * md.dependencies >> [dependency1, dependency2]
-        when:
-        File ivyFile = temporaryFolder.file("test/ivy/ivy.xml")
-        XmlTransformer xmlTransformer = new XmlTransformer()
-        xmlTransformer.addAction(new Action<XmlProvider>() {
-            void execute(XmlProvider xml) {
-                def node = xml.asNode()
-                node.info[0]. at status = "foo"
-                assert node.dependencies.dependency.collect { "${it. at org}:${it. at name}:${it. at rev}" } == ["org.test:Dep1:1.0", "org.test:Dep2:1.0"]
-                node.dependencies[0].dependency[0]. at name = "changed"
-            }
-        })
-        ivyXmlModuleDescriptorWriter.write(md, ivyFile, xmlTransformer)
-
-        then:
-        def ivyModule = new XmlSlurper().parse(ivyFile);
-        ivyModule.info. at status == "foo"
-        ivyModule.dependencies.dependency.collect { "${it. at org}:${it. at name}:${it. at rev}" } == ["org.test:changed:1.0", "org.test:Dep2:1.0"]
-    }
-
     def date(String timestamp) {
         def format = new SimpleDateFormat("yyyyMMddHHmmss")
         format.parse(timestamp)
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy
index 8748741..27a0654 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.groovy
@@ -26,6 +26,7 @@ import org.gradle.api.internal.artifacts.CachingDependencyResolveContext
 import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
 import org.gradle.api.specs.Spec
 import org.gradle.api.specs.Specs
 import spock.lang.Specification
@@ -35,18 +36,19 @@ public class SelfResolvingDependencyResolverTest extends Specification {
     private delegate = Mock(ArtifactDependencyResolver)
     private resolvedConfiguration = Mock(ResolvedConfiguration)
     private configuration = Mock(ConfigurationInternal)
+    private repositories = [Mock(ResolutionAwareRepository)]
     private dependencies = Mock(DependencySet)
 
     private resolver = new SelfResolvingDependencyResolver(delegate);
 
     void "returns correct resolved configuration"() {
         given:
-        delegate.resolve(configuration) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        delegate.resolve(configuration, repositories) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> true
 
         when:
-        def results = resolver.resolve(configuration)
+        def results = resolver.resolve(configuration, repositories)
 
         then:
         def conf = (SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration) results.resolvedConfiguration
@@ -58,12 +60,12 @@ public class SelfResolvingDependencyResolverTest extends Specification {
 
     void "uses configuration transitive setting"() {
         given:
-        delegate.resolve(configuration) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        delegate.resolve(configuration, repositories) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> false
 
         when:
-        def results = resolver.resolve(configuration)
+        def results = resolver.resolve(configuration, repositories)
 
         then:
         def conf = (SelfResolvingDependencyResolver.FilesAggregatingResolvedConfiguration) results.resolvedConfiguration
@@ -72,12 +74,12 @@ public class SelfResolvingDependencyResolverTest extends Specification {
 
     void "delegates to provided resolved configuration"() {
         given:
-        delegate.resolve(configuration) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
+        delegate.resolve(configuration, repositories) >> new ResolverResults(resolvedConfiguration, Mock(ResolutionResult))
         configuration.getAllDependencies() >> dependencies
         configuration.isTransitive() >> true
 
         when:
-        def results = resolver.resolve(configuration)
+        def results = resolver.resolve(configuration, repositories)
         results.resolvedConfiguration.getFirstLevelModuleDependencies(Specs.satisfyAll())
         results.resolvedConfiguration.getResolvedArtifacts()
         results.resolvedConfiguration.hasError()
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy
index d7e2e30..c6e5ac1 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsArtifactDependencyResolverSpec.groovy
@@ -23,6 +23,7 @@ import org.gradle.api.internal.artifacts.ArtifactDependencyResolver
 import org.gradle.api.internal.artifacts.DefaultModule
 import org.gradle.api.internal.artifacts.ResolverResults
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
+import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
 import org.gradle.api.specs.Specs
 import spock.lang.Specification
 
@@ -30,6 +31,7 @@ class ShortcircuitEmptyConfigsArtifactDependencyResolverSpec extends Specificati
 
     final ArtifactDependencyResolver delegate = Mock()
     final ConfigurationInternal configuration = Mock()
+    final List<ResolutionAwareRepository> repositories = [Mock(ResolutionAwareRepository)]
     final DependencySet dependencies = Mock()
 
     final ShortcircuitEmptyConfigsArtifactDependencyResolver dependencyResolver = new ShortcircuitEmptyConfigsArtifactDependencyResolver(delegate);
@@ -41,7 +43,7 @@ class ShortcircuitEmptyConfigsArtifactDependencyResolverSpec extends Specificati
         configuration.getModule() >> new DefaultModule("org", "foo", "1.0")
 
         when:
-        ResolverResults results = dependencyResolver.resolve(configuration);
+        ResolverResults results = dependencyResolver.resolve(configuration, repositories);
         ResolvedConfiguration resolvedConfig = results.resolvedConfiguration
 
         then:
@@ -59,10 +61,10 @@ class ShortcircuitEmptyConfigsArtifactDependencyResolverSpec extends Specificati
 
         dependencies.isEmpty() >> false
         configuration.getAllDependencies() >> dependencies
-        delegate.resolve(configuration) >> resultsDummy
+        delegate.resolve(configuration, repositories) >> resultsDummy
 
         when:
-        def out = dependencyResolver.resolve(configuration)
+        def out = dependencyResolver.resolve(configuration, repositories)
 
         then:
         out == resultsDummy
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy
index 660290e..5a72899 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/VersionForcingDependencyToModuleResolverSpec.groovy
@@ -15,10 +15,11 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice
 
-import org.apache.ivy.core.module.descriptor.DependencyDescriptor
 import org.apache.ivy.core.module.id.ModuleId
 import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.Action
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
 import spock.lang.Specification
 
@@ -60,7 +61,7 @@ class VersionForcingDependencyToModuleResolverSpec extends Specification {
         result.selectionReason == VersionSelectionReasons.SELECTED_BY_RULE
 
         and:
-        1 * dep.clone(new ModuleRevisionId(new ModuleId('org', 'module'), '1.0')) >> modified
+        1 * dep.withRequestedVersion(DefaultModuleVersionSelector.newInstance("org", "module", "1.0")) >> modified
         1 * target.resolve(modified) >> resolvedVersion
         0 * target._
     }
@@ -99,6 +100,8 @@ class VersionForcingDependencyToModuleResolverSpec extends Specification {
     }
 
     def dependency(String group, String module, String version) {
-        Mock(DependencyDescriptor) { getDependencyRevisionId() >> new ModuleRevisionId(new ModuleId(group, module), version) }
+        Mock(DependencyMetaData) {
+            getRequested() >> new DefaultModuleVersionSelector(group, module, version)
+        }
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
index c448063..dd8b5df 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/clientmodule/ClientModuleResolverTest.groovy
@@ -23,6 +23,7 @@ import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
 import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver
 import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ClientModuleDependencyDescriptor
 import spock.lang.Specification
 
@@ -39,43 +40,54 @@ class ClientModuleResolverTest extends Specification {
 
     def "replaces meta-data for a client module dependency"() {
         ClientModuleDependencyDescriptor dependencyDescriptor = Mock()
+        DependencyMetaData dependencyMetaData = Mock()
         BuildableModuleVersionResolveResult result = Mock()
 
         given:
+        _ * dependencyMetaData.descriptor >> dependencyDescriptor
         _ * dependencyDescriptor.targetModule >> module
+        _ * module.moduleRevisionId >> moduleId
 
         when:
-        resolver.resolve(dependencyDescriptor, result)
+        resolver.resolve(dependencyMetaData, result)
 
         then:
-        1 * target.resolve(dependencyDescriptor, result)
-        1 * result.setMetaData(module.moduleRevisionId, module)
+        1 * target.resolve(dependencyMetaData, result)
+        1 * result.setMetaData(module)
         _ * result.failure >> null
         0 * result._
     }
 
     def "does not replace meta-data for unknown module version"() {
         DependencyDescriptor dependencyDescriptor = Mock()
+        DependencyMetaData dependencyMetaData = Mock()
         BuildableModuleVersionResolveResult result = Mock()
 
+        given:
+        _ * dependencyMetaData.descriptor >> dependencyDescriptor
+
         when:
-        resolver.resolve(dependencyDescriptor, result)
+        resolver.resolve(dependencyMetaData, result)
 
         then:
-        1 * target.resolve(dependencyDescriptor, result)
+        1 * target.resolve(dependencyMetaData, result)
         _ * result.failure >> null
         0 * result._
     }
 
     def "does not replace meta-data for broken module version"() {
         ClientModuleDependencyDescriptor dependencyDescriptor = Mock()
+        DependencyMetaData dependencyMetaData = Mock()
         BuildableModuleVersionResolveResult result = Mock()
 
+        given:
+        _ * dependencyMetaData.descriptor >> dependencyDescriptor
+
         when:
-        resolver.resolve(dependencyDescriptor, result)
+        resolver.resolve(dependencyMetaData, result)
 
         then:
-        1 * target.resolve(dependencyDescriptor, result)
+        1 * target.resolve(dependencyMetaData, result)
         _ * result.failure >> new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
         0 * result._
     }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy
deleted file mode 100644
index a7d60ff..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionDescriptorTest.groovy
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
-
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor
-import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
-import spock.lang.Specification
-
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-
-class DefaultBuildableModuleVersionDescriptorTest extends Specification {
-    final DefaultBuildableModuleVersionDescriptor descriptor = new DefaultBuildableModuleVersionDescriptor()
-    ModuleSource moduleSource = Mock()
-
-    def "has unknown state by default"() {
-        expect:
-        descriptor.state == BuildableModuleVersionDescriptor.State.Unknown
-    }
-
-    def "can mark as missing"() {
-        when:
-        descriptor.missing()
-
-        then:
-        descriptor.state == BuildableModuleVersionDescriptor.State.Missing
-        descriptor.failure == null
-    }
-
-    def "can mark as probably missing"() {
-        when:
-        descriptor.probablyMissing()
-
-        then:
-        descriptor.state == BuildableModuleVersionDescriptor.State.ProbablyMissing
-        descriptor.failure == null
-    }
-
-    def "can mark as failed"() {
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-
-        when:
-        descriptor.failed(failure)
-
-        then:
-        descriptor.state == BuildableModuleVersionDescriptor.State.Failed
-        descriptor.failure == failure
-    }
-
-    def "can mark as resolved"() {
-        def moduleDescriptor = Mock(ModuleDescriptor)
-        ModuleRevisionId moduleRevisionId = Mock();
-        1 * moduleRevisionId.organisation >> "group"
-        1 * moduleRevisionId.name >> "project"
-        1 * moduleRevisionId.revision >> "1.0"
-        1 * moduleDescriptor.moduleRevisionId >> moduleRevisionId
-
-        when:
-        descriptor.resolved(moduleDescriptor, true, moduleSource)
-
-        then:
-        descriptor.state == BuildableModuleVersionDescriptor.State.Resolved
-        descriptor.failure == null
-        descriptor.descriptor == moduleDescriptor
-        descriptor.changing
-        descriptor.moduleSource == moduleSource
-    }
-
-    def "cannot get result when not resolved"() {
-        when:
-        descriptor.descriptor
-
-        then:
-        thrown(IllegalStateException)
-
-        when:
-        descriptor.failure
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot get result when failed"() {
-        given:
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-        descriptor.failed(failure)
-
-        when:
-        descriptor.descriptor
-
-        then:
-        ModuleVersionResolveException e = thrown()
-        e == failure
-    }
-
-    def "cannot get result when missing"() {
-        given:
-        descriptor.missing()
-
-        when:
-        descriptor.descriptor
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot get result when probably missing"() {
-        given:
-        descriptor.probablyMissing()
-
-        when:
-        descriptor.descriptor
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot get ModuleSource when failed"() {
-        given:
-        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
-        descriptor.failed(failure)
-
-        when:
-        descriptor.getModuleSource()
-
-        then:
-        ModuleVersionResolveException e = thrown()
-        e == failure
-    }
-
-    def "cannot get ModuleSource when missing"() {
-        given:
-        descriptor.missing()
-
-        when:
-        descriptor.getModuleSource()
-
-        then:
-        thrown(IllegalStateException)
-    }
-
-    def "cannot get ModuleSource when probably missing"() {
-        given:
-        descriptor.probablyMissing()
-
-        when:
-        descriptor.getModuleSource()
-
-        then:
-        thrown(IllegalStateException)
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataTest.groovy
new file mode 100644
index 0000000..df18c01
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultBuildableModuleVersionMetaDataTest.groovy
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.ivyservice.ModuleVersionResolveException
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
+
+class DefaultBuildableModuleVersionMetaDataTest extends Specification {
+    final DefaultBuildableModuleVersionMetaData descriptor = new DefaultBuildableModuleVersionMetaData()
+    ModuleSource moduleSource = Mock()
+
+    def "has unknown state by default"() {
+        expect:
+        descriptor.state == BuildableModuleVersionMetaData.State.Unknown
+    }
+
+    def "can mark as missing"() {
+        when:
+        descriptor.missing()
+
+        then:
+        descriptor.state == BuildableModuleVersionMetaData.State.Missing
+        descriptor.failure == null
+    }
+
+    def "can mark as probably missing"() {
+        when:
+        descriptor.probablyMissing()
+
+        then:
+        descriptor.state == BuildableModuleVersionMetaData.State.ProbablyMissing
+        descriptor.failure == null
+    }
+
+    def "can mark as failed"() {
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+
+        when:
+        descriptor.failed(failure)
+
+        then:
+        descriptor.state == BuildableModuleVersionMetaData.State.Failed
+        descriptor.failure == failure
+    }
+
+    def "can mark as resolved"() {
+        def id = Mock(ModuleVersionIdentifier)
+        def moduleDescriptor = Mock(ModuleDescriptor)
+
+        when:
+        descriptor.resolved(id, moduleDescriptor, true, moduleSource)
+
+        then:
+        descriptor.state == BuildableModuleVersionMetaData.State.Resolved
+        descriptor.failure == null
+        descriptor.id == id
+        descriptor.descriptor == moduleDescriptor
+        descriptor.changing
+        descriptor.moduleSource == moduleSource
+    }
+
+    def "builds and caches the dependency meta-data from the module descriptor"() {
+        def id = Mock(ModuleVersionIdentifier)
+        def moduleDescriptor = Mock(ModuleDescriptor)
+        def dependency1 = new DefaultDependencyDescriptor(ModuleRevisionId.newInstance("org", "module", "1.2"), false)
+        def dependency2 = new DefaultDependencyDescriptor(ModuleRevisionId.newInstance("org", "module", "1.2"), false)
+
+        given:
+        moduleDescriptor.dependencies >> ([dependency1, dependency2] as DependencyDescriptor[])
+
+        and:
+        descriptor.resolved(id, moduleDescriptor, true, moduleSource)
+
+        when:
+        def deps = descriptor.dependencies
+
+        then:
+        deps.size() == 2
+        deps[0].descriptor == dependency1
+        deps[1].descriptor == dependency2
+
+        and:
+        descriptor.dependencies.is(deps)
+    }
+
+    def "can replace the dependencies for the module version"() {
+        def id = Mock(ModuleVersionIdentifier)
+        def moduleDescriptor = Mock(ModuleDescriptor)
+        def dependency1 = Mock(DependencyMetaData)
+        def dependency2 = Mock(DependencyMetaData)
+
+        given:
+        descriptor.resolved(id, moduleDescriptor, true, moduleSource)
+
+        when:
+        descriptor.dependencies = [dependency1, dependency2]
+
+        then:
+        descriptor.dependencies == [dependency1, dependency2]
+
+        and:
+        0 * moduleDescriptor._
+    }
+
+    def "cannot get descriptor when not resolved"() {
+        when:
+        descriptor.descriptor
+
+        then:
+        thrown(IllegalStateException)
+
+        when:
+        descriptor.failure
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get descriptor when failed"() {
+        given:
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+        descriptor.failed(failure)
+
+        when:
+        descriptor.descriptor
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get descriptor when missing"() {
+        given:
+        descriptor.missing()
+
+        when:
+        descriptor.descriptor
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get descriptor when probably missing"() {
+        given:
+        descriptor.probablyMissing()
+
+        when:
+        descriptor.descriptor
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get module source when failed"() {
+        given:
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+        descriptor.failed(failure)
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot set module source when failed"() {
+        given:
+        def failure = new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
+        descriptor.failed(failure)
+
+        when:
+        descriptor.setModuleSource(Mock(ModuleSource))
+
+        then:
+        ModuleVersionResolveException e = thrown()
+        e == failure
+    }
+
+    def "cannot get module source when missing"() {
+        given:
+        descriptor.missing()
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot set module source when missing"() {
+        given:
+        descriptor.missing()
+
+        when:
+        descriptor.setModuleSource(Mock(ModuleSource))
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot get module source when probably missing"() {
+        given:
+        descriptor.probablyMissing()
+
+        when:
+        descriptor.getModuleSource()
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot set module source when probably missing"() {
+        given:
+        descriptor.probablyMissing()
+
+        when:
+        descriptor.setModuleSource(Mock(ModuleSource))
+
+        then:
+        thrown(IllegalStateException)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaDataTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaDataTest.groovy
new file mode 100644
index 0000000..183e345
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultDependencyMetaDataTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
+import spock.lang.Specification
+
+class DefaultDependencyMetaDataTest extends Specification {
+    final requestedModuleId = ModuleRevisionId.newInstance("org", "module", "1.2+")
+
+    def "constructs selector from descriptor"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        expect:
+        metaData.requested == DefaultModuleVersionSelector.newSelector("org", "module", "1.2+")
+    }
+
+    def "creates a copy with new requested version"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        given:
+
+        when:
+        def copy = metaData.withRequestedVersion("1.3+")
+
+        then:
+        copy.requested == DefaultModuleVersionSelector.newSelector("org", "module", "1.3+")
+        copy.descriptor.dependencyRevisionId == ModuleRevisionId.newInstance("org", "module", "1.3+")
+    }
+
+    def "returns this if new requested version is the same as current requested version"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        expect:
+        metaData.withRequestedVersion("1.2+").is(metaData)
+        metaData.withRequestedVersion(DefaultModuleVersionSelector.newSelector("org", "module", "1.2+")).is(metaData)
+    }
+
+    def "can set changing flag"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, false)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        when:
+        def copy = metaData.withChanging()
+
+        then:
+        copy.descriptor.dependencyRevisionId == requestedModuleId
+        copy.descriptor.changing
+    }
+
+    def "returns this when changing is already true"() {
+        def descriptor = new DefaultDependencyDescriptor(requestedModuleId, false, true)
+        def metaData = new DefaultDependencyMetaData(descriptor)
+
+        expect:
+        metaData.withChanging().is(metaData)
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy
index eb9b3fd..00268c2 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DependencyResolverIdentifierTest.groovy
@@ -13,13 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.internal.artifacts.ivyservice.ivyresolve;
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
 
-import org.apache.ivy.plugins.resolver.DependencyResolver
-import spock.lang.Specification
 import org.apache.ivy.plugins.resolver.AbstractPatternsBasedResolver
-
+import org.apache.ivy.plugins.resolver.DependencyResolver
 import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver
+import spock.lang.Specification
 
 public class DependencyResolverIdentifierTest extends Specification {
     def "uses dependency resolver name"() {
@@ -69,7 +68,7 @@ public class DependencyResolverIdentifierTest extends Specification {
         id(resolver2) != id(resolver2a)
     }
 
-    def "dependency resolvers of type ResourceCollectionResolver are differentiated by their patterns"() {
+    def "dependency resolvers of type ExternalResourceResolver are differentiated by their patterns"() {
         given:
         ExternalResourceResolver resolver1 = Mock()
         ExternalResourceResolver resolver1a = Mock()
@@ -108,7 +107,7 @@ public class DependencyResolverIdentifierTest extends Specification {
         id(resolver1) != id(resolver2)
     }
 
-    def "dependency resolvers of type ResourceCollectionResolver are differentiated by m2compatible flag"() {
+    def "dependency resolvers of type ExternalResourceResolver are differentiated by m2compatible flag"() {
         given:
         ExternalResourceResolver resolver1 = Mock()
         ExternalResourceResolver resolver2 = Mock()
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepositoryTest.groovy
new file mode 100644
index 0000000..2da5d0a
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/IvyDynamicResolveModuleVersionRepositoryTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.ivyresolve
+
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import spock.lang.Specification
+
+class IvyDynamicResolveModuleVersionRepositoryTest extends Specification {
+    final target = Mock(LocalAwareModuleVersionRepository)
+    final requestedDependency = Mock(DependencyMetaData)
+    final result = Mock(BuildableModuleVersionMetaData)
+    final repository = new IvyDynamicResolveModuleVersionRepository(target)
+
+    def "replaces each dependency version with revConstraint"() {
+        def original = dependency('1.2+')
+        def transformed = dependency()
+
+        given:
+        result.state >> BuildableModuleVersionMetaData.State.Resolved
+
+        when:
+        repository.getLocalDependency(requestedDependency, result)
+
+        then:
+        1 * target.getLocalDependency(requestedDependency, result)
+
+        and:
+        1 * result.dependencies >> [original]
+        1 * original.withRequestedVersion('1.2+') >> transformed
+        1 * result.setDependencies([transformed])
+    }
+
+    def "does nothing when dependency has not been resolved"() {
+        when:
+        repository.getLocalDependency(requestedDependency, result)
+
+        then:
+        1 * target.getLocalDependency(requestedDependency, result)
+        _ * result.state >> BuildableModuleVersionMetaData.State.Missing
+        0 * result._
+    }
+
+    def dependency(String revConstraint = '1.0') {
+        def dep = Mock(DependencyMetaData)
+        def descriptor = Mock(DependencyDescriptor)
+        _ * descriptor.dynamicConstraintDependencyRevisionId >> ModuleRevisionId.newInstance('org', 'module', revConstraint)
+        _ * dep.descriptor >> descriptor
+        return dep
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy
index 4a779fc..8828189 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/LazyDependencyToModuleResolverTest.groovy
@@ -21,7 +21,9 @@ import org.apache.ivy.core.module.descriptor.DependencyDescriptor
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor
 import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.apache.ivy.plugins.version.VersionMatcher
+import org.gradle.api.artifacts.ModuleVersionSelector
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
+import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
 import org.gradle.api.internal.artifacts.ivyservice.*
 import spock.lang.Specification
 
@@ -56,7 +58,8 @@ class LazyDependencyToModuleResolverTest extends Specification {
         moduleResolveResult.id.name == module.moduleRevisionId.name
         moduleResolveResult.id.version == module.moduleRevisionId.revision
 
-        moduleResolveResult.descriptor == module
+        moduleResolveResult.metaData.id == moduleResolveResult.id
+        moduleResolveResult.metaData.descriptor == module
 
         1 * target.resolve(dependency, _) >> { args -> args[1].resolved(moduleIdentifier(module), module, Mock(ArtifactResolver))}
         0 * target._
@@ -135,7 +138,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
         0 * target._
 
         when:
-        resolveResult.descriptor
+        resolveResult.metaData
 
         then:
         ModuleVersionResolveException e = thrown()
@@ -202,7 +205,7 @@ class LazyDependencyToModuleResolverTest extends Specification {
 
         when:
         def resolveResult = idResolveResult.resolve()
-        resolveResult.descriptor
+        resolveResult.metaData
 
         then:
         e = thrown()
@@ -268,7 +271,11 @@ class LazyDependencyToModuleResolverTest extends Specification {
         DependencyDescriptor descriptor = Mock()
         ModuleRevisionId id = ModuleRevisionId.newInstance("group", "module", "1.0")
         _ * descriptor.dependencyRevisionId >> id
-        return descriptor
+        DependencyMetaData metaData = Mock()
+        _ * metaData.descriptor >> descriptor
+        ModuleVersionSelector requested = DefaultModuleVersionSelector.newSelector("group", "module", "1.0")
+        _ * metaData.requested >> requested
+        return metaData
     }
 
     def artifact() {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
index c2e5f6d..2afc920 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/UserResolverChainTest.groovy
@@ -23,14 +23,16 @@ import org.apache.ivy.plugins.latest.LatestRevisionStrategy
 import org.apache.ivy.plugins.resolver.ResolverSettings
 import org.apache.ivy.plugins.version.VersionMatcher
 import org.gradle.api.artifacts.ModuleVersionIdentifier
+import org.gradle.api.artifacts.ModuleVersionSelector
 import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
 import spock.lang.Specification
 
 class UserResolverChainTest extends Specification {
     final UserResolverChain resolver = new UserResolverChain()
-    final ModuleRevisionId dependencyId = Stub()
-    final DependencyDescriptor dependency = Stub()
+    final ModuleVersionSelector dependencyId = Stub()
+    final DependencyMetaData dependency = Stub()
+    final DependencyDescriptor dependencyDescriptor = Stub()
     final ModuleDescriptor descriptor = descriptor("1.2")
     final ModuleVersionIdentifier resolvedId = moduleVersionIdentifier(descriptor)
 
@@ -44,10 +46,11 @@ class UserResolverChainTest extends Specification {
     final ModuleSource moduleSource = Mock()
 
     def setup() {
-        _ * dependencyId.organisation >> "group"
+        _ * dependencyId.group >> "group"
         _ * dependencyId.name >> "project"
-        _ * dependencyId.revision >> "1.0"
-        dependency.dependencyRevisionId >> dependencyId
+        _ * dependencyId.version >> "1.0"
+        _ * dependency.requested >> dependencyId
+        _ * dependency.descriptor >> dependencyDescriptor
         def settings = Stub(ResolverSettings)
         _ * settings.versionMatcher >> matcher
         _ * settings.defaultLatestStrategy >> new LatestRevisionStrategy();
@@ -66,9 +69,11 @@ class UserResolverChainTest extends Specification {
         1 * repo.getLocalDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, rep ->
-            assert rep.delegate == repo
-            assert rep.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo
+            assert source.moduleSource == moduleSource
         }
 
         and:
@@ -90,9 +95,11 @@ class UserResolverChainTest extends Specification {
         1 * repo.getDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, rep ->
-            assert rep.delegate == repo
-            assert rep.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo
+            assert source.moduleSource == moduleSource
         }
         and:
         _ * repo.name >> "repo"
@@ -115,9 +122,11 @@ class UserResolverChainTest extends Specification {
         1 * repo.getDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, rep ->
-            assert rep.delegate == repo
-            assert rep.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo
+            assert source.moduleSource == moduleSource
         }
 
         and:
@@ -138,12 +147,7 @@ class UserResolverChainTest extends Specification {
         1 * repo.getLocalDependency(dependency, _) >> { dep, result ->
             result.missing()
         }
-        1 * result.notFound(_ as ModuleVersionIdentifier) >> { ModuleVersionIdentifier mdV ->
-            assert mdV.group == "group"
-            assert mdV.name == "project"
-            assert mdV.version == "1.0"
-
-        }
+        1 * result.notFound(dependencyId)
 
         and:
         _ * repo.name >> "repo"
@@ -166,11 +170,7 @@ class UserResolverChainTest extends Specification {
         1 * repo.getDependency(dependency, _) >> { dep, result ->
             result.missing()
         }
-        1 * result.notFound(_ as ModuleVersionIdentifier) >> { ModuleVersionIdentifier moduleVersionIdentifier ->
-            assert moduleVersionIdentifier.group == "group"
-            assert moduleVersionIdentifier.name == "project"
-            assert moduleVersionIdentifier.version == "1.0"
-        }
+        1 * result.notFound(dependencyId)
 
         and:
         _ * repo.name >> "repo"
@@ -202,9 +202,11 @@ class UserResolverChainTest extends Specification {
         1 * repo3.getLocalDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor("1.0"), true, null)
         }
-        1 * result.resolved(moduleVersionIdentifier(version2), version2, _) >> { revision, descriptor, repo ->
-            assert repo.delegate == repo2
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == moduleVersionIdentifier(version2)
+            assert metaData.descriptor == version2
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
         }
 
         and:
@@ -234,9 +236,11 @@ class UserResolverChainTest extends Specification {
         1 * repo1.getLocalDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { revisionId, descriptor, repo ->
-            assert repo.delegate == repo1
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo1
+            assert source.moduleSource == moduleSource
         }
 
         and:
@@ -266,9 +270,11 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
-            assert repo.delegate == repo2
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
         }
 
         and:
@@ -296,9 +302,11 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
-            assert repo.delegate == repo2
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
         }
         and:
         _ * repo1.name >> "repo"
@@ -326,9 +334,11 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
-            assert repo.delegate == repo2
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
         }
         and:
         _ * repo1.name >> "repo"
@@ -361,9 +371,11 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
-            assert repo.delegate == repo2
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
         }
         and:
         _ * repo1.name >> "repo"
@@ -393,9 +405,11 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
-            assert repo.delegate == repo2
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
         }
 
         and:
@@ -427,9 +441,11 @@ class UserResolverChainTest extends Specification {
         1 * repo1.getDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
-            assert repo.delegate == repo1
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo1
+            assert source.moduleSource == moduleSource
         }
         and:
         _ * repo1.name >> "repo"
@@ -456,9 +472,11 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getLocalDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
-            assert repo.delegate == repo2
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
         }
 
         and:
@@ -488,10 +506,13 @@ class UserResolverChainTest extends Specification {
         1 * repo2.getDependency(dependency, _) >> { dep, result ->
             result.resolved(descriptor, true, moduleSource)
         }
-        1 * result.resolved(resolvedId, descriptor, _) >> { resolvedId, descr, repo ->
-            assert repo.delegate == repo2
-            assert repo.moduleSource == moduleSource
+        1 * result.resolved(_, _) >> { metaData, source ->
+            assert metaData.id == resolvedId
+            assert metaData.descriptor == descriptor
+            assert source.delegate == repo2
+            assert source.moduleSource == moduleSource
         }
+
         and:
         _ * repo1.name >> "repo"
         _ * repo2.name >> "repo"
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy
index 1c41312..b8f0ba2 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/IvyXmlModuleDescriptorParserTest.groovy
@@ -15,14 +15,8 @@
  */
 
 package org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser
-
 import org.apache.ivy.core.module.descriptor.*
-import org.apache.ivy.core.module.id.ModuleId
-import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.apache.ivy.core.settings.IvySettings
-import org.apache.ivy.plugins.conflict.ConflictManager
-import org.apache.ivy.plugins.conflict.FixedConflictManager
-import org.apache.ivy.plugins.conflict.NoConflictManager
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher
 import org.apache.ivy.plugins.matcher.GlobPatternMatcher
 import org.apache.ivy.plugins.matcher.PatternMatcher
@@ -130,12 +124,6 @@ class IvyXmlModuleDescriptorParserTest extends Specification {
         assertEquals(13, dependencies.length)
 
         verifyFullDependencies(dependencies)
-        verifyConflictManagers(md)
-
-        assertEquals(ModuleRevisionId.parse("yourorg#yourmodule1#BRANCH;1.0"),
-                md.mediate(new DefaultDependencyDescriptor(
-                        ModuleRevisionId.parse("yourorg#yourmodule1;2.0"), false))
-                        .getDependencyRevisionId())
 
         ExcludeRule[] rules = md.getAllExcludeRules()
         assertNotNull(rules)
@@ -148,28 +136,6 @@ class IvyXmlModuleDescriptorParserTest extends Specification {
         true
     }
 
-    def verifyConflictManagers(ModuleDescriptor md) {
-        ConflictManager cm = md.getConflictManager(new ModuleId("yourorg", "yourmodule1"))
-        assertNotNull(cm)
-        assertTrue(cm instanceof NoConflictManager)
-
-        cm = md.getConflictManager(new ModuleId("yourorg", "yourmodule2"))
-        assertNotNull(cm)
-        assertTrue(cm instanceof NoConflictManager)
-
-        cm = md.getConflictManager(new ModuleId("theirorg", "theirmodule1"))
-        assertNotNull(cm)
-        assertTrue(cm instanceof FixedConflictManager)
-        FixedConflictManager fcm = (FixedConflictManager) cm
-        assertEquals(2, fcm.getRevs().size())
-        assertTrue(fcm.getRevs().contains("1.0"))
-        assertTrue(fcm.getRevs().contains("1.1"))
-
-        cm = md.getConflictManager(new ModuleId("theirorg", "theirmodule2"))
-        assertNull(cm)
-        true
-    }
-
     def verifyFullDependencies(DependencyDescriptor[] dependencies) {
         // no conf def => equivalent to *->*
         DependencyDescriptor dd = getDependency(dependencies, "mymodule2")
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy
index e1dd1c9..b28e9a1 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.groovy
@@ -21,6 +21,7 @@ import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
 import org.gradle.api.artifacts.Configuration
 import org.gradle.api.artifacts.Module
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory
 import spock.lang.Specification
 
 /**
@@ -34,11 +35,13 @@ public class ResolveModuleDescriptorConverterTest extends Specification {
         def module = Mock(Module)
         def moduleDescriptor = Mock(DefaultModuleDescriptor)
         def moduleDescriptorFactory = Mock(ModuleDescriptorFactory)
+        def dependencyDescriptorFactory = Mock(DependencyDescriptorFactory)
         def configurationsConverter = Mock(ConfigurationsToModuleDescriptorConverter)
         def dependenciesConverter = Mock(DependenciesToModuleDescriptorConverter)
 
         ResolveModuleDescriptorConverter resolveModuleDescriptorConverter = new ResolveModuleDescriptorConverter(
                 moduleDescriptorFactory,
+                dependencyDescriptorFactory,
                 configurationsConverter,
                 dependenciesConverter);
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
index 29c16cb..3d70134 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
@@ -17,8 +17,13 @@
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
 
 import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.DependencyArtifact;
 import org.gradle.api.artifacts.ExcludeRule;
@@ -53,7 +58,7 @@ public abstract class AbstractDependencyDescriptorFactoryInternalTest {
     protected static final String TEST_DEP_CONF = "depconf1";
 
     protected static final ExcludeRule TEST_EXCLUDE_RULE = new org.gradle.api.internal.artifacts.DefaultExcludeRule("testOrg", null);
-    protected static final org.apache.ivy.core.module.descriptor.ExcludeRule TEST_IVY_EXCLUDE_RULE = HelperUtil.getTestExcludeRule("testOrg");
+    protected static final org.apache.ivy.core.module.descriptor.ExcludeRule TEST_IVY_EXCLUDE_RULE = getTestExcludeRule();
     protected ExcludeRuleConverter excludeRuleConverterStub = context.mock(ExcludeRuleConverter.class);
     protected final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(TEST_CONF));
     private DefaultDependencyArtifact artifact = new DefaultDependencyArtifact("name", "type", null, null, null);
@@ -121,5 +126,13 @@ public abstract class AbstractDependencyDescriptorFactoryInternalTest {
         assertEquals(artifact.getName(), artifactDescriptor.getName());
         assertEquals(artifact.getType(), artifactDescriptor.getType());
     }
+
+    private static DefaultExcludeRule getTestExcludeRule() {
+        return new DefaultExcludeRule(new ArtifactId(
+                new ModuleId("org", "testOrg"), PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION),
+                ExactPatternMatcher.INSTANCE, null);
+    }
 }
 
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
index a7730d6..6f21ece 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
@@ -39,7 +39,7 @@ public class ClientModuleDependencyDescriptorFactoryTest extends AbstractDepende
     private JUnit4Mockery context = new JUnit4Mockery();
 
     private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule = context.mock(ModuleDescriptorFactoryForClientModule.class);
-    private ClientModuleDependencyDescriptorFactory clientModuleDependencyDescriptorFactory = new ClientModuleDependencyDescriptorFactory(
+    private ClientModuleIvyDependencyDescriptorFactory clientModuleDependencyDescriptorFactory = new ClientModuleIvyDependencyDescriptorFactory(
             excludeRuleConverterStub,
             moduleDescriptorFactoryForClientModule
     );
@@ -67,11 +67,9 @@ public class ClientModuleDependencyDescriptorFactoryTest extends AbstractDepende
             will(returnValue(moduleDescriptorForClientModule));
         }});
 
-        clientModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, clientModule);
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        DefaultDependencyDescriptor dependencyDescriptor = clientModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, clientModule, moduleDescriptor);
         assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
-        assertEquals(testModuleRevisionId,
-                dependencyDescriptor.getDependencyRevisionId());
+        assertEquals(testModuleRevisionId, dependencyDescriptor.getDependencyRevisionId());
         assertFalse(dependencyDescriptor.isChanging());
     }
 
@@ -88,8 +86,7 @@ public class ClientModuleDependencyDescriptorFactoryTest extends AbstractDepende
             will(returnValue(moduleDescriptorForClientModule));
         }});
 
-        clientModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, clientModule);
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        DefaultDependencyDescriptor dependencyDescriptor = clientModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, clientModule, moduleDescriptor);
         assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(testModuleRevisionId));
     }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
index 0c2406c..12f2ca1 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
@@ -21,6 +21,7 @@ import org.gradle.api.artifacts.DependencySet;
 import org.gradle.api.artifacts.ExcludeRule;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.IvyConverterTestUtil;
 import org.gradle.util.HelperUtil;
@@ -51,9 +52,9 @@ public class DefaultDependenciesToModuleDescriptorConverterTest {
 
     private ModuleDependency dependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
     private ModuleDependency dependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
-    private ModuleDependency similarDependency1 = HelperUtil.createDependency("group", "name", "version");
-    private ModuleDependency similarDependency2 = HelperUtil.createDependency("group", "name", "version");
-    private ModuleDependency similarDependency3 = HelperUtil.createDependency("group", "name", "version");
+    private ModuleDependency similarDependency1 = createDependency("group", "name", "version");
+    private ModuleDependency similarDependency2 = createDependency("group", "name", "version");
+    private ModuleDependency similarDependency3 = createDependency("group", "name", "version");
     private org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRuleStub1 = context.mock(org.apache.ivy.core.module.descriptor.ExcludeRule.class, "rule1");
     private org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRuleStub2 = context.mock(org.apache.ivy.core.module.descriptor.ExcludeRule.class, "rule2");
 
@@ -102,7 +103,7 @@ public class DefaultDependenciesToModuleDescriptorConverterTest {
     private void associateDependencyWithDescriptor(final ModuleDependency dependency, final DefaultModuleDescriptor parent,
                                                    final Configuration configuration) {
         context.checking(new Expectations() {{
-            allowing(dependencyDescriptorFactoryStub).addDependencyDescriptor(with(sameInstance(configuration)),
+            allowing(dependencyDescriptorFactoryStub).addDependencyDescriptor(with(equal(configuration.getName())),
                     with(equal(parent)), with(sameInstance(dependency)));
         }});
     }
@@ -124,4 +125,8 @@ public class DefaultDependenciesToModuleDescriptorConverterTest {
         }});
         return configurationStub;
     }
+
+    ModuleDependency createDependency(String group, String name, String version) {
+        return new DefaultExternalModuleDependency(group, name, version);
+    }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactoryTest.groovy
new file mode 100644
index 0000000..367df66
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependencyDescriptorFactoryTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.ProjectDependency
+import spock.lang.Specification
+
+public class DefaultDependencyDescriptorFactoryTest extends Specification {
+    def configurationName = "conf";
+    def moduleDescriptor = Mock(DefaultModuleDescriptor);
+    def projectDependency = Mock(ProjectDependency);
+    def dependencyDescriptor = Mock(EnhancedDependencyDescriptor)
+
+    def "delegates to internal factory"() {
+        given:
+        def ivyDependencyDescriptorFactory1 = Mock(IvyDependencyDescriptorFactory);
+        def ivyDependencyDescriptorFactory2 = Mock(IvyDependencyDescriptorFactory);
+
+        when:
+        1 * ivyDependencyDescriptorFactory1.canConvert(projectDependency) >> false
+        1 * ivyDependencyDescriptorFactory2.canConvert(projectDependency) >> true
+        1 * ivyDependencyDescriptorFactory2.createDependencyDescriptor(configurationName, projectDependency, moduleDescriptor) >> dependencyDescriptor
+        1 * moduleDescriptor.addDependency(dependencyDescriptor)
+
+        then:
+        DefaultDependencyDescriptorFactory dependencyDescriptorFactory = new DefaultDependencyDescriptorFactory(
+                ivyDependencyDescriptorFactory1, ivyDependencyDescriptorFactory2
+        );
+        dependencyDescriptorFactory.addDependencyDescriptor(configurationName, moduleDescriptor, projectDependency);
+    }
+
+    def "fails where no internal factory can handle dependency type"() {
+        def ivyDependencyDescriptorFactory1 = Mock(IvyDependencyDescriptorFactory);
+
+        when:
+        ivyDependencyDescriptorFactory1.canConvert(projectDependency) >> false
+
+        and:
+        DefaultDependencyDescriptorFactory dependencyDescriptorFactory = new DefaultDependencyDescriptorFactory(
+                ivyDependencyDescriptorFactory1
+        );
+        dependencyDescriptorFactory.addDependencyDescriptor(configurationName, moduleDescriptor, projectDependency);
+
+        then:
+        thrown InvalidUserDataException
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
index 14ec924..bfa2b1c 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
@@ -15,12 +15,10 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
 
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.util.WrapUtil;
@@ -80,24 +78,12 @@ public class DefaultModuleDescriptorFactoryForClientModuleTest {
             this.dependencyDescriptor = dependencyDescriptor;
         }
 
-        public DefaultDependencyDescriptor addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
+        public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
                                             ModuleDependency dependency) {
             this.configuration = configuration;
             this.parent = moduleDescriptor;
             this.dependency = dependency;
-            return null;
         }
 
-        public DefaultDependencyDescriptor addDependencyDescriptor(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
-            this.configuration = configuration.getName();
-            this.parent = moduleDescriptor;
-            this.dependency = dependency;
-            return null;
-        }
-
-        public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
-            // do nothing
-            return null;
-        }
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java
deleted file mode 100644
index 64bf566..0000000
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.legacy.ClassImposteriser;
-import org.junit.Test;
-
-/**
- * @author Hans Dockter
- */
-public class DependencyDescriptorFactoryDelegateTest {
-    private JUnit4Mockery context = new JUnit4Mockery() {{
-        setImposteriser(ClassImposteriser.INSTANCE);
-    }};
-
-    private String configurationName = "conf";
-    private DefaultModuleDescriptor moduleDescriptorDummy = context.mock(DefaultModuleDescriptor.class);
-    private ProjectDependency projectDependency = context.mock(ProjectDependency.class);
-
-    @Test
-    public void convertShouldDelegateToTypeSpecificFactory() {
-        final DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal1 = context.mock(DependencyDescriptorFactoryInternal.class, "factory1");
-        final DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal2 = context.mock(DependencyDescriptorFactoryInternal.class, "factory2");
-        context.checking(new Expectations() {{
-            allowing(dependencyDescriptorFactoryInternal1).canConvert(projectDependency);
-            will(returnValue(false));
-            allowing(dependencyDescriptorFactoryInternal2).canConvert(projectDependency);
-            will(returnValue(true));
-            one(dependencyDescriptorFactoryInternal2).addDependencyDescriptor(configurationName, moduleDescriptorDummy, projectDependency);
-        }});
-        DependencyDescriptorFactoryDelegate dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
-            dependencyDescriptorFactoryInternal1, dependencyDescriptorFactoryInternal2            
-        );
-        dependencyDescriptorFactoryDelegate.addDependencyDescriptor(configurationName, moduleDescriptorDummy, projectDependency);
-    }
-
-    @Test(expected = InvalidUserDataException.class)
-    public void convertShouldThrowExForUnknownDependencyType() {
-        final DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal1 = context.mock(DependencyDescriptorFactoryInternal.class, "factory1");
-        context.checking(new Expectations() {{
-            allowing(dependencyDescriptorFactoryInternal1).canConvert(projectDependency);
-            will(returnValue(false));
-        }});
-        DependencyDescriptorFactoryDelegate dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
-            dependencyDescriptorFactoryInternal1            
-        );
-        dependencyDescriptorFactoryDelegate.addDependencyDescriptor(configurationName, moduleDescriptorDummy, projectDependency);
-    }
-}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
index c71bf50..1b58c4c 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
@@ -36,8 +36,8 @@ import static org.junit.Assert.assertThat;
 public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
     private JUnit4Mockery context = new JUnit4Mockery();
 
-    ExternalModuleDependencyDescriptorFactory externalModuleDependencyDescriptorFactory =
-            new ExternalModuleDependencyDescriptorFactory(excludeRuleConverterStub);
+    ExternalModuleIvyDependencyDescriptorFactory externalModuleDependencyDescriptorFactory =
+            new ExternalModuleIvyDependencyDescriptorFactory(excludeRuleConverterStub);
     
     @Test
     public void canConvert() {
@@ -48,8 +48,7 @@ public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDepen
     @Test
     public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
         ModuleDependency dependency = new DefaultExternalModuleDependency(null, "gradle-core", null, TEST_DEP_CONF);
-        externalModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency);
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        DefaultDependencyDescriptor dependencyDescriptor = externalModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, dependency, moduleDescriptor);
         assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(IvyUtil.createModuleRevisionId(dependency)));
     }
 
@@ -59,10 +58,7 @@ public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDepen
                 "gradle-core", "1.0", TEST_DEP_CONF);
         setUpDependency(moduleDependency);
 
-        externalModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor,
-                moduleDependency);
-        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor
-                .getDependencies()[0];
+        DefaultDependencyDescriptor dependencyDescriptor = externalModuleDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, moduleDependency, moduleDescriptor);
 
         assertEquals(moduleDependency.isChanging(), dependencyDescriptor.isChanging());
         assertEquals(dependencyDescriptor.isForce(), moduleDependency.isForce());
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java
index d443f3a..e5b7fb9 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java
@@ -1,66 +1,65 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
-
-import org.apache.ivy.core.module.id.ModuleRevisionId;
-import org.gradle.api.artifacts.ExternalModuleDependency;
-import org.gradle.api.artifacts.ProjectDependency;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
-import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
-import org.gradle.api.internal.project.AbstractProject;
-import org.gradle.util.HelperUtil;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
-public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
-    private JUnit4Mockery context = new JUnit4Mockery();
-
-    private ProjectDependencyDescriptorFactory projectDependencyDescriptorFactory =
-            new ProjectDependencyDescriptorFactory(excludeRuleConverterStub);
-
-    @Test
-    public void canConvert() {
-        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), equalTo(true));
-        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), equalTo(false));
-    }
-
-    @Test
-    public void testCreateFromProjectDependency() {
-        ProjectDependency projectDependency = createProjectDependency(TEST_DEP_CONF);
-        setUpDependency(projectDependency);
-        projectDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, projectDependency);
-        ProjectDependencyDescriptor dependencyDescriptor = (ProjectDependencyDescriptor) moduleDescriptor.getDependencies()[0];
-
-        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
-        assertFalse(dependencyDescriptor.isChanging());
-        assertFalse(dependencyDescriptor.isForce());
-        assertEquals(ModuleRevisionId.newInstance("someGroup", "test", "someVersion"), dependencyDescriptor.getDependencyRevisionId());
-        assertSame(projectDependency.getDependencyProject(), dependencyDescriptor.getTargetProject());
-    }
-
-    private ProjectDependency createProjectDependency(String dependencyConfiguration) {
-        AbstractProject dependencyProject = HelperUtil.createRootProject();
-        dependencyProject.setGroup("someGroup");
-        dependencyProject.setVersion("someVersion");
-        return new DefaultProjectDependency(dependencyProject, dependencyConfiguration, new ProjectDependenciesBuildInstruction(true));
-    }
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.project.AbstractProject;
+import org.gradle.util.HelperUtil;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+//TODO SF spockify
+public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private ProjectIvyDependencyDescriptorFactory projectDependencyDescriptorFactory =
+            new ProjectIvyDependencyDescriptorFactory(excludeRuleConverterStub);
+
+    @Test
+    public void canConvert() {
+        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), equalTo(true));
+        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), equalTo(false));
+    }
+
+    @Test
+    public void testCreateFromProjectDependency() {
+        ProjectDependency projectDependency = createProjectDependency(TEST_DEP_CONF);
+        setUpDependency(projectDependency);
+        ProjectDependencyDescriptor dependencyDescriptor = (ProjectDependencyDescriptor) projectDependencyDescriptorFactory.createDependencyDescriptor(TEST_CONF, projectDependency, moduleDescriptor);
+
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+        assertFalse(dependencyDescriptor.isChanging());
+        assertFalse(dependencyDescriptor.isForce());
+        assertEquals(ModuleRevisionId.newInstance("someGroup", "test", "someVersion"), dependencyDescriptor.getDependencyRevisionId());
+        assertSame(projectDependency.getDependencyProject(), dependencyDescriptor.getTargetProject());
+    }
+
+    private ProjectDependency createProjectDependency(String dependencyConfiguration) {
+        AbstractProject dependencyProject = HelperUtil.createRootProject();
+        dependencyProject.setGroup("someGroup");
+        dependencyProject.setVersion("someVersion");
+        return new DefaultProjectDependency(dependencyProject, dependencyConfiguration, null, true);
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactoryTest.groovy
new file mode 100644
index 0000000..9b92d9b
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ReflectiveDependencyDescriptorFactoryTest.groovy
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies
+
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import spock.lang.Specification
+import org.apache.ivy.core.module.descriptor.*
+
+/**
+ * by Szczepan Faber, created at: 1/29/13
+ */
+class ReflectiveDependencyDescriptorFactoryTest extends Specification {
+
+    def factory = new ReflectiveDependencyDescriptorFactory()
+    def mrid = ModuleRevisionId.newInstance("org.gradle", "gradle-core", "1.0")
+    def target = ModuleRevisionId.newInstance("org.gradle", "gradle-core", "2.0")
+    def dynamicId = ModuleRevisionId.newInstance("org.gradle", "gradle-core", "latest-integration")
+
+    ModuleDescriptor md = DefaultModuleDescriptor.newBasicInstance(mrid, new Date())
+
+    def "creates updated copy of the descriptor"() {
+        def source = new DefaultDependencyDescriptor(md, mrid, dynamicId, true, true, true)
+
+        when:
+        def copy = factory.create(source, target)
+
+        then:
+        copy.dependencyRevisionId == target
+
+        and:
+        copy.force
+        copy.changing
+        copy.transitive
+        copy.dynamicConstraintDependencyRevisionId == dynamicId
+        copy.namespace == source.namespace
+        copy.sourceModule == source.sourceModule
+
+        and:
+        copy.md == source.md
+        copy.parentId == source.parentId
+        copy.confs == source.confs
+        copy.excludeRules == source.excludeRules
+        copy.includeRules == source.includeRules
+        copy.dependencyArtifacts == source.dependencyArtifacts
+    }
+
+    def "correctly configures flags"() {
+        def source = new DefaultDependencyDescriptor(md, mrid, dynamicId, false, false, false)
+
+        when:
+        def copy = factory.create(source, target)
+
+        then:
+        !copy.force
+        !copy.changing
+        !copy.transitive
+    }
+
+    def "correctly configures rules and artifacts"() {
+        def source = new DefaultDependencyDescriptor(md, mrid, dynamicId, true, true, false)
+        source.addIncludeRule("foo", Mock(IncludeRule))
+        source.addExcludeRule("foo", Mock(ExcludeRule))
+        source.addDependencyArtifact("foo", Mock(DependencyArtifactDescriptor))
+
+        when:
+        def copy = factory.create(source, target)
+
+        then:
+        copy.force
+        copy.changing
+        !copy.transitive
+
+        and:
+        copy.confs == source.confs
+        copy.excludeRules == source.excludeRules
+        copy.includeRules == source.includeRules
+        copy.dependencyArtifacts == source.dependencyArtifacts
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
index 6a90639..30d2d65 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolverTest.groovy
@@ -21,14 +21,18 @@ import org.apache.ivy.core.module.id.ModuleRevisionId
 import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.ivyservice.BuildableModuleVersionResolveResult
 import org.gradle.api.internal.artifacts.ivyservice.DependencyToModuleResolver
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DependencyMetaData
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectDependencyDescriptor
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.initialization.ProjectAccessListener
 import spock.lang.Specification
 
 class ProjectDependencyResolverTest extends Specification {
     final ProjectModuleRegistry registry = Mock()
-    final DependencyDescriptor dependencyDescriptor = Mock()
     final ModuleRevisionId moduleRevisionId = Mock()
     final DependencyToModuleResolver target = Mock()
-    final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, target)
+    final ProjectAccessListener projectAccessListener = Mock()
+    final ProjectDependencyResolver resolver = new ProjectDependencyResolver(registry, target, projectAccessListener)
 
     def "resolves project dependency"() {
         setup:
@@ -36,11 +40,18 @@ class ProjectDependencyResolverTest extends Specification {
         1 * moduleRevisionId.name >> "project"
         1 * moduleRevisionId.revision >> "1.0"
 
-        ModuleDescriptor moduleDescriptor = Mock()
-        BuildableModuleVersionResolveResult result = Mock()
+        def moduleDescriptor = Mock(ModuleDescriptor)
+        def result = Mock(BuildableModuleVersionResolveResult)
+        def dependencyProject = Mock(ProjectInternal)
+        def dependencyDescriptor = Stub(ProjectDependencyDescriptor) {
+            getTargetProject() >> dependencyProject
+        }
+        def dependencyMetaData = Stub(DependencyMetaData) {
+            getDescriptor() >> dependencyDescriptor
+        }
 
         when:
-        resolver.resolve(dependencyDescriptor, result)
+        resolver.resolve(dependencyMetaData, result)
 
         then:
         1 * registry.findProject(dependencyDescriptor) >> moduleDescriptor
@@ -51,18 +62,22 @@ class ProjectDependencyResolverTest extends Specification {
             moduleVersionIdentifier.name == "project"
             moduleVersionIdentifier.version == "1.0"
         }
+        1 * projectAccessListener.beforeResolvingProjectDependency(dependencyProject)
         0 * result._
     }
 
     def "delegates to backing resolver for non-project dependency"() {
-        BuildableModuleVersionResolveResult result = Mock()
+        def result = Mock(BuildableModuleVersionResolveResult)
+        def dependencyDescriptor = Mock(DependencyDescriptor)
+        def dependencyMetaData = Stub(DependencyMetaData) {
+            getDescriptor() >> dependencyDescriptor
+        }
 
         when:
-        resolver.resolve(dependencyDescriptor, result)
+        resolver.resolve(dependencyMetaData, result)
 
         then:
-        1 * registry.findProject(dependencyDescriptor) >> null
-        1 * target.resolve(dependencyDescriptor, result)
-        0 * result._
+        1 * target.resolve(dependencyMetaData, result)
+        0 * _
     }
 }
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
index d5df830..0d8a100 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolutionstrategy/DefaultResolutionStrategySpec.groovy
@@ -22,6 +22,8 @@ import org.gradle.api.internal.artifacts.DependencyResolveDetailsInternal
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
 import spock.lang.Specification
 
+import java.util.concurrent.TimeUnit
+
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.util.Assertions.assertThat
 
@@ -30,7 +32,8 @@ import static org.gradle.util.Assertions.assertThat
  */
 public class DefaultResolutionStrategySpec extends Specification {
 
-    def strategy = new DefaultResolutionStrategy()
+    def cachePolicy = Mock(DefaultCachePolicy)
+    def strategy = new DefaultResolutionStrategy(cachePolicy, [] as Set)
 
     def "allows setting forced modules"() {
         expect:
@@ -141,11 +144,8 @@ public class DefaultResolutionStrategySpec extends Specification {
     def "provides a copy"() {
         given:
         def newCachePolicy = Mock(DefaultCachePolicy)
-        def cachePolicy = Mock(DefaultCachePolicy) {
-            copy() >> newCachePolicy
-        }
+        cachePolicy.copy() >> newCachePolicy
 
-        strategy = new DefaultResolutionStrategy(cachePolicy, [] as Set)
         strategy.failOnVersionConflict()
         strategy.force("org:foo:1.0")
         strategy.eachDependency(Mock(Action))
@@ -161,4 +161,36 @@ public class DefaultResolutionStrategySpec extends Specification {
         strategy.cachePolicy == cachePolicy
         copy.cachePolicy == newCachePolicy
     }
+
+    def "configures changing modules cache with jdk5+ units"() {
+        when:
+        strategy.cacheChangingModulesFor(30000, "milliseconds")
+
+        then:
+        1 * cachePolicy.cacheChangingModulesFor(30000, TimeUnit.MILLISECONDS)
+    }
+
+    def "configures changing modules cache with jdk6+ units"() {
+        when:
+        strategy.cacheChangingModulesFor(5, "minutes")
+
+        then:
+        1 * cachePolicy.cacheChangingModulesFor(5 * 60 * 1000, TimeUnit.MILLISECONDS)
+    }
+
+    def "configures dynamic version cache with jdk5+ units"() {
+        when:
+        strategy.cacheDynamicVersionsFor(10000, "milliseconds")
+
+        then:
+        1 * cachePolicy.cacheDynamicVersionsFor(10000, TimeUnit.MILLISECONDS)
+    }
+
+    def "configures dynamic version cache with jdk6+ units"() {
+        when:
+        strategy.cacheDynamicVersionsFor(1, "hours")
+
+        then:
+        1 * cachePolicy.cacheDynamicVersionsFor(1 * 60 * 60 * 1000, TimeUnit.MILLISECONDS)
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
index 7d428b5..48e159f 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy
@@ -25,11 +25,12 @@ import org.apache.ivy.core.resolve.ResolveOptions
 import org.apache.ivy.plugins.matcher.ExactPatternMatcher
 import org.apache.ivy.plugins.matcher.PatternMatcher
 import org.gradle.api.artifacts.*
-import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier
 import org.gradle.api.internal.artifacts.DefaultModuleVersionSelector
 import org.gradle.api.internal.artifacts.DefaultResolvedArtifact
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal
 import org.gradle.api.internal.artifacts.ivyservice.*
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.DefaultBuildableModuleVersionMetaData
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ModuleVersionMetaData
 import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.EnhancedDependencyDescriptor
 import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.ResolvedConfigurationListener
 import org.gradle.api.specs.Spec
@@ -48,13 +49,13 @@ class DependencyGraphBuilderTest extends Specification {
     final ModuleConflictResolver conflictResolver = Mock()
     final DependencyToModuleVersionIdResolver dependencyResolver = Mock()
     final ResolvedConfigurationListener listener = Mock()
-    final DefaultModuleDescriptor root = revision('root')
+    final ModuleVersionMetaData root = revision('root')
     final DependencyGraphBuilder builder = new DependencyGraphBuilder(moduleDescriptorConverter, resolvedArtifactFactory, dependencyResolver, conflictResolver)
 
     def setup() {
         config(root, 'root', 'default')
         _ * configuration.name >> 'root'
-        _ * moduleDescriptorConverter.convert(_, _) >> root
+        _ * moduleDescriptorConverter.convert(_, _) >> root.descriptor
         _ * resolvedArtifactFactory.create(_, _, _) >> { owner, artifact, resolver ->
             return new DefaultResolvedArtifact(owner, artifact, null)
         }
@@ -822,47 +823,49 @@ class DependencyGraphBuilderTest extends Specification {
 
     def revision(String name, String revision = '1.0') {
         DefaultModuleDescriptor descriptor = new DefaultModuleDescriptor(new ModuleRevisionId(new ModuleId("group", name), revision), "release", new Date())
-        config(descriptor, 'default')
+        DefaultBuildableModuleVersionMetaData metaData = new DefaultBuildableModuleVersionMetaData()
+        metaData.resolved(descriptor, false, null)
+        config(metaData, 'default')
         descriptor.addArtifact('default', new DefaultArtifact(descriptor.moduleRevisionId, new Date(), "art1", "art", "zip"))
-        return descriptor
+        return metaData
     }
 
-    def config(DefaultModuleDescriptor descriptor, String name, String... extendsFrom) {
+    def config(ModuleVersionMetaData metaData, String name, String... extendsFrom) {
         def configuration = new Configuration(name, Configuration.Visibility.PUBLIC, null, extendsFrom, true, null)
-        descriptor.addConfiguration(configuration)
+        metaData.descriptor.addConfiguration(configuration)
         return configuration
     }
 
-    def traverses(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
-        def descriptor = dependsOn(args, from, to.moduleRevisionId)
-        def moduleVersionIdentifier = toModuleVersionIdentifier(to.moduleRevisionId)
-        def idResolveResult = selectorResolvesTo(descriptor, moduleVersionIdentifier);
+    def traverses(Map<String, ?> args = [:], ModuleVersionMetaData from, ModuleVersionMetaData to) {
+        def descriptor = dependsOn(args, from.descriptor, to.descriptor.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, to.id);
         ModuleVersionResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
-        1 * resolveResult.id >> moduleVersionIdentifier
-        1 * resolveResult.descriptor >> { println "RESOLVE $from.moduleRevisionId -> $to.moduleRevisionId"; return to }
+        1 * resolveResult.id >> to.id
+        1 * resolveResult.metaData >> to
     }
 
-    def doesNotResolve(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
-        def descriptor = dependsOn(args, from, to.moduleRevisionId)
+    def doesNotResolve(Map<String, ?> args = [:], ModuleVersionMetaData from, ModuleVersionMetaData to) {
+        def descriptor = dependsOn(args, from.descriptor, to.descriptor.moduleRevisionId)
         ModuleVersionIdResolveResult result = Mock()
-        (0..1) * dependencyResolver.resolve(descriptor) >> result
-        (0..1) * result.id >> toModuleVersionIdentifier(to.moduleRevisionId);
+        (0..1) * dependencyResolver.resolve({it.descriptor == descriptor}) >> result
+        (0..1) * result.id >> to.id;
+        _ * result.failure >> null
+        _ * result.selectionReason >> null
+        0 * result._
     }
 
-    def traversesMissing(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
-        def descriptor = dependsOn(args, from, to.moduleRevisionId)
-        def moduleVersionIdentifier = toModuleVersionIdentifier(to.moduleRevisionId)
-        def idResolveResult = selectorResolvesTo(descriptor, moduleVersionIdentifier)
+    def traversesMissing(Map<String, ?> args = [:], ModuleVersionMetaData from, ModuleVersionMetaData to) {
+        def descriptor = dependsOn(args, from.descriptor, to.descriptor.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, to.id)
         ModuleVersionResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
         1 * resolveResult.id >> { throw new ModuleVersionNotFoundException(newId("org", "a", "1.2")) }
     }
 
-    def traversesBroken(Map<String, ?> args = [:], DefaultModuleDescriptor from, DefaultModuleDescriptor to) {
-        def descriptor = dependsOn(args, from, to.moduleRevisionId)
-        def moduleVersionIdentifier = toModuleVersionIdentifier(to.moduleRevisionId)
-        def idResolveResult = selectorResolvesTo(descriptor, moduleVersionIdentifier)
+    def traversesBroken(Map<String, ?> args = [:], ModuleVersionMetaData from, ModuleVersionMetaData to) {
+        def descriptor = dependsOn(args, from.descriptor, to.descriptor.moduleRevisionId)
+        def idResolveResult = selectorResolvesTo(descriptor, to.id)
         ModuleVersionResolveResult resolveResult = Mock()
         1 * idResolveResult.resolve() >> resolveResult
         1 * resolveResult.id >> { throw new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken") }
@@ -876,10 +879,10 @@ class DependencyGraphBuilderTest extends Specification {
         moduleVersionIdentifier
     }
 
-    def brokenSelector(Map<String, ?> args = [:], DefaultModuleDescriptor from, String to) {
-        def descriptor = dependsOn(args, from, ModuleRevisionId.newInstance("group", to, "1.0"))
+    def brokenSelector(Map<String, ?> args = [:], ModuleVersionMetaData from, String to) {
+        def descriptor = dependsOn(args, from.descriptor, ModuleRevisionId.newInstance("group", to, "1.0"))
         ModuleVersionIdResolveResult result = Mock()
-        (0..1) * dependencyResolver.resolve(descriptor) >> result
+        (0..1) * dependencyResolver.resolve({it.descriptor == descriptor}) >> result
         _ * result.failure >> new ModuleVersionResolveException(newSelector("a", "b", "c"), "broken")
         _ * result.selectionReason >> VersionSelectionReasons.REQUESTED
         0 * result._
@@ -894,7 +897,7 @@ class DependencyGraphBuilderTest extends Specification {
         descriptor.addDependencyConfiguration("default", "default")
         if (args.exclude) {
             descriptor.addExcludeRule("default", new DefaultExcludeRule(new ArtifactId(
-                    args.exclude.moduleRevisionId.moduleId, PatternMatcher.ANY_EXPRESSION,
+                    args.exclude.descriptor.moduleRevisionId.moduleId, PatternMatcher.ANY_EXPRESSION,
                     PatternMatcher.ANY_EXPRESSION,
                     PatternMatcher.ANY_EXPRESSION),
                     ExactPatternMatcher.INSTANCE, null))
@@ -905,13 +908,13 @@ class DependencyGraphBuilderTest extends Specification {
 
     def selectorResolvesTo(DependencyDescriptor descriptor, ModuleVersionIdentifier to) {
         ModuleVersionIdResolveResult result = Mock()
-        1 * dependencyResolver.resolve(descriptor) >> result
+        1 * dependencyResolver.resolve({it.descriptor == descriptor}) >> result
         1 * result.id >> to
         return result
     }
 
-    def ids(ModuleDescriptor... descriptors) {
-        return descriptors.collect { new DefaultModuleVersionIdentifier(it.moduleRevisionId.organisation, it.moduleRevisionId.name, it.moduleRevisionId.revision) } as Set
+    def ids(ModuleVersionMetaData... descriptors) {
+        return descriptors.collect { it.id } as Set
     }
 
     def modules(LenientConfiguration config) {
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolverTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolverTest.groovy
new file mode 100644
index 0000000..062c409
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/VersionSelectionReasonResolverTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine
+
+import spock.lang.Specification
+import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons
+
+/**
+ * by Szczepan Faber, created at: 1/29/13
+ */
+class VersionSelectionReasonResolverTest extends Specification {
+
+    def "configures selection reason"() {
+        def delegate = Mock(ModuleConflictResolver)
+        VersionSelectionReasonResolver resolver = new VersionSelectionReasonResolver(delegate)
+
+        def root = Mock(ModuleRevisionResolveState)
+        def candidate1 = Mock(ModuleRevisionResolveState)
+        def candidate2 = Mock(ModuleRevisionResolveState)
+
+        when:
+        def out = resolver.select([candidate1, candidate2], root)
+
+        then:
+        out == candidate2
+
+        and:
+        1 * delegate.select([candidate1, candidate2], root) >> candidate2
+        1 * candidate2.getSelectionReason() >> VersionSelectionReasons.REQUESTED
+        1 * candidate2.setSelectionReason(VersionSelectionReasons.CONFLICT_RESOLUTION)
+        0 * _._
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasonsTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasonsTest.groovy
new file mode 100644
index 0000000..1584706
--- /dev/null
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/VersionSelectionReasonsTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result
+
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.VersionSelectionReasons.*
+
+/**
+ * by Szczepan Faber, created at: 1/29/13
+ */
+class VersionSelectionReasonsTest extends Specification {
+
+    def "decorates with conflict resolution"() {
+        expect:
+        withConflictResolution(REQUESTED) == CONFLICT_RESOLUTION
+        withConflictResolution(SELECTED_BY_RULE) == CONFLICT_RESOLUTION_BY_RULE
+        withConflictResolution(CONFLICT_RESOLUTION) == CONFLICT_RESOLUTION
+        withConflictResolution(CONFLICT_RESOLUTION_BY_RULE) == CONFLICT_RESOLUTION_BY_RULE
+    }
+
+    def "does not decorate unsupported reasons"() {
+        when:
+        withConflictResolution(FORCED)
+
+        then:
+        def ex = thrown(IllegalArgumentException)
+        ex.message.contains FORCED.toString()
+    }
+}
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepositoryTest.groovy
index 7e923d3..9fcf452 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultFlatDirArtifactRepositoryTest.groovy
@@ -37,7 +37,7 @@ class DefaultFlatDirArtifactRepositoryTest extends Specification {
         repository.dirs('a', 'b')
 
         when:
-        def repo = repository.createResolver()
+        def repo = repository.createLegacyDslObject()
 
         then:
         repo instanceof FileSystemResolver
@@ -57,7 +57,7 @@ class DefaultFlatDirArtifactRepositoryTest extends Specification {
         _ * fileResolver.resolveFiles(_) >> new SimpleFileCollection()
 
         when:
-        repository.createResolver()
+        repository.createLegacyDslObject()
 
         then:
         InvalidUserDataException e = thrown()
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
index ca49111..44adf6b 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy
@@ -18,7 +18,8 @@ package org.gradle.api.internal.artifacts.repositories
 import org.apache.ivy.core.cache.RepositoryCacheManager
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.repositories.PasswordCredentials
-import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceResolver
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ExternalResourceResolverAdapter
+import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
 import org.gradle.api.internal.externalresource.cached.CachedExternalResourceIndex
 import org.gradle.api.internal.externalresource.local.LocallyAvailableResourceFinder
@@ -44,6 +45,12 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
             fileResolver, credentials, transportFactory, locallyAvailableResourceFinder, new DirectInstantiator()
     )
 
+    def "default values"() {
+        expect:
+        repository.url == null
+        !repository.resolve.dynamicMode
+    }
+
     def "cannot create a resolver for url with unknown scheme"() {
         repository.name = 'name'
         repository.artifactPattern 'pattern1'
@@ -52,11 +59,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         fileResolver.resolveUri('pattern1') >> new URI('scheme:resource1')
 
         when:
-        repository.createResolver()
+        repository.createRealResolver()
 
         then:
         InvalidUserDataException e = thrown()
-        e.message == "You may only specify 'file', 'http' and 'https' urls for an ivy repository."
+        e.message == "You may only specify 'file', 'http' and 'https' urls for an Ivy repository."
     }
 
     def "cannot creates a resolver for mixed url scheme"() {
@@ -69,11 +76,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         fileResolver.resolveUri('pattern2') >> new URI('file:resource2')
 
         when:
-        repository.createResolver()
+        repository.createRealResolver()
 
         then:
         InvalidUserDataException e = thrown()
-        e.message == "You cannot mix file and http(s) urls for a single ivy repository. Please declare 2 separate repositories."
+        e.message == "You cannot mix file and http(s) urls for a single Ivy repository. Please declare 2 separate repositories."
     }
 
     def "creates a resolver for HTTP patterns"() {
@@ -88,11 +95,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
 
         when:
-        def resolver = repository.createResolver()
+        def resolver = repository.createRealResolver()
 
         then:
         with(resolver) {
-            it instanceof ExternalResourceResolver
+            it instanceof IvyResolver
             repository instanceof ExternalResourceRepository
             name == 'name'
             artifactPatterns == ['http://host/[organisation]/[artifact]-[revision].[ext]', 'http://other/[module]/[artifact]-[revision].[ext]']
@@ -113,11 +120,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         transportFactory.createFileTransport('name') >> new FileTransport('name', cacheManager, Mock(TemporaryFileProvider))
 
         when:
-        def resolver = repository.createResolver()
+        def resolver = repository.createRealResolver()
 
         then:
         with(resolver) {
-            it instanceof ExternalResourceResolver
+            it instanceof IvyResolver
             repository instanceof ExternalResourceRepository
             name == 'name'
             artifactPatterns == ["${file.absolutePath}/[organisation]/[artifact]-[revision].[ext]", "${file.absolutePath}/[organisation]/[module]/[artifact]-[revision].[ext]"]
@@ -125,6 +132,33 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         }
     }
 
+    def "creates a DSL wrapper for resolver"() {
+        repository.name = 'name'
+        repository.artifactPattern 'repo/[organisation]/[artifact]-[revision].[ext]'
+        def file = new File("test")
+        def fileUri = file.toURI()
+
+        given:
+        fileResolver.resolveUri('repo/') >> fileUri
+        transportFactory.createFileTransport('name') >> new FileTransport('name', cacheManager, Mock(TemporaryFileProvider))
+
+        when:
+        def wrapper = repository.createLegacyDslObject()
+
+        then:
+        with(wrapper) {
+            it instanceof LegacyDependencyResolver
+            resolver instanceof IvyResolver
+        }
+
+        when:
+        def repo = wrapper.createResolver()
+
+        then:
+        repo instanceof ExternalResourceResolverAdapter
+        repo.resolver.is(wrapper.resolver)
+    }
+
     def "uses gradle patterns with specified url and default layout"() {
         repository.name = 'name'
         repository.url = 'http://host'
@@ -134,11 +168,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
 
         when:
-        def resolver = repository.createResolver()
+        def resolver = repository.createRealResolver()
 
         then:
         with(resolver) {
-            it instanceof ExternalResourceResolver
+            it instanceof IvyResolver
             repository instanceof ExternalResourceRepository
             name == 'name'
             artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])']
@@ -156,11 +190,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
 
         when:
-        def resolver = repository.createResolver()
+        def resolver = repository.createRealResolver()
 
         then:
         with(resolver) {
-            it instanceof ExternalResourceResolver
+            it instanceof IvyResolver
             repository instanceof ExternalResourceRepository
             name == 'name'
             artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])']
@@ -182,11 +216,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
 
         when:
-        def resolver = repository.createResolver()
+        def resolver = repository.createRealResolver()
 
         then:
         with(resolver) {
-            it instanceof ExternalResourceResolver
+            it instanceof IvyResolver
             repository instanceof ExternalResourceRepository
             name == 'name'
             artifactPatterns == ['http://host/[module]/[revision]/[artifact](.[ext])']
@@ -209,11 +243,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
 
         when:
-        def resolver = repository.createResolver()
+        def resolver = repository.createRealResolver()
 
         then:
         with(resolver) {
-            it instanceof ExternalResourceResolver
+            it instanceof IvyResolver
             repository instanceof ExternalResourceRepository
             name == 'name'
             artifactPatterns == ['http://host/[module]/[revision]/[artifact](.[ext])']
@@ -233,11 +267,11 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
 
         when:
-        def resolver = repository.createResolver()
+        def resolver = repository.createRealResolver()
 
         then:
         with(resolver) {
-            it instanceof ExternalResourceResolver
+            it instanceof IvyResolver
             repository instanceof ExternalResourceRepository
             name == 'name'
             artifactPatterns == ['http://host/[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])', 'http://host/[other]/artifact']
@@ -259,7 +293,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         fileResolver.resolveUri('http://other/') >> new URI('http://other/')
 
         when:
-        def resolver = repository.createResolver()
+        def resolver = repository.createRealResolver()
 
         then:
         resolver.artifactPatterns == ['http://host/[layoutPattern]', 'http://other/[additionalPattern]']
@@ -271,7 +305,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification {
         transportFactory.createHttpTransport('name', credentials) >> createHttpTransport("name", credentials)
 
         when:
-        repository.createResolver()
+        repository.createRealResolver()
 
         then:
         InvalidUserDataException e = thrown()
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy
index ca2b132..7ce96a5 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.repositories
 import org.apache.ivy.core.cache.RepositoryCacheManager
 import org.gradle.api.InvalidUserDataException
 import org.gradle.api.artifacts.repositories.PasswordCredentials
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ExternalResourceResolverAdapter
 import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver
 import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory
 import org.gradle.api.internal.externalresource.transport.file.FileTransport
@@ -53,7 +54,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         repository.url = 'repo-dir'
 
         when:
-        def repo = repository.createResolver()
+        def repo = repository.createRealResolver()
 
         then:
         repo instanceof MavenResolver
@@ -75,13 +76,39 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         repository.url = 'repo-dir'
 
         when:
-        def repo = repository.createResolver()
+        def repo = repository.createRealResolver()
 
         then:
         repo instanceof MavenResolver
         repo.root == "${uri}/"
     }
 
+    def "creates a DSL wrapper for the repository"() {
+        given:
+        def file = new File('repo')
+        def uri = file.toURI()
+        _ * this.resolver.resolveUri('repo-dir') >> uri
+        transportFactory.createFileTransport('repo') >> new FileTransport('repo', cacheManager, Mock(TemporaryFileProvider))
+
+        and:
+        repository.name = 'repo'
+        repository.url = 'repo-dir'
+
+        when:
+        def resolver = repository.createLegacyDslObject()
+
+        then:
+        resolver instanceof LegacyMavenResolver
+        resolver.resolver instanceof MavenResolver
+
+        when:
+        def repo = resolver.createResolver()
+
+        then:
+        repo instanceof ExternalResourceResolverAdapter
+        repo.resolver.is(resolver.resolver)
+    }
+
     def "creates repository with additional artifact URLs"() {
         given:
         def uri = new URI("http://localhost:9090/repo")
@@ -98,7 +125,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
         repository.artifactUrls('repo1', 'repo2')
 
         when:
-        def repo = repository.createResolver()
+        def repo = repository.createRealResolver()
 
         then:
         repo instanceof MavenResolver
@@ -115,7 +142,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification {
 
     def "fails when no root url specified"() {
         when:
-        repository.createResolver()
+        repository.createLegacyDslObject()
 
         then:
         InvalidUserDataException e = thrown()
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy
index 32a5814..2fdda25 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/DependencyNotationParserTest.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011 the original author or authors.
+ * Copyright 2013 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
index f76c398..7b877c6 100644
--- a/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
+++ b/subprojects/core-impl/src/test/groovy/org/gradle/api/internal/notations/ProjectDependencyFactoryTest.groovy
@@ -16,10 +16,12 @@
 package org.gradle.api.internal.notations;
 
 
-import org.gradle.internal.reflect.DirectInstantiator
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.artifacts.DefaultProjectDependencyFactory
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder
 import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.initialization.ProjectAccessListener
+import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.util.GUtil
 import spock.lang.Specification
 
@@ -28,12 +30,13 @@ import spock.lang.Specification
  */
 public class ProjectDependencyFactoryTest extends Specification {
 
-    def ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(false);
-    def ProjectDependencyFactory factory = new ProjectDependencyFactory(projectDependenciesBuildInstruction, new DirectInstantiator());
-    def ProjectFinder projectFinder = Mock(ProjectFinder.class);
-    def ProjectInternal projectDummy = Mock(ProjectInternal.class);
+    def projectDummy = Mock(ProjectInternal)
+    def projectFinder = Mock(ProjectFinder)
+
+    def depFactory = new DefaultProjectDependencyFactory(Mock(ProjectAccessListener), new DirectInstantiator(), true)
+    def factory = new ProjectDependencyFactory(depFactory)
 
-    def testCreateProjectDependencyWithMapNotation() {
+    def "creates project dependency with map notation"() {
         given:
         boolean expectedTransitive = false;
         final Map<String, Object> mapNotation = GUtil.map("path", ":foo:bar", "configuration", "compile", "transitive", expectedTransitive);
@@ -49,4 +52,16 @@ public class ProjectDependencyFactoryTest extends Specification {
         projectDependency.getConfiguration() == "compile"
         projectDependency.isTransitive() == expectedTransitive
     }
+
+    def "fails with decent message if provided map is invalid"() {
+        given:
+        projectFinder.getProject(':foo:bar') >> projectDummy
+
+        when:
+        factory.createFromMap(projectFinder, GUtil.map("paths", ":foo:bar"));
+
+        then:
+        def ex = thrown(InvalidUserDataException)
+        ex.message.contains("Required keys [path] are missing from map")
+    }
 }
diff --git a/subprojects/core/core.gradle b/subprojects/core/core.gradle
index 53b252e..fd5a202 100755
--- a/subprojects/core/core.gradle
+++ b/subprojects/core/core.gradle
@@ -50,6 +50,7 @@ dependencies {
 
     testCompile libraries.log4j_to_slf4j
     testCompile libraries.jcl_to_slf4j
+    testCompile libraries.jsoup
 
     testRuntime libraries.xerces
     testRuntime project(":diagnostics")
@@ -57,8 +58,6 @@ dependencies {
     testFixturesCompile project(":internalTesting")
     testFixturesRuntime project(':coreImpl')
 
-    testCompile project(':coreImpl')
-
     integTestCompile project(":internalIntegTesting")
 
     reports 'css3-pie:css3-pie:1.0beta3'
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 a4a081f..6d50251 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ConfigurationOnDemandIntegrationTest.groovy
@@ -14,19 +14,21 @@
  * limitations under the License.
  */
 
-
 package org.gradle.api
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.ProjectLifecycleFixture
+import org.junit.Rule
 
 /**
  * by Szczepan Faber, created at: 11/21/12
  */
 class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
 
+    @Rule ProjectLifecycleFixture fixture = new ProjectLifecycleFixture(executer, temporaryFolder)
+
     def setup() {
-        file("gradle.properties") << "systemProp.org.gradle.configuration.ondemand=true"
-        alwaysUsing { it.withArgument('-i') }
+        file("gradle.properties") << "org.gradle.configureondemand=true"
     }
 
     def "works with single-module project"() {
@@ -34,7 +36,8 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         when:
         run("foo")
         then:
-        result.assertProjectsEvaluated(":")
+        fixture.assertProjectsConfigured(":")
+        assert output.contains("Configuration on demand is an incubating feature")
     }
 
     def "evaluates only project referenced in the task list"() {
@@ -45,7 +48,16 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run(":foo", ":util:impl:foo")
 
         then:
-        result.assertProjectsEvaluated(":", ":util:impl")
+        fixture.assertProjectsConfigured(":", ":util:impl")
+        assert output.contains("Configuration on demand is an incubating feature")
+    }
+
+    def "does not show configuration on demand message in a regular mode"() {
+        file("gradle.properties").text = "org.gradle.configureondemand=false"
+        when:
+        run()
+        then:
+        assert !output.contains("Configuration on demand is an incubating feature")
     }
 
     def "follows java project dependencies"() {
@@ -72,19 +84,26 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run(":api:build")
 
         then:
-        result.assertProjectsEvaluated(":", ":api")
+        fixture.assertProjectsConfigured(":", ":api")
+
+        when:
+        inDirectory("impl")
+        run(":api:build")
+
+        then:
+        fixture.assertProjectsConfigured(":", ":api")
 
         when:
         run(":impl:build")
 
         then:
-        result.assertProjectsEvaluated(":", ":impl", ":api")
+        fixture.assertProjectsConfigured(":", ":impl", ":api")
 
         when:
         run(":util:build")
 
         then:
-        result.assertProjectsEvaluated(":", ":util", ":impl", ":api")
+        fixture.assertProjectsConfigured(":", ":util", ":impl", ":api")
     }
 
     def "follows project dependencies when ran in subproject"() {
@@ -102,7 +121,7 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run("build")
 
         then:
-        result.assertProjectsEvaluated(':', ':impl', ':api')
+        fixture.assertProjectsConfigured(':', ':impl', ':api')
     }
 
     def "name matching execution from root evaluates all projects"() {
@@ -113,13 +132,13 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run("foo")
 
         then:
-        result.assertProjectsEvaluated(":", ":api", ":impl")
+        fixture.assertProjectsConfigured(":", ":api", ":impl")
 
         when:
         run(":foo")
 
         then:
-        result.assertProjectsEvaluated(":")
+        fixture.assertProjectsConfigured(":")
     }
 
     def "name matching execution from subproject evaluates only the subproject recursively"() {
@@ -131,7 +150,7 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run("foo")
 
         then:
-        result.assertProjectsEvaluated(":", ":impl", ":impl:one", ":impl:two", ":impl:two:abc")
+        fixture.assertProjectsConfigured(":", ":impl", ":impl:one", ":impl:two", ":impl:two:abc")
     }
 
     def "may run implicit tasks from root"() {
@@ -141,7 +160,7 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run(":tasks")
 
         then:
-        result.assertProjectsEvaluated(":")
+        fixture.assertProjectsConfigured(":")
     }
 
     def "may run implicit tasks for subproject"() {
@@ -151,10 +170,10 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run(":api:tasks")
 
         then:
-        result.assertProjectsEvaluated(":", ":api")
+        fixture.assertProjectsConfigured(":", ":api")
     }
 
-    def "works with default tasks"() {
+    def "respects default tasks"() {
         settingsFile << "include 'api', 'impl'"
         file("api/build.gradle") << """
             task foo
@@ -166,7 +185,81 @@ class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
         run()
 
         then:
-        result.assertProjectsEvaluated(":", ":api")
+        fixture.assertProjectsConfigured(":", ":api")
         result.assertTasksExecuted(':api:foo')
     }
+
+    def "respects evaluationDependsOn"() {
+        settingsFile << "include 'api', 'impl', 'other'"
+        file("api/build.gradle") << """
+            evaluationDependsOn(":impl")
+        """
+
+        when:
+        run("api:tasks")
+
+        then:
+        fixture.assertProjectsConfigured(":", ":impl", ":api")
+    }
+
+    def "respects buildProjectDependencies setting"() {
+        settingsFile << "include 'api', 'impl', 'other'"
+        file("impl/build.gradle") << """
+            apply plugin: 'java'
+            dependencies { compile project(":api") }
+        """
+        file("api/build.gradle") << "apply plugin: 'java'"
+
+        when:
+        run("impl:build")
+
+        then:
+        fixture.assertProjectsConfigured(":", ":impl", ":api")
+
+        when:
+        run("impl:build", "--no-rebuild") // impl -> api
+
+        then:
+        //api tasks are not executed
+        !result.executedTasks.find { it.startsWith ":api" }
+        //but the api project is still configured
+        //ideally, the ':api' project is not configured in the configure on demand mode
+        //but this is complicated to implement so lets leave it for now
+        fixture.assertProjectsConfigured(":", ":impl", ":api")
+    }
+
+    def "respects external task dependencies"() {
+        settingsFile << "include 'api', 'impl', 'other'"
+        file("build.gradle") << "allprojects { task foo }"
+        file("impl/build.gradle") << """
+            task bar(dependsOn: ":api:foo")
+        """
+
+        when:
+        run("impl:bar")
+
+        then:
+        fixture.assertProjectsConfigured(":", ":impl", ":api")
+        result.assertTasksExecuted(":api:foo", ":impl:bar")
+    }
+
+    def "supports buildSrc"() {
+        file("buildSrc/src/main/java/FooTask.java") << """
+            import org.gradle.api.DefaultTask;
+            import org.gradle.api.tasks.TaskAction;
+
+            public class FooTask extends DefaultTask {
+                @TaskAction public void logStuff(){
+                    System.out.println(String.format("Horray!!! '%s' executed.", getName()));
+                }
+            }
+        """
+
+        buildFile << "task foo(type: FooTask)"
+
+        when:
+        run("foo")
+        then:
+        output.contains "Horray!!!"
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigurationIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigurationIntegrationTest.groovy
new file mode 100644
index 0000000..88b916a
--- /dev/null
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ProjectConfigurationIntegrationTest.groovy
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
+/**
+ * by Szczepan Faber, created at: 11/21/12
+ */
+class ProjectConfigurationIntegrationTest extends AbstractIntegrationSpec {
+
+    def "accessing the task by path from containing project is safe"() {
+        buildFile << """
+            task foobar
+            println "the name: " + tasks.getByPath(":foobar").name
+        """
+
+        when:
+        run()
+
+        then:
+        output.contains "the name: foobar"
+    }
+}
\ 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 e588286..6aa4069 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
@@ -19,6 +19,8 @@
 
 package org.gradle.api.tasks
 
+import org.apache.commons.lang.RandomStringUtils
+import org.apache.commons.lang.StringUtils
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.test.fixtures.file.TestFile
@@ -220,7 +222,7 @@ public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
         file('dest').assertHasDescendants('someDir/1.txt')
     }
 
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(temporaryFolder)
 
     def "tarTreeFailsGracefully"() {
         given:
@@ -296,6 +298,79 @@ public class ArchiveIntegrationTest extends AbstractIntegrationSpec {
         expandDir.file('prefix/dir1/renamed_file1.txt').assertContents(equalTo('[abc]'))
     }
 
+    def canCreateAZipArchiveWithContentsUncompressed() {
+        def randomAscii = RandomStringUtils.randomAscii(300)
+        given:
+        createDir('test') {
+            dir1 {
+                file('file1.txt').write(randomAscii)
+            }
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+                file 'script.sh'
+            }
+        }
+        and:
+        buildFile << '''
+            task uncompressedZip(type: Zip) {
+                into('prefix') {
+                    from 'test'
+                    include '**/*.txt'
+                }
+                into('scripts') {
+                    from 'test'
+                    include '**/*.sh'
+                }
+                destinationDir = buildDir
+                archiveName = 'uncompressedTest.zip'
+                entryCompression = ZipEntryCompression.STORED
+            }
+
+            task compressedZip(type: Zip) {
+                into('prefix') {
+                    from 'test'
+                    include '**/*.txt'
+                }
+                into('scripts') {
+                    from 'test'
+                    include '**/*.sh'
+                }
+                destinationDir = buildDir
+                archiveName = 'compressedTest.zip'
+            }
+        '''
+        when:
+        run 'uncompressedZip'
+        run 'compressedZip'
+        then:
+	def uncompressedSize = file('build/uncompressedTest.zip').length()
+	def compressedSize = file('build/compressedTest.zip').length()
+	println "uncompressed" + uncompressedSize
+	println "compressed" + compressedSize
+    assert compressedSize < uncompressedSize
+
+        def expandDir = file('expandedUncompressed')
+        file('build/uncompressedTest.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'prefix/dir1/file1.txt',
+                'prefix/file1.txt',
+                'prefix/dir2/file2.txt',
+                'scripts/dir2/script.sh')
+
+        expandDir.file('prefix/dir1/file1.txt').assertContents(equalTo(randomAscii))
+
+        def expandCompressedDir = file('expandedCompressed')
+        file('build/compressedTest.zip').unzipTo(expandCompressedDir)
+        expandCompressedDir.assertHasDescendants(
+                'prefix/dir1/file1.txt',
+                'prefix/file1.txt',
+                'prefix/dir2/file2.txt',
+                'scripts/dir2/script.sh')
+
+        expandCompressedDir.file('prefix/dir1/file1.txt').assertContents(equalTo(randomAscii))
+    }
+
     def canCreateATarArchive() {
         given:
         createDir('test') {
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
index 07bfea3..2c420e1 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationTest.groovy
@@ -27,7 +27,7 @@ import static org.junit.Assert.assertThat
 
 public class CopyTaskIntegrationTest extends AbstractIntegrationTest {
     @Rule
-    public final TestResources resources = new TestResources("copyTestResources")
+    public final TestResources resources = new TestResources(testDirectoryProvider, "copyTestResources")
 
     @Test
     public void testSingleSourceWithIncludeAndExclude() {
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy
index d4a094f..3a5ad01 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/FileTreeCopyIntegrationTest.groovy
@@ -23,7 +23,7 @@ import org.junit.Test
 
 public class FileTreeCopyIntegrationTest extends AbstractIntegrationTest {
     @Rule
-    public final TestResources resources = new TestResources("copyTestResources")
+    public final TestResources resources = new TestResources(testDirectoryProvider, "copyTestResources")
 
     @Test public void testCopyWithClosure() {
         TestFile buildFile = testFile("build.gradle").writelns(
diff --git a/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy
index 27c2445..0ef3d46 100644
--- a/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy
+++ b/subprojects/core/src/integTest/groovy/org/gradle/groovy/scripts/StatementLabelsIntegrationTest.groovy
@@ -83,6 +83,5 @@ class Foo {
 
         expect:
         succeeds("tasks")
-        executer.assertOutputHasNoDeprecationWarnings(result)
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
index a95bc52..ab9741d 100644
--- a/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/StartParameter.java
@@ -73,7 +73,8 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     private boolean refreshDependencies;
     private boolean recompileScripts;
     private int parallelThreadCount;
-
+    private boolean configureOnDemand;
+    private boolean parallelThreadCountConfigured;
 
     /**
      * Sets the project's cache location. Set to null to use the default location.
@@ -115,35 +116,25 @@ public class StartParameter extends LoggingConfiguration implements Serializable
      * @return the new parameters.
      */
     public StartParameter newInstance() {
-        StartParameter startParameter = new StartParameter();
-        startParameter.buildFile = buildFile;
-        startParameter.projectDir = projectDir;
-        startParameter.settingsFile = settingsFile;
-        startParameter.useEmptySettings = useEmptySettings;
-        startParameter.taskNames = taskNames;
-        startParameter.buildProjectDependencies = buildProjectDependencies;
-        startParameter.currentDir = currentDir;
-        startParameter.searchUpwards = searchUpwards;
-        startParameter.projectProperties = projectProperties;
-        startParameter.systemPropertiesArgs = systemPropertiesArgs;
-        startParameter.gradleUserHomeDir = gradleUserHomeDir;
-        startParameter.gradleHomeDir = gradleHomeDir;
-        startParameter.cacheUsage = cacheUsage;
-        startParameter.initScripts = new ArrayList<File>(initScripts);
-        startParameter.setLogLevel(getLogLevel());
-        startParameter.setColorOutput(isColorOutput());
-        startParameter.setShowStacktrace(getShowStacktrace());
-        startParameter.dryRun = dryRun;
-        startParameter.rerunTasks = rerunTasks;
-        startParameter.recompileScripts = recompileScripts;
-        startParameter.profile = profile;
-        startParameter.projectCacheDir = projectCacheDir;
-        startParameter.continueOnFailure = continueOnFailure;
-        startParameter.offline = offline;
-        startParameter.refreshDependencies = refreshDependencies;
-        startParameter.parallelThreadCount = parallelThreadCount;
-
-        return startParameter;
+        StartParameter p = newBuild();
+
+        p.buildFile = buildFile;
+        p.projectDir = projectDir;
+        p.settingsFile = settingsFile;
+        p.useEmptySettings = useEmptySettings;
+        p.taskNames = new ArrayList<String>(taskNames);
+        p.excludedTaskNames = new HashSet<String>(excludedTaskNames);
+        p.buildProjectDependencies = buildProjectDependencies;
+        p.currentDir = currentDir;
+        p.searchUpwards = searchUpwards;
+        p.projectProperties = new HashMap<String, String>(projectProperties);
+        p.systemPropertiesArgs = new HashMap<String, String>(systemPropertiesArgs);
+        p.gradleHomeDir = gradleHomeDir;
+        p.initScripts = new ArrayList<File>(initScripts);
+        p.dryRun = dryRun;
+        p.projectCacheDir = projectCacheDir;
+
+        return p;
     }
 
     /**
@@ -153,20 +144,23 @@ public class StartParameter extends LoggingConfiguration implements Serializable
      * @return The new parameters.
      */
     public StartParameter newBuild() {
-        StartParameter startParameter = new StartParameter();
-        startParameter.gradleUserHomeDir = gradleUserHomeDir;
-        startParameter.cacheUsage = cacheUsage;
-        startParameter.setLogLevel(getLogLevel());
-        startParameter.setColorOutput(isColorOutput());
-        startParameter.setShowStacktrace(getShowStacktrace());
-        startParameter.profile = profile;
-        startParameter.continueOnFailure = continueOnFailure;
-        startParameter.offline = offline;
-        startParameter.rerunTasks = rerunTasks;
-        startParameter.recompileScripts = recompileScripts;
-        startParameter.refreshDependencies = refreshDependencies;
-        startParameter.parallelThreadCount = parallelThreadCount;
-        return startParameter;
+        StartParameter p = new StartParameter();
+
+        p.gradleUserHomeDir = gradleUserHomeDir;
+        p.cacheUsage = cacheUsage;
+        p.setLogLevel(getLogLevel());
+        p.setColorOutput(isColorOutput());
+        p.setShowStacktrace(getShowStacktrace());
+        p.profile = profile;
+        p.continueOnFailure = continueOnFailure;
+        p.offline = offline;
+        p.rerunTasks = rerunTasks;
+        p.recompileScripts = recompileScripts;
+        p.refreshDependencies = refreshDependencies;
+        p.parallelThreadCount = parallelThreadCount;
+        p.configureOnDemand = configureOnDemand;
+
+        return p;
     }
 
     public boolean equals(Object obj) {
@@ -285,13 +279,13 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     /**
      * Sets the directory to use to select the default project, and to search for the settings file. Set to null to use the default current directory.
      *
-     * @param currentDir The directory. Should not be null.
+     * @param currentDir The directory. Set to null to use the default.
      */
     public void setCurrentDir(File currentDir) {
         if (currentDir != null) {
             this.currentDir = GFileUtils.canonicalise(currentDir);
         } else {
-            this.currentDir = GFileUtils.canonicalise(new File(System.getProperty("user.dir")));
+            this.currentDir = GFileUtils.canonicalise(SystemProperties.getCurrentDir());
         }
     }
 
@@ -320,7 +314,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     }
 
     /**
-     * Returns a newly constructed map that is the JVM system properties merged with the system property args. <p> System property args take precedency overy JVM system properties.
+     * Returns a newly constructed map that is the JVM system properties merged with the system property args. <p> System property args take precedence over JVM system properties.
      *
      * @return The merged system properties
      */
@@ -632,6 +626,7 @@ public class StartParameter extends LoggingConfiguration implements Serializable
      * @see #getParallelThreadCount()
      */
     public void setParallelThreadCount(int parallelThreadCount) {
+        this.parallelThreadCountConfigured = true;
         this.parallelThreadCount = parallelThreadCount;
     }
 
@@ -640,8 +635,15 @@ public class StartParameter extends LoggingConfiguration implements Serializable
      */
     @Incubating
     public boolean isConfigureOnDemand() {
-        //TODO SF this needs to be handled same way as other properties, like 'org.gradle.daemon'
-        return "true".equals(System.getProperty("org.gradle.configuration.ondemand"));
+        return configureOnDemand;
+    }
+
+    @Incubating
+    public boolean isParallelThreadCountConfigured() {
+        //This is not beautiful. As the number of gradle properties grows we may something like:
+        //1. Make StartParameter an interface
+        //2. StartParameter (StartParameterInternal) needs to inform if certain property was configured or not
+        return parallelThreadCountConfigured;
     }
 
     @Override
@@ -665,6 +667,8 @@ public class StartParameter extends LoggingConfiguration implements Serializable
                 + ", recompileScripts=" + recompileScripts
                 + ", offline=" + offline
                 + ", refreshDependencies=" + refreshDependencies
+                + ", parallelThreadCount=" + parallelThreadCount
+                + ", configureOnDemand=" + configureOnDemand
                 + '}';
     }
 
@@ -674,4 +678,9 @@ public class StartParameter extends LoggingConfiguration implements Serializable
     void setGradleHomeDir(File gradleHomeDir) {
         this.gradleHomeDir = gradleHomeDir;
     }
+
+    @Incubating
+    public void setConfigureOnDemand(boolean configureOnDemand) {
+        this.configureOnDemand = configureOnDemand;
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java
new file mode 100644
index 0000000..5df7fe2
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/ExtensiblePolymorphicDomainObjectContainer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api;
+
+/**
+ * A {@link org.gradle.api.PolymorphicDomainObjectContainer} that can be extended at runtime to
+ * create elements of new types.
+ *
+ * @param <T> the (base) container element type
+ */
+ at Incubating
+public interface ExtensiblePolymorphicDomainObjectContainer<T> extends PolymorphicDomainObjectContainer<T> {
+    /**
+     * Registers a factory for creating elements of the specified type. Typically, the specified type
+     * is an interface type.
+     *
+     * @param type the type of objects created by the factory
+     * @param factory the factory to register
+     * @param <U> the type of objects created by the factory
+     *
+     * @throws IllegalArgumentException if the specified type is not a subtype of the container element type
+     */
+    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
index 5c737f3..fa836d8 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
@@ -42,6 +42,15 @@ public interface NamedDomainObjectContainer<T> extends NamedDomainObjectSet<T>,
     T create(String name) throws InvalidUserDataException;
 
     /**
+     * Looks for an item with the given name, creating and adding it to this container if it does not exist.
+     *
+     * @param name The name to find or assign to the created object
+     * @return The found or created object. Never null.
+     */
+    @Incubating
+    T maybeCreate(String name);
+
+    /**
      * Creates a new item with the given name, adding it to this container, then configuring it with the given closure.
      *
      * @param name The name to assign to the created object
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/PolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/PolymorphicDomainObjectContainer.java
new file mode 100644
index 0000000..cf47a0e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/PolymorphicDomainObjectContainer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api;
+
+/**
+ * A {@link NamedDomainObjectContainer} that allows to create domain objects with different types.
+ *
+ * @param <T> the (base) type of domain objects in the container
+ */
+ at Incubating
+public interface PolymorphicDomainObjectContainer<T> extends NamedDomainObjectContainer<T> {
+    /**
+     * Creates a domain object with the specified name and type, and adds it to the container.
+     *
+     * @param name the name of the domain object to be created
+     *
+     * @param type the type of the domain object to be created
+     *
+     * @param <U> the type of the domain object to be created
+     *
+     * @return the created domain object
+     *
+     * @throws InvalidUserDataException if a domain object with the specified name already exists
+     * or the container does not support creating a domain object with the specified type
+     */
+    <U extends T> U create(String name, Class<U> type) throws InvalidUserDataException;
+
+    /**
+     * Creates a domain object with the specified name and type, adds it to the container, and configures
+     * it with the specified action.
+     *
+     * @param name the name of the domain object to be created
+     *
+     * @param type the type of the domain object to be created
+     *
+     * @param configuration an action for configuring the domain object
+     *
+     * @param <U> the type of the domain object to be created
+     *
+     * @return the created domain object
+     *
+     * @throws InvalidUserDataException if a domain object with the specified name already exists
+     * or the container does not support creating a domain object with the specified type
+     */
+    <U extends T> U create(String name, Class<U> type, Action<? super U> configuration) throws InvalidUserDataException;
+}
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 f54f9ae..8d97a1e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/Project.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Project.java
@@ -247,7 +247,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * generated into. The path parameter is evaluated as described for {@link #file(Object)}. This mean you can use,
      * amongst other things, a relative or absolute path or File object to specify the build directory.</p>
      *
-     * @param path The build directory. This is evaluated as for {@link #file(Object)}
+     * @param path The build directory. This is evaluated as per {@link #file(Object)}
      */
     void setBuildDir(Object path);
 
@@ -700,10 +700,10 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * <p>Returns a {@link ConfigurableFileCollection} containing the given files. You can pass any of the following
      * types to this method:</p>
      *
-     * <ul> <li>A {@link CharSequence}, including {@link String} or {@link groovy.lang.GString}. Interpreted relative to the project directory, as for {@link #file(Object)}. A string
+     * <ul> <li>A {@link CharSequence}, including {@link String} or {@link groovy.lang.GString}. Interpreted relative to the project directory, as per {@link #file(Object)}. A string
      * that starts with {@code file:} is treated as a file URL.</li>
      *
-     * <li>A {@link File}. Interpreted relative to the project directory, as for {@link #file(Object)}.</li>
+     * <li>A {@link File}. Interpreted relative to the project directory, as per {@link #file(Object)}.</li>
      *
      * <li>A {@link java.net.URI} or {@link java.net.URL}. The URL's path is interpreted as a file path. Currently, only
      * {@code file:} URLs are supported.
@@ -725,7 +725,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      *
      * <li>A {@link org.gradle.api.tasks.TaskOutputs}. Converted to the output files the related task.</li>
      *
-     * <li>An Object. Its {@code toString()} value is treated the same way as a String, as for {@link #file(Object)}.
+     * <li>An Object. Its {@code toString()} value is treated the same way as a String, as per {@link #file(Object)}.
      * This has been deprecated and will be removed in the next version of Gradle.</li>
      *
      * </ul>
@@ -742,7 +742,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
     ConfigurableFileCollection files(Object... paths);
 
     /**
-     * <p>Creates a new {@code ConfigurableFileCollection} using the given paths. The paths are evaluated as for {@link
+     * <p>Creates a new {@code ConfigurableFileCollection} using the given paths. The paths are evaluated as per {@link
      * #files(Object...)}. The file collection is configured using the given closure. The file collection is passed to
      * the closure as its delegate. Example:</p>
      * <pre>
@@ -754,7 +754,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * collection are queried. The file collection is also live, so that it evaluates the above each time the contents
      * of the collection is queried.</p>
      *
-     * @param paths The contents of the file collection. Evaluated as for {@link #files(Object...)}.
+     * @param paths The contents of the file collection. Evaluated as per {@link #files(Object...)}.
      * @param configureClosure The closure to use to configure the file collection.
      * @return the configured file tree. Never returns null.
      */
@@ -762,7 +762,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
 
     /**
      * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
-     * as for {@link #file(Object)}.</p>
+     * as per {@link #file(Object)}.</p>
      *
      * <p><b>Note:</b> to use a closure as the baseDir, you must explicitly cast the closure to {@code Object} to force
      * the use of this method instead of {@link #fileTree(Closure)}. Example:</p>
@@ -775,14 +775,14 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
-     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @param baseDir The base directory of the file tree. Evaluated as per {@link #file(Object)}.
      * @return the file tree. Never returns null.
      */
     ConfigurableFileTree fileTree(Object baseDir);
 
     /**
      * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
-     * as for {@link #file(Object)}. The closure will be used to configure the new file tree.
+     * as per {@link #file(Object)}. The closure will be used to configure the new file tree.
      * The file tree is passed to the closure as its delegate.  Example:</p>
      *
      * <pre>
@@ -795,7 +795,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
-     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @param baseDir The base directory of the file tree. Evaluated as per {@link #file(Object)}.
      * @param configureClosure Closure to configure the {@code ConfigurableFileTree} object.
      * @return the configured file tree. Never returns null.
      */
@@ -842,14 +842,14 @@ public interface Project extends Comparable<Project>, ExtensionAware {
 
     /**
      * <p>Creates a new {@code FileTree} which contains the contents of the given ZIP file. The given zipPath path is
-     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * evaluated as per {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
      * method to unzip a ZIP file.</p>
      *
      * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
-     * @param zipPath The ZIP file. Evaluated as for {@link #file(Object)}.
+     * @param zipPath The ZIP file. Evaluated as per {@link #file(Object)}.
      * @return the file tree. Never returns null.
      */
     FileTree zipTree(Object zipPath);
@@ -859,7 +859,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
      * Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path can be:
      * <ul>
      *   <li>an instance of {@link org.gradle.api.resources.Resource}</li>
-     *   <li>any other object is evaluated as for {@link #file(Object)}</li>
+     *   <li>any other object is evaluated as per {@link #file(Object)}</li>
      * </ul>
      *
      * The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
@@ -895,7 +895,7 @@ public interface Project extends Comparable<Project>, ExtensionAware {
     /**
      * Creates a directory and returns a file pointing to it.
      *
-     * @param path The path for the directory to be created. Evaluated as for {@link #file(Object)}.
+     * @param path The path for the directory to be created. Evaluated as per {@link #file(Object)}.
      * @return the created directory
      * @throws org.gradle.api.InvalidUserDataException If the path points to an existing file.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/Script.java b/subprojects/core/src/main/groovy/org/gradle/api/Script.java
index c03b30e..abe3980 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/Script.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/Script.java
@@ -131,7 +131,7 @@ public interface Script {
      * using the given closure. This method works as described for {@link Project#files(Object, groovy.lang.Closure)}.
      * Relative paths are resolved relative to the directory containing this script.</p>
      *
-     * @param paths The contents of the file collection. Evaluated as for {@link #files(Object...)}.
+     * @param paths The contents of the file collection. Evaluated as per {@link #files(Object...)}.
      * @param configureClosure The closure to use to configure the file collection.
      * @return the configured file tree. Never returns null.
      */
@@ -148,7 +148,7 @@ public interface Script {
 
     /**
      * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
-     * as for {@link #file(Object)}.</p>
+     * as per {@link #file(Object)}.</p>
      * 
      * <p><b>Note:</b> to use a closure as the baseDir, you must explicitly cast the closure to {@code Object} to force
      * the use of this method instead of {@link #fileTree(Closure)}. Example:</p>
@@ -161,7 +161,7 @@ public interface Script {
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
-     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @param baseDir The base directory of the file tree. Evaluated as per {@link #file(Object)}.
      * @return the file tree. Never returns null.
      */
     ConfigurableFileTree fileTree(Object baseDir);
@@ -202,7 +202,7 @@ public interface Script {
 
     /**
      * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
-     * as for {@link #file(Object)}. The closure will be used to configure the new file tree. 
+     * as per {@link #file(Object)}. The closure will be used to configure the new file tree.
      * The file tree is passed to the closure as its delegate.  Example:</p>
      *
      * <pre>
@@ -215,7 +215,7 @@ public interface Script {
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
-     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @param baseDir The base directory of the file tree. Evaluated as per {@link #file(Object)}.
      * @param configureClosure Closure to configure the {@code ConfigurableFileTree} object.
      * @return the configured file tree. Never returns null.
      */
@@ -223,14 +223,14 @@ public interface Script {
 
     /**
      * <p>Creates a new {@code FileTree} which contains the contents of the given ZIP file. The given zipPath path is
-     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * evaluated as per {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
      * method to unzip a ZIP file.</p>
      *
      * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
      * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
      * queried.</p>
      *
-     * @param zipPath The ZIP file. Evaluated as for {@link #file(Object)}.
+     * @param zipPath The ZIP file. Evaluated as per {@link #file(Object)}.
      * @return the file tree. Never returns null.
      */
     FileTree zipTree(Object zipPath);
@@ -239,7 +239,7 @@ public interface Script {
      * Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path can be:
      * <ul>
      *   <li>an instance of {@link org.gradle.api.resources.Resource}</li>
-     *   <li>any other object is evaluated as for {@link #file(Object)}</li>
+     *   <li>any other object is evaluated as per {@link #file(Object)}</li>
      * </ul>
      *
      * The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
@@ -314,7 +314,7 @@ public interface Script {
     /**
      * Creates a directory and returns a file pointing to it.
      *
-     * @param path The path for the directory to be created. Evaluated as for {@link #file(Object)}.
+     * @param path The path for the directory to be created. Evaluated as per {@link #file(Object)}.
      * @return the created directory
      * @throws org.gradle.api.InvalidUserDataException If the path points to an existing file.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java
index 6296979..14cbc80 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ArtifactRepositoryContainer.java
@@ -225,6 +225,8 @@ public interface ArtifactRepositoryContainer extends NamedDomainObjectList<Artif
      * Returns the resolvers in this container, in sequence.
      *
      * @return The resolvers in sequence. Returns an empty list if this container is empty.
+     * @deprecated No replacement.
      */
+    @Deprecated
     List<DependencyResolver> getResolvers();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurablePublishArtifact.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurablePublishArtifact.java
index 60bdd16..51d4650 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurablePublishArtifact.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/ConfigurablePublishArtifact.java
@@ -50,7 +50,7 @@ public interface ConfigurablePublishArtifact extends PublishArtifact {
     /**
      * Registers some tasks which build this artifact.
      *
-     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @param tasks The tasks. These are evaluated as per {@link org.gradle.api.Task#dependsOn(Object...)}.
      * @return this
      */
     ConfigurablePublishArtifact builtBy(Object... tasks);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolveDetails.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolveDetails.java
index 6984c52..dea71e1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolveDetails.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/DependencyResolveDetails.java
@@ -40,14 +40,32 @@ public interface DependencyResolveDetails {
      * Forcing modules via {@link ResolutionStrategy#force(Object...)} uses this capability.
      * Configuring a version different than requested will cause {@link #getTarget()} method
      * return a target module with updated target version.
+     * <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.
-     *
-     * @param version to use when resolving this dependency, cannot be null
      */
     void useVersion(String version);
 
     /**
+     * Allows to override the details of the dependency (see {@link #getTarget()})
+     * when it is resolved (see {@link #getRequested()}).
+     * This method can be used to change the dependency before it is resolved,
+     * e.g. change group, name or version (or all three of them).
+     * In many cases users are interested in changing the version.
+     * For such scenario you can use the {@link #useVersion(String)} method.
+     *
+     * @param notation the notation that gets parsed into an instance of {@link ModuleVersionSelector}.
+     * You can pass Strings like 'org.gradle:gradle-core:1.4',
+     * Maps like [group: 'org.gradle', name: 'gradle-core', version: '1.4'],
+     * or instances of ModuleVersionSelector.
+     *
+     * @since 1.5
+     */
+    void useTarget(Object notation);
+
+    /**
      * The target module selector used to resolve the dependency.
      * Never returns null. Target module is updated when methods like {@link #useVersion(String)} are used.
      */
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 b1f0c51..5edf7f7 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
@@ -48,6 +48,10 @@ import java.util.concurrent.TimeUnit;
  *       if (details.requested.group == 'org.gradle') {
  *           details.useVersion'1.4'
  *       }
+ *       //changing 'groovy-all' into 'groovy':
+ *       if (details.requested.name == 'groovy-all') {
+ *          details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
+ *       }
  *     }
  *
  *     // cache dynamic versions for 10 minutes
@@ -100,11 +104,11 @@ public interface ResolutionStrategy {
      * }
      * </pre>
      *
-     * @param forcedModuleNotations typically group:name:version notations to append
+     * @param moduleVersionSelectorNotations typically group:name:version notations to append
      * @return this ResolutionStrategy instance
      * @since 1.0-milestone-7
      */
-    ResolutionStrategy force(Object... forcedModuleNotations);
+    ResolutionStrategy force(Object... moduleVersionSelectorNotations);
 
     /**
      * Allows forcing certain versions of dependencies, including transitive dependencies.
@@ -121,11 +125,11 @@ public interface ResolutionStrategy {
      * }
      * </pre>
      *
-     * @param forcedModuleNotations typically group:name:version notations to set
+     * @param moduleVersionSelectorNotations typically group:name:version notations to set
      * @return this ResolutionStrategy instance
      * @since 1.0-milestone-7
      */
-    ResolutionStrategy setForcedModules(Object... forcedModuleNotations);
+    ResolutionStrategy setForcedModules(Object... moduleVersionSelectorNotations);
 
     /**
      * Returns currently configured forced modules. For more information on forcing versions see {@link #force(Object...)}
@@ -151,8 +155,12 @@ public interface ResolutionStrategy {
      *         details.useVersion '1.4'
      *       }
      *     }
-     *     eachDependency {
+     *     eachDependency { details ->
      *       //multiple actions can be specified
+     *       if (details.requested.name == 'groovy-all') {
+     *          //changing the name:
+     *          details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
+     *       }
      *     }
      *   }
      * }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
index a858122..1c0c851 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
@@ -48,7 +48,7 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      * The default is a Hash value of the rootdir paths. The name is used in the console output,
      * to point to information related to a particular repository. A name must be unique amongst a repository group.</td></tr>
      * <tr><td><code>dirs</code></td>
-     *     <td>Specifies a list of rootDirs where to look for dependencies. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}</td></tr>
+     *     <td>Specifies a list of rootDirs where to look for dependencies. These are evaluated as per {@link org.gradle.api.Project#files(Object...)}</td></tr>
      * </table>
      *
      * <p>Examples:
@@ -85,7 +85,7 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
 
     /**
      * Adds a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is
-     * always {@link org.gradle.api.artifacts.ArtifactRepositoryContainer#MAVEN_CENTRAL_URL}. The behavior of this resolver
+     * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#MAVEN_CENTRAL_URL}. The behavior of this resolver
      * is otherwise the same as the ones added by {@link #mavenRepo(java.util.Map)}.
      *
      * The following parameter are accepted as keys for the map:
@@ -101,7 +101,7 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      * <tr><td><code>artifactUrls</code></td>
      *     <td>A single jar repository or a collection of jar repositories containing additional artifacts not found in the maven central repository.
      * But be aware that the POM must exist in maven central.
-     * The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}.</td></tr>
+     * The provided values are evaluated as per {@link org.gradle.api.Project#uri(Object)}.</td></tr>
      * </table>
      *
      * <p>Examples:
@@ -175,12 +175,12 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer {
      * </td></tr>
      * <tr><td><code>url</code></td>
      *     <td>The root repository where POM files and artifacts are located.
-     * The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}.</td></tr>
+     * The provided values are evaluated as per {@link org.gradle.api.Project#uri(Object)}.</td></tr>
      * <tr><td><code>artifactUrls</code></td>
      *     <td>A single jar repository or a collection of jar repositories containing additional artifacts not found in the root repository. Sometimes the artifact
      * lives in a different repository than the POM. In such a case you can specify further locations to look for an artifact.
      * But be aware that the POM is only looked up in the root repository.
-     * The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}.</td></tr>
+     * The provided values are evaluated as per {@link org.gradle.api.Project#uri(Object)}.</td></tr>
      * </table>
      *
      * <p>Examples:
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/FlatDirectoryArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/FlatDirectoryArtifactRepository.java
index dc2b0df..d32bfac 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/FlatDirectoryArtifactRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/FlatDirectoryArtifactRepository.java
@@ -47,7 +47,7 @@ public interface FlatDirectoryArtifactRepository extends ArtifactRepository {
     /**
      * Adds a directory where this repository will look for artifacts.
      *
-     * <p>The provided value are evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * <p>The provided value are evaluated as per {@link org.gradle.api.Project#file(Object)}.
      *
      * @param dir the directory
      */
@@ -56,7 +56,7 @@ public interface FlatDirectoryArtifactRepository extends ArtifactRepository {
     /**
      * Adds some directories where this repository will look for artifacts.
      *
-     * <p>The provided values are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * <p>The provided values are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param dirs the directories.
      */
@@ -65,7 +65,7 @@ public interface FlatDirectoryArtifactRepository extends ArtifactRepository {
     /**
      * Sets the directories where this repository will look for artifacts.
      *
-     * <p>The provided values are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * <p>The provided values are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param dirs the directories.
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepository.java
index f5f4d3a..9eb30de 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepository.java
@@ -16,11 +16,12 @@
 package org.gradle.api.artifacts.repositories;
 
 import groovy.lang.Closure;
+import org.gradle.api.Incubating;
 
 import java.net.URI;
 
 /**
- * <p>An artifact repository which uses an Ivy format to store artifacts and meta-data</p>
+ * <p>An artifact repository which uses an Ivy format to store artifacts and meta-data.</p>
  *
  * <p>When used to resolve metadata and artifact files, all available patterns will be searched.</p>
  *
@@ -47,7 +48,7 @@ public interface IvyArtifactRepository extends ArtifactRepository, Authenticatio
     URI getUrl();
 
     /**
-     * Sets the base URL of this repository. The provided value is evaluated as for {@link org.gradle.api.Project#uri(Object)}. This means,
+     * Sets the base URL of this repository. The provided value is evaluated as per {@link org.gradle.api.Project#uri(Object)}. This means,
      * for example, you can pass in a File object or a relative path which is evaluated relative to the project directory.
      *
      * File are resolved based on the supplied URL and the configured {@link #layout(String, Closure)} for this repository.
@@ -125,4 +126,12 @@ public interface IvyArtifactRepository extends ArtifactRepository, Authenticatio
      */
     void layout(String layoutName, Closure config);
 
+    /**
+     * Returns the meta-data provider used when resolving artifacts from this repository. The provider is responsible for locating and interpreting the meta-data
+     * for the modules and artifacts contained in this repository. Using this provider, you can fine tune how this resolution happens.
+     *
+     * @return The meta-data provider for this repository.
+     */
+    @Incubating
+    IvyArtifactRepositoryMetaDataProvider getResolve();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepositoryMetaDataProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepositoryMetaDataProvider.java
new file mode 100644
index 0000000..1587e18
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/IvyArtifactRepositoryMetaDataProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.artifacts.repositories;
+
+import org.gradle.api.Incubating;
+
+/**
+ * The meta-data provider for an Ivy repository. Uses the Ivy module descriptor ({@code ivy.xml}) to determine the meta-data for module versions and artifacts.
+ */
+ at Incubating
+public interface IvyArtifactRepositoryMetaDataProvider {
+    /**
+     * Returns true if dynamic resolve mode should be used for Ivy modules. When enabled, the {@code revConstraint} attribute for each dependency declaration
+     * is used in preference to the {@code rev} attribute. When disabled (the default), the {@code rev} attribute is always used.
+     */
+    boolean isDynamicMode();
+
+    /**
+     * Specifies whether dynamic resolve mode should be used for Ivy modules. When enabled, the {@code revConstraint} attribute for each dependency declaration
+     * is used in preference to the {@code rev} attribute. When disabled (the default), the {@code rev} attribute is always used.
+     */
+    void setDynamicMode(boolean mode);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/MavenArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/MavenArtifactRepository.java
index bdff5e6..f6da525 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/MavenArtifactRepository.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/artifacts/repositories/MavenArtifactRepository.java
@@ -35,7 +35,7 @@ public interface MavenArtifactRepository extends ArtifactRepository, Authenticat
      * Sets the base URL of this repository. This URL is used to find both POMs and artifact files. You can add additional URLs to use to look for artifact files, such as jars, using {@link
      * #setArtifactUrls(Iterable)}.
      *
-     * <p>The provided value is evaluated as for {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated relative
+     * <p>The provided value is evaluated as per {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated relative
      * to the project directory.
      *
      * @param url The base URL.
@@ -52,7 +52,7 @@ public interface MavenArtifactRepository extends ArtifactRepository, Authenticat
     /**
      * Adds some additional URLs to use to find artifact files. Note that these URLs are not used to find POM files.
      *
-     * <p>The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated
+     * <p>The provided values are evaluated as per {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated
      * relative to the project directory.
      *
      * @param urls The URLs to add.
@@ -62,7 +62,7 @@ public interface MavenArtifactRepository extends ArtifactRepository, Authenticat
     /**
      * Sets the additional URLs to use to find artifact files. Note that these URLs are not used to find POM files.
      *
-     * <p>The provided values are evaluated as for {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated
+     * <p>The provided values are evaluated as per {@link org.gradle.api.Project#uri(Object)}. This means, for example, you can pass in a {@code File} object, or a relative path to be evaluated
      * relative to the project directory.
      *
      * @param urls The URLs.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/execution/TaskExecutionAdapter.java b/subprojects/core/src/main/groovy/org/gradle/api/execution/TaskExecutionAdapter.java
new file mode 100644
index 0000000..c4091c1
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/execution/TaskExecutionAdapter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.tasks.TaskState;
+
+/**
+ * A {@link TaskExecutionListener} adapter class for receiving task execution events.
+ *
+ * The methods in this class are empty. This class exists as convenience for creating listener objects.
+ */
+public class TaskExecutionAdapter implements TaskExecutionListener {
+
+    public void beforeExecute(Task task) {}
+
+    public void afterExecute(Task task, TaskState state) {}
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/ConfigurableFileCollection.java b/subprojects/core/src/main/groovy/org/gradle/api/file/ConfigurableFileCollection.java
index 9389957..efd2f0b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/ConfigurableFileCollection.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/ConfigurableFileCollection.java
@@ -25,28 +25,28 @@ import java.util.Set;
  */
 public interface ConfigurableFileCollection extends FileCollection {
     /**
-     * Returns the set of source paths for this collection. The paths are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * Returns the set of source paths for this collection. The paths are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @return The set of source paths. Returns an empty set if none.
      */
     Set<Object> getFrom();
 
     /**
-     * Sets the source paths for this collection. The given paths are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * Sets the source paths for this collection. The given paths are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param paths The paths.
      */
     void setFrom(Iterable<?> paths);
 
     /**
-     * Sets the source paths for this collection. The given paths are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * Sets the source paths for this collection. The given paths are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param paths The paths.
      */
     void setFrom(Object... paths);
 
     /**
-     * Adds a set of source paths to this collection. The given paths are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * Adds a set of source paths to this collection. The given paths are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param paths The files to add.
      * @return this
@@ -63,7 +63,7 @@ public interface ConfigurableFileCollection extends FileCollection {
     /**
      * Sets the tasks which build the files of this collection.
      *
-     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @param tasks The tasks. These are evaluated as per {@link org.gradle.api.Task#dependsOn(Object...)}.
      * @return this
      */
     ConfigurableFileCollection setBuiltBy(Iterable<?> tasks);
@@ -71,7 +71,7 @@ public interface ConfigurableFileCollection extends FileCollection {
     /**
      * Registers some tasks which build the files of this collection.
      *
-     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @param tasks The tasks. These are evaluated as per {@link org.gradle.api.Task#dependsOn(Object...)}.
      * @return this
      */
     ConfigurableFileCollection builtBy(Object... tasks);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/ConfigurableFileTree.java b/subprojects/core/src/main/groovy/org/gradle/api/file/ConfigurableFileTree.java
index 173b105..e50dde0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/ConfigurableFileTree.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/ConfigurableFileTree.java
@@ -28,7 +28,7 @@ import java.util.Set;
  */
 public interface ConfigurableFileTree extends FileTree, DirectoryTree, PatternFilterable, Buildable {
     /**
-     * Specifies base directory for this file tree using the given path. The path is evaluated as for {@link
+     * Specifies base directory for this file tree using the given path. The path is evaluated as per {@link
      * org.gradle.api.Project#file(Object)}.
      *
      * @param dir The base directory.
@@ -44,7 +44,7 @@ public interface ConfigurableFileTree extends FileTree, DirectoryTree, PatternFi
     File getDir();
 
     /**
-     * Specifies base directory for this file tree using the given path. The path is evaluated as for {@link
+     * Specifies base directory for this file tree using the given path. The path is evaluated as per {@link
      * org.gradle.api.Project#file(Object)}.
      *
      * @param dir The base directory.
@@ -62,7 +62,7 @@ public interface ConfigurableFileTree extends FileTree, DirectoryTree, PatternFi
     /**
      * Sets the tasks which build the files of this collection.
      *
-     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @param tasks The tasks. These are evaluated as per {@link org.gradle.api.Task#dependsOn(Object...)}.
      * @return this
      */
     ConfigurableFileTree setBuiltBy(Iterable<?> tasks);
@@ -70,7 +70,7 @@ public interface ConfigurableFileTree extends FileTree, DirectoryTree, PatternFi
     /**
      * Registers some tasks which build the files of this collection.
      *
-     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @param tasks The tasks. These are evaluated as per {@link org.gradle.api.Task#dependsOn(Object...)}.
      * @return this
      */
     ConfigurableFileTree builtBy(Object... tasks);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java
index 46c569d..ff564c0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java
@@ -25,7 +25,7 @@ import java.util.regex.Pattern;
  */
 public interface CopyProcessingSpec extends ContentFilterable {
     /**
-     * Specifies the destination directory for a copy. The destination is evaluated as for {@link
+     * Specifies the destination directory for a copy. The destination is evaluated as per {@link
      * org.gradle.api.Project#file(Object)}.
      *
      * @param destPath Path to the destination directory for a Copy
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java
index 2b6f00b..2ef2794 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java
@@ -24,7 +24,7 @@ import groovy.lang.Closure;
  */
 public interface CopySourceSpec {
     /**
-     * Specifies source files or directories for a copy. The given paths are evaluated as for {@link
+     * Specifies source files or directories for a copy. The given paths are evaluated as per {@link
      * org.gradle.api.Project#files(Object...)}.
      *
      * @param sourcePaths Paths to source files for the copy
@@ -33,7 +33,7 @@ public interface CopySourceSpec {
 
     /**
      * Specifies the source files or directories for a copy and creates a child {@code CopySourceSpec}. The given source
-     * path is evaluated as for {@link org.gradle.api.Project#files(Object...)} .
+     * path is evaluated as per {@link org.gradle.api.Project#files(Object...)} .
      *
      * @param sourcePath Path to source for the copy
      * @param configureClosure closure for configuring the child CopySourceSpec
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
index 65cf25f..256a4a0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/CopySpec.java
@@ -195,7 +195,7 @@ public interface CopySpec extends CopySourceSpec, CopyProcessingSpec, PatternFil
 
     /**
      * Creates and configures a child {@code CopySpec} with the given destination path.
-     * The destination is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * The destination is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      *
      * @param destPath Path to the destination directory for a Copy
      * @param configureClosure The closure to use to configure the child {@code CopySpec}.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java b/subprojects/core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java
index e189a4d..626f092 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java
@@ -27,7 +27,7 @@ import java.util.Set;
  *
  * TODO - configure includes/excludes for individual source dirs, and sync up with CopySpec
  * TODO - allow add FileTree
-' */
+ */
 public interface SourceDirectorySet extends FileTree, PatternFilterable, Named {
 
     /**
@@ -38,7 +38,7 @@ public interface SourceDirectorySet extends FileTree, PatternFilterable, Named {
     /**
      * Adds the given source directory to this set.
      *
-     * @param srcPath The source directory. This is evaluated as for {@link org.gradle.api.Project#file(Object)}
+     * @param srcPath The source directory. This is evaluated as per {@link org.gradle.api.Project#file(Object)}
      * @return this
      */
     SourceDirectorySet srcDir(Object srcPath);
@@ -46,7 +46,7 @@ public interface SourceDirectorySet extends FileTree, PatternFilterable, Named {
     /**
      * Adds the given source directories to this set.
      *
-     * @param srcPaths The source directories. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}
+     * @param srcPaths The source directories. These are evaluated as per {@link org.gradle.api.Project#files(Object...)}
      * @return this
      */
     SourceDirectorySet srcDirs(Object... srcPaths);
@@ -61,7 +61,7 @@ public interface SourceDirectorySet extends FileTree, PatternFilterable, Named {
     /**
      * Sets the source directories for this set.
      *
-     * @param srcPaths The source directories. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}
+     * @param srcPaths The source directories. These are evaluated as per {@link org.gradle.api.Project#files(Object...)}
      * @return this
      */
     SourceDirectorySet setSrcDirs(Iterable<?> srcPaths);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
index 327d636..2d64cb1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainer.java
@@ -41,6 +41,14 @@ public abstract class AbstractNamedDomainObjectContainer<T> extends DefaultNamed
         return create(name, null);
     }
 
+    public T maybeCreate(String name) {
+        T item = findByName(name);
+        if (item != null) {
+            return item;
+        }
+        return create(name);
+    }
+
     public T create(String name, Closure configureClosure) {
         assertCanAdd(name);
         T object = doCreate(name);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractPolymorphicDomainObjectContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractPolymorphicDomainObjectContainer.java
new file mode 100644
index 0000000..1f5046e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/AbstractPolymorphicDomainObjectContainer.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.*;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.plugins.Convention;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.util.ConfigureUtil;
+
+import groovy.lang.Closure;
+import groovy.lang.MissingPropertyException;
+
+import java.util.Map;
+
+public abstract class AbstractPolymorphicDomainObjectContainer<T>
+        extends AbstractNamedDomainObjectContainer<T> implements PolymorphicDomainObjectContainer<T> {
+
+    private final ContainerElementsDynamicObject elementsDynamicObject = new ContainerElementsDynamicObject();
+    private final Convention convention;
+    private final DynamicObject dynamicObject;
+
+    protected AbstractPolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, instantiator, namer);
+        this.convention = new DefaultConvention(instantiator);
+        this.dynamicObject = new ExtensibleDynamicObject(this, new ContainerDynamicObject(elementsDynamicObject), convention);
+    }
+
+    protected AbstractPolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator) {
+        this(type, instantiator, Named.Namer.forType(type));
+    }
+
+    protected abstract <U extends T> U doCreate(String name, Class<U> type);
+
+    public <U extends T> U create(String name, Class<U> type) {
+        return create(name, type, null);
+    }
+
+    public <U extends T> U create(String name, Class<U> type, Action<? super U> configuration) {
+        assertCanAdd(name);
+        U object = doCreate(name, type);
+        add(object);
+        if (configuration != null) {
+            configuration.execute(object);
+        }
+        return object;
+    }
+
+    @Override
+    public Convention getConvention() {
+        return convention;
+    }
+
+    @Override
+    protected DynamicObject getElementsAsDynamicObject() {
+        return elementsDynamicObject;
+    }
+
+    @Override
+    public DynamicObject getAsDynamicObject() {
+        return dynamicObject;
+    }
+
+    @Override
+    protected Object createConfigureDelegate(Closure configureClosure) {
+        return new PolymorphicDomainObjectContainerConfigureDelegate(configureClosure.getOwner(), this);
+    }
+
+    private class ContainerDynamicObject extends CompositeDynamicObject {
+        private ContainerDynamicObject(ContainerElementsDynamicObject elementsDynamicObject) {
+            setObjects(new BeanDynamicObject(AbstractPolymorphicDomainObjectContainer.this), elementsDynamicObject, getConvention().getExtensionsAsDynamicObject());
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return AbstractPolymorphicDomainObjectContainer.this.getDisplayName();
+        }
+    }
+
+    private class ContainerElementsDynamicObject extends AbstractDynamicObject {
+        @Override
+        protected String getDisplayName() {
+            return AbstractPolymorphicDomainObjectContainer.this.getDisplayName();
+        }
+
+        @Override
+        public boolean hasProperty(String name) {
+            return findByName(name) != null;
+        }
+
+        @Override
+        public Object getProperty(String name) throws MissingPropertyException {
+            Object object = findByName(name);
+            if (object == null) {
+                return super.getProperty(name);
+            }
+            return object;
+        }
+
+        @Override
+        public Map<String, T> getProperties() {
+            return getAsMap();
+        }
+
+        @Override
+        public boolean hasMethod(String name, Object... arguments) {
+            return isConfigureMethod(name, arguments);
+        }
+
+        @Override
+        public Object invokeMethod(String name, Object... arguments) throws groovy.lang.MissingMethodException {
+            if (isConfigureMethod(name, arguments)) {
+                return ConfigureUtil.configure((Closure) arguments[arguments.length - 1], getByName(name));
+            } else {
+                return super.invokeMethod(name, arguments);
+            }
+        }
+
+        private boolean isConfigureMethod(String name, Object... arguments) {
+            return (arguments.length == 1 && arguments[0] instanceof Closure
+                    || arguments.length == 2 && arguments[0] instanceof Class && arguments[1] instanceof Closure)
+                    && hasProperty(name);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java
index 98b1285..0a1e81e 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConfigureDelegate.java
@@ -14,18 +14,17 @@
  * limitations under the License.
  */
 
-
 package org.gradle.api.internal;
 
 import groovy.lang.Closure;
 import groovy.lang.GroovyObjectSupport;
-import org.gradle.api.Action;
 
 public class ConfigureDelegate extends GroovyObjectSupport {
-    private final DynamicObject owner;
-    private final DynamicObject delegate;
-    private final Action<? super String> onMissing;
-    private final ThreadLocal<Boolean> configuring = new ThreadLocal<Boolean>() {
+    private static final Object[] EMPTY_PARAMS = new Object[0];
+
+    protected final DynamicObject _owner;
+    protected final DynamicObject _delegate;
+    private final ThreadLocal<Boolean> _configuring = new ThreadLocal<Boolean>() {
         @Override
         protected Boolean initialValue() {
             return false;
@@ -33,59 +32,60 @@ public class ConfigureDelegate extends GroovyObjectSupport {
     };
 
     public ConfigureDelegate(Object owner, Object delegate) {
-        this(owner, delegate, Actions.doNothing());
+        _owner = DynamicObjectUtil.asDynamicObject(owner);
+        _delegate = DynamicObjectUtil.asDynamicObject(delegate);
     }
 
-    public ConfigureDelegate(Object owner, Object delegate, Action<? super String> onMissing) {
-        this.owner = DynamicObjectUtil.asDynamicObject(owner);
-        this.delegate = DynamicObjectUtil.asDynamicObject(delegate);
-        this.onMissing = onMissing;
+    protected boolean _isConfigureMethod(String name, Object[] params) {
+        return params.length == 1 && params[0] instanceof Closure;
+    }
+
+    protected void _configure(String name, Object[] params) {
+        // do nothing
     }
 
-    @SuppressWarnings("EmptyCatchBlock")
     public Object invokeMethod(String name, Object paramsObj) {
         Object[] params = (Object[])paramsObj;
 
-        boolean isTopLevelCall = !configuring.get();
-        configuring.set(true);
+        boolean isTopLevelCall = !_configuring.get();
+        _configuring.set(true);
         try {
-            if (delegate.hasMethod(name, params)) {
-                return delegate.invokeMethod(name, params);
+            if (_delegate.hasMethod(name, params)) {
+                return _delegate.invokeMethod(name, params);
             }
 
             // try the owner
             try {
-                return owner.invokeMethod(name, params);
+                return _owner.invokeMethod(name, params);
             } catch (groovy.lang.MissingMethodException e) {
                 // ignore
             }
 
-            boolean isConfigureMethod = (params.length == 1) && (params[0] instanceof Closure);
-            if (isTopLevelCall && isConfigureMethod) {
-                onMissing.execute(name);
+            if (isTopLevelCall && _isConfigureMethod(name, params)) {
+                _configure(name, params);
             }
 
-            return delegate.invokeMethod(name, params);
+            return _delegate.invokeMethod(name, params);
         } finally {
-            configuring.set(!isTopLevelCall);
+            _configuring.set(!isTopLevelCall);
         }
     }
 
-    @SuppressWarnings("EmptyCatchBlock")
     public Object get(String name) {
-        if (delegate.hasProperty(name)) {
-            return delegate.getProperty(name);
+        if (_delegate.hasProperty(name)) {
+            return _delegate.getProperty(name);
         }
 
         // try the owner
         try {
-            return owner.getProperty(name);
+            return _owner.getProperty(name);
         } catch (groovy.lang.MissingPropertyException e) {
             // Ignore
         }
 
+        _configure(name, EMPTY_PARAMS);
+
         // try the delegate again
-        onMissing.execute(name);
-        return delegate.getProperty(name);
+        return _delegate.getProperty(name);
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
index 0390c0a..05e5925 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
@@ -33,22 +33,21 @@ import java.util.concurrent.Callable;
  * @author Hans Dockter
  */
 public class ConventionAwareHelper implements ConventionMapping, HasConvention {
-
-    private Convention convention;
-    private IConventionAware source;
-    private Map<String, MappedPropertyImpl> mappings = new HashMap<String, MappedPropertyImpl>();
+    //prefix internal fields with _ so that they don't get into the way of propertyMissing()
+    private final Convention _convention;
+    private IConventionAware _source;
+    private final Map<String, MappedPropertyImpl> _mappings = new HashMap<String, MappedPropertyImpl>();
 
     /**
      * @see org.gradle.api.internal.AsmBackedClassGenerator.ClassBuilderImpl#mixInConventionAware()
      */
     public ConventionAwareHelper(IConventionAware source) {
-        this.source = source;
-        this.convention = new DefaultConvention();
+        this(source, new DefaultConvention());
     }
 
     public ConventionAwareHelper(IConventionAware source, Convention convention) {
-        this.source = source;
-        this.convention = convention;
+        this._source = source;
+        this._convention = convention;
     }
 
     private static interface Value<T> {
@@ -56,13 +55,13 @@ public class ConventionAwareHelper implements ConventionMapping, HasConvention {
     }
 
     private MappedProperty map(String propertyName, Value<?> value) {
-        if (!ReflectionUtil.hasProperty(source, propertyName)) {
+        if (!ReflectionUtil.hasProperty(_source, propertyName)) {
             throw new InvalidUserDataException(
                     "You can't map a property that does not exist: propertyName=" + propertyName);
         }
 
         MappedPropertyImpl mappedProperty = new MappedPropertyImpl(value);
-        mappings.put(propertyName, mappedProperty);
+        _mappings.put(propertyName, mappedProperty);
         return mappedProperty;
     }
 
@@ -107,7 +106,7 @@ public class ConventionAwareHelper implements ConventionMapping, HasConvention {
         }
 
         Object returnValue = actualValue;
-        if (mappings.containsKey(propertyName)) {
+        if (_mappings.containsKey(propertyName)) {
             boolean useMapping = true;
             if (actualValue instanceof Collection && !((Collection<?>) actualValue).isEmpty()) {
                 useMapping = false;
@@ -115,22 +114,22 @@ public class ConventionAwareHelper implements ConventionMapping, HasConvention {
                 useMapping = false;
             }
             if (useMapping) {
-                returnValue = mappings.get(propertyName).getValue(convention, source);
+                returnValue = _mappings.get(propertyName).getValue(_convention, _source);
             }
         }
         return (T) returnValue;
     }
 
     public Convention getConvention() {
-        return convention;
+        return _convention;
     }
 
     public IConventionAware getSource() {
-        return source;
+        return _source;
     }
 
     public void setSource(IConventionAware source) {
-        this.source = source;
+        this._source = source;
     }
 
     private static class MappedPropertyImpl implements MappedProperty {
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
new file mode 100644
index 0000000..06e2635
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainer.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal;
+
+import org.gradle.api.*;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultPolymorphicDomainObjectContainer<T> extends AbstractPolymorphicDomainObjectContainer<T>
+        implements ExtensiblePolymorphicDomainObjectContainer<T> {
+    @Nullable
+    private NamedDomainObjectFactory<? extends T> defaultFactory;
+
+    private final Map<Class<?>, NamedDomainObjectFactory<?>> factories =
+            new HashMap<Class<?>, NamedDomainObjectFactory<?>>();
+
+    public DefaultPolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator, Namer<? super T> namer) {
+        super(type, instantiator, namer);
+    }
+
+    public DefaultPolymorphicDomainObjectContainer(Class<T> type, Instantiator instantiator) {
+        this(type, instantiator, Named.Namer.forType(type));
+    }
+
+    protected T doCreate(String name) {
+        if (defaultFactory == null) {
+            throw new InvalidUserDataException("This container does not support "
+                    + "creating domain objects without specifying a type.");
+        }
+        return defaultFactory.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("This container does not support "
+                    + "creating domain objects of type '%s'.", type.getName()));
+        }
+        return factory.create(name);
+    }
+
+    public void registerDefaultFactory(NamedDomainObjectFactory<? extends T> factory) {
+        defaultFactory = factory;
+    }
+
+    public <U extends T> void registerFactory(Class<U> type, NamedDomainObjectFactory<? extends U> factory) {
+        if (!getType().isAssignableFrom(type)) {
+            throw new IllegalArgumentException(String.format("Factory element type '%s' is not a subtype of "
+                    + "container element type '%s'", type.getName(), getType().getName()));
+        }
+        factories.put(type, factory);
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java
index b999fc7..ce26dc4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/NamedDomainObjectContainerConfigureDelegate.java
@@ -15,18 +15,18 @@
  */
 package org.gradle.api.internal;
 
-import org.gradle.api.Action;
 import org.gradle.api.NamedDomainObjectContainer;
 
 public class NamedDomainObjectContainerConfigureDelegate extends ConfigureDelegate {
+    private final NamedDomainObjectContainer container;
 
-    public NamedDomainObjectContainerConfigureDelegate(Object owner, final NamedDomainObjectContainer container) {
-        super(owner, container, new Action<String>() {
-            public void execute(String name) {
-                container.create(name);
-            }
-        });
+    public NamedDomainObjectContainerConfigureDelegate(Object owner, NamedDomainObjectContainer container) {
+        super(owner, container);
+        this.container = container;
     }
 
-
+    @Override
+    protected void _configure(String name, Object[] params) {
+        container.create(name);
+    }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicDomainObjectContainerConfigureDelegate.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicDomainObjectContainerConfigureDelegate.java
new file mode 100644
index 0000000..f07befc
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/PolymorphicDomainObjectContainerConfigureDelegate.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal;
+
+import org.gradle.api.PolymorphicDomainObjectContainer;
+
+import groovy.lang.Closure;
+
+public class PolymorphicDomainObjectContainerConfigureDelegate extends NamedDomainObjectContainerConfigureDelegate {
+    private final PolymorphicDomainObjectContainer container;
+
+    public PolymorphicDomainObjectContainerConfigureDelegate(Object owner, PolymorphicDomainObjectContainer container) {
+        super(owner, container);
+        this.container = container;
+    }
+
+    @Override
+    protected boolean _isConfigureMethod(String name, Object[] params) {
+        return super._isConfigureMethod(name, params) || params.length == 2 && params[0] instanceof Class && params[1] instanceof Closure;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    protected void _configure(String name, Object[] params) {
+        if (params.length <= 1) {
+            container.create(name);
+        } else {
+            container.create(name, (Class) params[0]);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
index a0a4ce9..64f14c0 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactPublisher.java
@@ -15,10 +15,10 @@
  */
 package org.gradle.api.internal.artifacts;
 
-import org.apache.ivy.plugins.resolver.DependencyResolver;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishException;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 
 import java.io.File;
 import java.util.Set;
@@ -27,5 +27,5 @@ import java.util.Set;
  * @author Hans Dockter
  */
 public interface ArtifactPublisher {
-    void publish(Iterable<DependencyResolver> dependencyResolvers, Module module, Set<? extends Configuration> configurations, File descriptor) throws PublishException;
+    void publish(Iterable<? extends PublicationAwareRepository> repositories, Module module, Set<? extends Configuration> configurations, File descriptor) throws PublishException;
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/BaseRepositoryFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/BaseRepositoryFactory.java
index 3a0e240..b265e39 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/BaseRepositoryFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/BaseRepositoryFactory.java
@@ -20,14 +20,9 @@ import org.gradle.api.artifacts.repositories.ArtifactRepository;
 import org.gradle.api.artifacts.repositories.FlatDirectoryArtifactRepository;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository;
 
 /**
  * Factory for {@link ArtifactRepository} implementations.
- *
- * Differs from {@link org.gradle.api.internal.artifacts.dsl.RepositoryFactory} in that this is internal and does not provide
- * API for configuring the repositories at creation time. {@link org.gradle.api.internal.artifacts.dsl.RepositoryFactory} is the DSL
- * layer on top of this internal factory.
  */
 public interface BaseRepositoryFactory {
 
@@ -45,5 +40,5 @@ public interface BaseRepositoryFactory {
 
     DependencyResolver toResolver(ArtifactRepository repository);
 
-    FixedResolverArtifactRepository createResolverBackedRepository(DependencyResolver resolver);
+    ArtifactRepository createResolverBackedRepository(DependencyResolver resolver);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
index 3d88572..a5badfc 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainer.java
@@ -167,6 +167,7 @@ public class DefaultArtifactRepositoryContainer extends DefaultNamedDomainObject
     }
 
     public List<DependencyResolver> getResolvers() {
+        DeprecationLogger.nagUserOfDiscontinuedMethod("ArtifactRepositoryContainer.getResolvers()");
         List<DependencyResolver> returnedResolvers = new ArrayList<DependencyResolver>();
         for (ArtifactRepository repository : this) {
             returnedResolvers.add(baseRepositoryFactory.toResolver(repository));
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectDependenciesBuildInstruction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectDependenciesBuildInstruction.java
deleted file mode 100644
index fbb5638..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ProjectDependenciesBuildInstruction.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts;
-
-/**
- * @author Hans Dockter
- */
-public class ProjectDependenciesBuildInstruction {
-    private final boolean rebuild;
-
-    public ProjectDependenciesBuildInstruction(boolean rebuild) {
-        this.rebuild = rebuild;
-    }
-
-    public boolean isRebuild() {
-        return rebuild;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        ProjectDependenciesBuildInstruction that = (ProjectDependenciesBuildInstruction) o;
-
-        return rebuild == that.rebuild;
-    }
-
-    @Override
-    public int hashCode() {
-        return rebuild ? 1 : 0;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolverProvider.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolverProvider.java
deleted file mode 100644
index 0eb4870..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolverProvider.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.configurations;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-
-import java.util.List;
-
-/**
- * @author Hans Dockter
- */
-public interface ResolverProvider {
-    List<DependencyResolver> getResolvers();
-}
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 09c136f..76f9bde 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
@@ -22,11 +22,11 @@ import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.ProjectDependency;
 import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
 import org.gradle.api.internal.artifacts.DependencyResolveContext;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyInternal;
 import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
-import org.gradle.api.tasks.TaskDependency;
+import org.gradle.initialization.ProjectAccessListener;
 
 import java.io.File;
 import java.util.Set;
@@ -36,18 +36,20 @@ import java.util.Set;
  */
 public class DefaultProjectDependency extends AbstractModuleDependency implements ProjectDependency {
     private ProjectInternal dependencyProject;
-    private final ProjectDependenciesBuildInstruction instruction;
+    private final boolean buildProjectDependencies;
     private final TaskDependencyImpl taskDependency = new TaskDependencyImpl();
+    private final ProjectAccessListener projectAccessListener;
 
-    public DefaultProjectDependency(ProjectInternal dependencyProject, ProjectDependenciesBuildInstruction instruction) {
-        this(dependencyProject, null, instruction);
+    public DefaultProjectDependency(ProjectInternal dependencyProject, ProjectAccessListener projectAccessListener, boolean buildProjectDependencies) {
+        this(dependencyProject, null, projectAccessListener, buildProjectDependencies);
     }
 
     public DefaultProjectDependency(ProjectInternal dependencyProject, String configuration,
-                                    ProjectDependenciesBuildInstruction instruction) {
+                                    ProjectAccessListener projectAccessListener, boolean buildProjectDependencies) {
         super(configuration);
         this.dependencyProject = dependencyProject;
-        this.instruction = instruction;
+        this.projectAccessListener = projectAccessListener;
+        this.buildProjectDependencies = buildProjectDependencies;
     }
 
     public Project getDependencyProject() {
@@ -72,7 +74,7 @@ public class DefaultProjectDependency extends AbstractModuleDependency implement
 
     public ProjectDependency copy() {
         DefaultProjectDependency copiedProjectDependency = new DefaultProjectDependency(dependencyProject,
-                getConfiguration(), instruction);
+                getConfiguration(), projectAccessListener, buildProjectDependencies);
         copyTo(copiedProjectDependency);
         return copiedProjectDependency;
     }
@@ -97,7 +99,7 @@ public class DefaultProjectDependency extends AbstractModuleDependency implement
         }
     }
 
-    public TaskDependency getBuildDependencies() {
+    public TaskDependencyInternal getBuildDependencies() {
         return taskDependency;
     }
 
@@ -133,7 +135,7 @@ public class DefaultProjectDependency extends AbstractModuleDependency implement
         if (!this.getConfiguration().equals(that.getConfiguration())) {
             return false;
         }
-        if (!this.instruction.equals(that.instruction)) {
+        if (this.buildProjectDependencies != that.buildProjectDependencies) {
             return false;
         }
         return true;
@@ -141,9 +143,10 @@ public class DefaultProjectDependency extends AbstractModuleDependency implement
 
     @Override
     public int hashCode() {
-        return getDependencyProject().hashCode() ^ getConfiguration().hashCode() ^ instruction.hashCode();
+        return getDependencyProject().hashCode() ^ getConfiguration().hashCode() ^ (buildProjectDependencies ? 1 : 0);
     }
 
+
     @Override
     public String toString() {
         return "DefaultProjectDependency{" + "dependencyProject='" + dependencyProject + '\'' + ", configuration='"
@@ -152,10 +155,11 @@ public class DefaultProjectDependency extends AbstractModuleDependency implement
 
     private class TaskDependencyImpl extends AbstractTaskDependency {
         public void resolve(TaskDependencyResolveContext context) {
-            if (!instruction.isRebuild()) {
+            if (!buildProjectDependencies) {
                 return;
             }
-            dependencyProject.ensureEvaluated();
+            projectAccessListener.beforeResolvingProjectDependency(dependencyProject);
+
             Configuration configuration = getProjectConfiguration();
             context.add(configuration);
             context.add(configuration.getAllArtifacts());
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
index 8474ef5..a7058ad 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
@@ -26,8 +26,6 @@ import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.api.internal.ConfigureByMapAction;
 import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
 import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainer;
-import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
-import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.DeprecationLogger;
@@ -41,7 +39,7 @@ import static org.gradle.util.CollectionUtils.flattenToList;
 /**
  * @author Hans Dockter
  */
-public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer implements RepositoryHandler, ResolverProvider {
+public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer implements RepositoryHandler {
 
     public static final String FLAT_DIR_DEFAULT_NAME = "flatDir";
     private static final String MAVEN_REPO_DEFAULT_NAME = "maven";
@@ -115,7 +113,7 @@ public class DefaultRepositoryHandler extends DefaultArtifactRepositoryContainer
         ConfigureUtil.configureByMap(modifiedArgs, repository);
         DependencyResolver resolver = repositoryFactory.toResolver(repository);
         ConfigureUtil.configure(configClosure, resolver);
-        addRepository(new FixedResolverArtifactRepository(resolver), "mavenRepo");
+        addRepository(repositoryFactory.createResolverBackedRepository(resolver), "mavenRepo");
         return resolver;
     }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
index d95ffa3..d870833 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyModuleDescriptorWriter.java
@@ -17,14 +17,9 @@
 package org.gradle.api.internal.artifacts.ivyservice;
 
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.internal.xml.XmlTransformer;
 
 import java.io.File;
 
 public interface IvyModuleDescriptorWriter {
-
     public void write(ModuleDescriptor md, File output);
-
-    public void write(ModuleDescriptor md, File output, XmlTransformer descriptorModifier);
-
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
index c706a2e..4b4a995 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
@@ -15,9 +15,11 @@
  */
 package org.gradle.api.internal.artifacts.ivyservice;
 
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ModuleDependency;
 
 import java.util.Set;
 
@@ -26,4 +28,8 @@ import java.util.Set;
  */
 public interface ModuleDescriptorConverter {
     ModuleDescriptor convert(Set<? extends Configuration> configurations, Module module);
+
+    ModuleDescriptor createModuleDescriptor(Module module);
+
+    void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository.java
deleted file mode 100644
index f87eb00..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/AbstractArtifactRepository.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.repositories;
-
-import org.gradle.api.NamedDomainObjectCollection;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
-import org.gradle.util.DeprecationLogger;
-
-public abstract class AbstractArtifactRepository implements ArtifactRepositoryInternal {
-
-    private String name;
-    private boolean isPartOfContainer;
-
-    public void onAddToContainer(NamedDomainObjectCollection<ArtifactRepository> container) {
-        isPartOfContainer = true;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public void setName(String name) {
-        if (isPartOfContainer) {
-            DeprecationLogger.nagUserOfDeprecated("Changing the name of an ArtifactRepository that is part of a container", "Set the name when creating the repository");
-        }
-        this.name = name;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryInternal.java
index 516195e..858f4d6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/ArtifactRepositoryInternal.java
@@ -21,7 +21,10 @@ import org.gradle.api.artifacts.repositories.ArtifactRepository;
 
 public interface ArtifactRepositoryInternal extends ArtifactRepository {
 
-    DependencyResolver createResolver();
+    /**
+     * Create a DependencyResolver implementation to use to expose this repository via the old DSL.
+     */
+    DependencyResolver createLegacyDslObject();
 
     void onAddToContainer(NamedDomainObjectCollection<ArtifactRepository> container);
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java
deleted file mode 100644
index a89836e..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/FixedResolverArtifactRepository.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts.repositories;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
-
-public class FixedResolverArtifactRepository extends AbstractArtifactRepository implements ArtifactRepository, ArtifactRepositoryInternal {
-    protected final DependencyResolver resolver;
-
-    public FixedResolverArtifactRepository(DependencyResolver resolver) {
-        this.resolver = resolver;
-    }
-
-    public String getName() {
-        return resolver.getName();
-    }
-
-    public void setName(String name) {
-        resolver.setName(name);
-
-        // We are doing this because we are relying on the deprecation warning that
-        // AbstractArtifactRepository (super) issues. This is a bit awkward.
-        super.setName(name);
-    }
-
-    public DependencyResolver createResolver() {
-        return resolver;
-    }
-
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.java
new file mode 100644
index 0000000..9c493a5
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/PublicationAwareRepository.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.repositories;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+public interface PublicationAwareRepository {
+    DependencyResolver createPublisher();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/component/SoftwareComponentInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/component/SoftwareComponentInternal.java
index 4fd702b..7f6f6ab 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/component/SoftwareComponentInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/component/SoftwareComponentInternal.java
@@ -16,11 +16,11 @@
 
 package org.gradle.api.internal.component;
 
-import org.gradle.api.artifacts.DependencySet;
-import org.gradle.api.artifacts.PublishArtifactSet;
 import org.gradle.api.component.SoftwareComponent;
 
+import java.util.Set;
+
 public interface SoftwareComponentInternal extends SoftwareComponent {
-    PublishArtifactSet getArtifacts();
-    DependencySet getRuntimeDependencies();
+    Set<Usage> getUsages();
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/component/Usage.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/component/Usage.java
new file mode 100644
index 0000000..abe6f5c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/component/Usage.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.component;
+
+import org.gradle.api.Named;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.PublishArtifact;
+
+import java.util.Set;
+
+public interface Usage extends Named {
+    Set<PublishArtifact> getArtifacts();
+    Set<ModuleDependency> getDependencies();
+}
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 75582dd..c43f0bd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
@@ -22,6 +22,8 @@ import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
 import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
 import org.gradle.api.resources.ReadableResource;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
@@ -32,6 +34,7 @@ import java.io.File;
 import java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.regex.Pattern;
@@ -53,6 +56,19 @@ public abstract class AbstractFileResolver implements FileResolver {
         return resolve(path, PathValidation.NONE);
     }
 
+    public NotationParser<File> asNotationParser() {
+        return new NotationParser<File>() {
+            public File parseNotation(Object notation) throws UnsupportedNotationException {
+                // TODO Further differentiate between unsupported notation errors and others (particularly when we remove the deprecated 'notation.toString()' resolution)
+                return resolve(notation, PathValidation.NONE);
+            }
+
+            public void describe(Collection<String> candidateFormats) {
+                candidateFormats.add("Anything that can be converted to a file, as per Project.file()");
+            }
+        };
+    }
+
     public File resolve(Object path, PathValidation validation) {
         File file = doResolve(path);
 
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 8e05cae..0d5c954 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.file;
 import org.gradle.api.PathValidation;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.notations.api.NotationParser;
 import org.gradle.api.resources.ReadableResource;
 import org.gradle.internal.Factory;
 
@@ -46,4 +47,6 @@ public interface FileResolver {
      * @param path The path for the base directory. Resolved relative to the current base directory (if any).
      */
     FileResolver withBaseDir(Object path);
+
+    NotationParser<File> asNotationParser();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
deleted file mode 100644
index 55c52f9..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.file.archive;
-
-import org.gradle.api.internal.file.archive.compression.Compressor;
-import org.gradle.api.internal.file.copy.ArchiveCopyAction;
-
-public interface TarCopyAction extends ArchiveCopyAction {
-    Compressor getCompressor();
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
index 9d46946..506dc4c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
@@ -21,6 +21,7 @@ import org.apache.tools.zip.UnixStat;
 import org.gradle.api.GradleException;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.internal.file.copy.ArchiveCopyAction;
 import org.gradle.api.internal.file.copy.CopyAction;
 import org.gradle.api.internal.file.copy.EmptyCopySpecVisitor;
 
@@ -33,10 +34,10 @@ public class TarCopySpecVisitor extends EmptyCopySpecVisitor {
     private File tarFile;
 
     public void startVisit(CopyAction action) {
-        TarCopyAction archiveAction = (TarCopyAction) action;
+        ArchiveCopyAction archiveAction = (ArchiveCopyAction) action;
         try {
             tarFile = archiveAction.getArchivePath();
-            OutputStream outStr = archiveAction.getCompressor().compress(tarFile);
+            OutputStream outStr = archiveAction.getCompressor().createArchiveOutputStream(tarFile);
             tarOutStr = new TarOutputStream(outStr);
             tarOutStr.setLongFileMode(TarOutputStream.LONGFILE_GNU);
         } catch (Exception e) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopyAction.java
new file mode 100644
index 0000000..14ebd22
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopyAction.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.archive;
+
+import org.gradle.api.internal.file.copy.ArchiveCopyAction;
+import org.gradle.api.internal.file.copy.ZipCompressor;
+
+public interface ZipCopyAction extends ArchiveCopyAction {
+
+    public ZipCompressor getCompressor();
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
index 28881ed..1491f4b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
@@ -20,7 +20,6 @@ import org.gradle.api.GradleException;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.file.copy.CopyAction;
 import org.gradle.api.file.FileVisitDetails;
-import org.gradle.api.internal.file.copy.ArchiveCopyAction;
 import org.gradle.api.internal.file.copy.EmptyCopySpecVisitor;
 
 import java.io.File;
@@ -31,10 +30,10 @@ public class ZipCopySpecVisitor extends EmptyCopySpecVisitor {
     private File zipFile;
 
     public void startVisit(CopyAction action) {
-        ArchiveCopyAction archiveAction = (ArchiveCopyAction) action;
+        ZipCopyAction archiveAction = (ZipCopyAction) action;
         zipFile = archiveAction.getArchivePath();
         try {
-            zipOutStr = new ZipOutputStream(zipFile);
+            zipOutStr = archiveAction.getCompressor().createArchiveOutputStream(zipFile);
         } catch (Exception e) {
             throw new GradleException(String.format("Could not create ZIP '%s'.", zipFile), e);
         }
@@ -53,7 +52,6 @@ public class ZipCopySpecVisitor extends EmptyCopySpecVisitor {
     public void visitFile(FileVisitDetails fileDetails) {
         try {
             ZipEntry archiveEntry = new ZipEntry(fileDetails.getRelativePath().getPathString());
-            archiveEntry.setMethod(ZipEntry.DEFLATED);
             archiveEntry.setTime(fileDetails.getLastModified());
             archiveEntry.setUnixMode(UnixStat.FILE_FLAG | fileDetails.getMode());
             zipOutStr.putNextEntry(archiveEntry);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/ArchiveOutputStreamFactory.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/ArchiveOutputStreamFactory.java
new file mode 100644
index 0000000..5f48c8b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/ArchiveOutputStreamFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.file.archive.compression;
+
+import java.io.File;
+import java.io.OutputStream;
+
+/**
+ * Compresses the input
+ */
+public interface ArchiveOutputStreamFactory {
+
+    /**
+     * Returns the output stream that is able to compress into the destination file
+     *
+     * @param destination the destination of the archive output stream
+     */
+    OutputStream createArchiveOutputStream(File destination);
+}
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 95893bd..b923418 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
@@ -42,11 +42,11 @@ public class Bzip2Archiver implements ReadableResource {
         this.uri = new URIBuilder(resource.getURI()).schemePrefix("bzip2:").build();
     }
 
-    public static Compressor getCompressor() {
+    public static ArchiveOutputStreamFactory getCompressor() {
         // this is not very beautiful but at some point we will
-        // get rid of Compressor in favor of the writable Resource
-        return new Compressor() {
-            public OutputStream compress(File destination) {
+        // get rid of ArchiveOutputStreamFactory in favor of the writable Resource
+        return new ArchiveOutputStreamFactory() {
+            public OutputStream createArchiveOutputStream(File destination) {
                 try {
                     OutputStream outStr = new FileOutputStream(destination);
                     outStr.write('B');
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Compressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Compressor.java
deleted file mode 100644
index dce667d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/Compressor.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.file.archive.compression;
-
-import java.io.File;
-import java.io.OutputStream;
-
-/**
- * Compresses the input
- */
-public interface Compressor {
-
-    /**
-     * Returns the output stream that is able to compress into the destination file
-     *
-     * @param destination the destination of the compression
-     */
-    OutputStream compress(File destination);
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/GzipArchiver.java
index 9666700..619d812 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
@@ -42,11 +42,11 @@ public class GzipArchiver implements ReadableResource {
         this.uri = new URIBuilder(resource.getURI()).schemePrefix("gzip:").build();
     }
 
-    public static Compressor getCompressor() {
+    public static ArchiveOutputStreamFactory getCompressor() {
         // this is not very beautiful but at some point we will
-        // get rid of Compressor in favor of the writable Resource
-        return new Compressor() {
-            public OutputStream compress(File destination) {
+        // get rid of ArchiveOutputStreamFactory in favor of the writable Resource
+        return new ArchiveOutputStreamFactory() {
+            public OutputStream createArchiveOutputStream(File destination) {
                 try {
                     OutputStream outStr = new FileOutputStream(destination);
                     return new GZIPOutputStream(outStr);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java
index 9d29703..c47a5df 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/archive/compression/SimpleCompressor.java
@@ -23,9 +23,9 @@ import java.io.OutputStream;
 /**
  * by Szczepan Faber, created at: 11/16/11
  */
-public class SimpleCompressor implements Compressor {
+public class SimpleCompressor implements ArchiveOutputStreamFactory {
 
-    public OutputStream compress(File destination) {
+    public OutputStream createArchiveOutputStream(File destination) {
         try {
             return new FileOutputStream(destination);
         } catch (Exception e) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/AbstractZipCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/AbstractZipCompressor.java
new file mode 100644
index 0000000..4d1667a
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/AbstractZipCompressor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.apache.tools.zip.ZipOutputStream;
+import org.gradle.api.UncheckedIOException;
+
+import java.io.File;
+
+abstract class AbstractZipCompressor implements ZipCompressor {
+
+    abstract public int getCompressedMethod();
+
+    public ZipOutputStream createArchiveOutputStream(File destination) {
+        try {
+            ZipOutputStream outStream = new ZipOutputStream(destination);
+            outStream.setMethod(getCompressedMethod());
+            return outStream;
+        } catch (Exception e) {
+            String message = String.format("Unable to create ZIP output stream for file %s.", destination);
+            throw new UncheckedIOException(message, e);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java
index 8058188..bccdf42 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java
@@ -17,6 +17,9 @@ package org.gradle.api.internal.file.copy;
 
 import java.io.File;
 
+import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
+
 public interface ArchiveCopyAction extends CopyAction {
     File getArchivePath();
+    ArchiveOutputStreamFactory getCompressor();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipCompressor.java
new file mode 100644
index 0000000..c99e67b
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipCompressor.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import java.io.File;
+
+import org.apache.tools.zip.ZipOutputStream;
+import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
+
+public interface ZipCompressor extends ArchiveOutputStreamFactory {
+
+    ZipOutputStream createArchiveOutputStream(File destination);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipDeflatedCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipDeflatedCompressor.java
new file mode 100644
index 0000000..65bcbde
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipDeflatedCompressor.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.apache.tools.zip.ZipOutputStream;
+
+public class ZipDeflatedCompressor extends AbstractZipCompressor {
+
+    public static final ZipCompressor INSTANCE = new ZipDeflatedCompressor();
+
+    public ZipDeflatedCompressor() {
+    }
+
+    @Override
+    public int getCompressedMethod() {
+        return ZipOutputStream.DEFLATED;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipStoredCompressor.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipStoredCompressor.java
new file mode 100644
index 0000000..fa71d59
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/file/copy/ZipStoredCompressor.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.file.copy;
+
+import org.apache.tools.zip.ZipOutputStream;
+
+public class ZipStoredCompressor extends AbstractZipCompressor{
+
+    public static final ZipCompressor INSTANCE = new ZipStoredCompressor();
+
+    @Override
+    public int getCompressedMethod() {
+        return ZipOutputStream.STORED;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/html/SimpleHtmlWriter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/html/SimpleHtmlWriter.java
new file mode 100644
index 0000000..afd493e
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/html/SimpleHtmlWriter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.html;
+
+import org.gradle.api.internal.xml.SimpleMarkupWriter;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * <p>A streaming HTML writer.</p>
+ */
+public class SimpleHtmlWriter extends SimpleMarkupWriter {
+
+    public SimpleHtmlWriter(Writer writer) throws IOException {
+        this(writer, null);
+    }
+
+    public SimpleHtmlWriter(Writer writer, String indent) throws IOException {
+        super(writer, indent);
+        writeHtmlHeader();
+    }
+
+    private void writeHtmlHeader() throws IOException {
+        writeRaw("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/TopLevelNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/TopLevelNotationParser.java
deleted file mode 100644
index e0c1ff8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/api/TopLevelNotationParser.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.notations.api;
-
-/**
- * Marker interface to group all top-level notation parsers.
- *
- * by Szczepan Faber, created at: 11/8/11
- */
-public interface TopLevelNotationParser {}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java
index 0e9cf2f..db00741 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/CompositeNotationParser.java
@@ -19,7 +19,6 @@ package org.gradle.api.internal.notations.parsers;
 import org.gradle.api.internal.notations.api.NotationParser;
 import org.gradle.api.internal.notations.api.UnsupportedNotationException;
 
-import java.util.Arrays;
 import java.util.Collection;
 
 /**
@@ -29,10 +28,6 @@ public class CompositeNotationParser<T> implements NotationParser<T> {
 
     private final Collection<NotationParser<? extends T>> delegates;
 
-    public CompositeNotationParser(NotationParser<? extends T>... delegates) {
-        this.delegates = Arrays.asList(delegates);
-    }
-
     public CompositeNotationParser(Collection<NotationParser<? extends T>> delegates) {
         assert delegates != null : "delegates cannot be null!";
         this.delegates = delegates;
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java
index 979d79e..527c1d4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParser.java
@@ -45,18 +45,16 @@ public class ErrorHandlingNotationParser<T> implements NotationParser<T> {
     }
 
     public T parseNotation(Object notation) {
-        Object brokenNotation;
-        try {
-            return delegate.parseNotation(notation);
-        } catch (UnsupportedNotationException e) {
-            brokenNotation = e.getNotation();
-        }
-
         Formatter message = new Formatter();
-        if (brokenNotation == null) {
+        if (notation == null) {
+            //we don't support null input at the moment. If you need this please implement it.
             message.format("Cannot convert a null value to an object of type %s.%n", targetTypeDisplayName);
         } else {
-            message.format("Cannot convert the provided notation to an object of type %s: %s.%n", targetTypeDisplayName, brokenNotation);
+            try {
+                return delegate.parseNotation(notation);
+            } catch (UnsupportedNotationException e) {
+                message.format("Cannot convert the provided notation to an object of type %s: %s.%n", targetTypeDisplayName, e.getNotation());
+            }
         }
 
         message.format("The following types/formats are supported:");
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java
index f7ce1b8..834cbeb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/MapNotationParser.java
@@ -85,6 +85,8 @@ public abstract class MapNotationParser<T> extends TypedNotationParser<Map, T> i
             params[i] = value;
         }
         if (!missing.isEmpty()) {
+            //TODO SF below could be better.
+            //Throwing InvalidUserDataException here means that useful context information (including candidate formats, etc.) is not presented to the user
             throw new InvalidUserDataException(String.format("Required keys %s are missing from map %s.", missing, values));
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/NormalizedTimeUnit.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/NormalizedTimeUnit.java
new file mode 100644
index 0000000..0cf3327
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/NormalizedTimeUnit.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.notations.parsers;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * by Szczepan Faber, created at: 2/18/13
+ */
+public class NormalizedTimeUnit {
+
+    private int value;
+    private TimeUnit timeUnit;
+
+    public NormalizedTimeUnit(int value, TimeUnit timeUnit) {
+        this.value = value;
+        this.timeUnit = timeUnit;
+    }
+
+    public static NormalizedTimeUnit millis(int value) {
+        return new NormalizedTimeUnit(value, TimeUnit.MILLISECONDS);
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public TimeUnit getTimeUnit() {
+        return timeUnit;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParser.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParser.java
new file mode 100644
index 0000000..e37616f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParser.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.notations.parsers;
+
+import org.gradle.api.InvalidUserDataException;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.gradle.api.internal.notations.parsers.NormalizedTimeUnit.millis;
+
+/**
+ * by Szczepan Faber, created at: 2/12/13
+ */
+public class TimeUnitsParser {
+
+    public NormalizedTimeUnit parseNotation(CharSequence notation, int value) {
+        String candidate = notation.toString().toUpperCase();
+        //jdk5 does not have days, hours or minutes, normalizing to millis
+        if (candidate.equals("DAYS")) {
+            return millis(value * 24 * 60 * 60 * 1000);
+        } else if (candidate.equals("HOURS")) {
+            return millis(value * 60 * 60 * 1000);
+        } else if (candidate.equals("MINUTES")) {
+            return millis(value * 60 * 1000);
+        }
+        try {
+            return new NormalizedTimeUnit(value, TimeUnit.valueOf(candidate));
+        } catch (Exception e) {
+            throw new InvalidUserDataException("Unable to parse provided TimeUnit: " + notation, e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java
index b64bf1b..5584d4c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.java
@@ -18,6 +18,7 @@ package org.gradle.api.internal.plugins;
 
 import groovy.lang.MissingMethodException;
 import groovy.lang.MissingPropertyException;
+import org.gradle.api.Action;
 import org.gradle.api.GradleException;
 import org.gradle.api.internal.BeanDynamicObject;
 import org.gradle.api.internal.DynamicObject;
@@ -136,6 +137,10 @@ public class DefaultConvention implements Convention {
         return extensionsStorage.findByName(name);
     }
 
+    public <T> void configure(Class<T> type, Action<? super T> action) {
+        extensionsStorage.configureExtension(type, action);
+    }
+
     public Object propertyMissing(String name) {
         return getByName(name);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java
index acb8e91..00372ba 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/plugins/ExtensionsStorage.java
@@ -17,23 +17,30 @@
 package org.gradle.api.internal.plugins;
 
 import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.UnknownDomainObjectException;
-import org.gradle.util.ConfigureUtil;
+import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.plugins.DeferredConfigurable;
+import org.gradle.listener.ActionBroadcast;
+import org.gradle.listener.ListenerNotificationException;
 
-import java.util.*;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 
 /**
- * @author: Szczepan Faber, created at: 6/24/11
+ * @author Szczepan Faber, created at: 6/24/11
  */
 public class ExtensionsStorage {
-
-    private final Map<String, Object> extensions = new LinkedHashMap<String, Object>();
+    private final Map<String, ExtensionHolder> extensions = new LinkedHashMap<String, ExtensionHolder>();
 
     public void add(String name, Object extension) {
         if (extensions.containsKey(name)) {
             throw new IllegalArgumentException(String.format("Cannot add extension with name '%s', as there is an extension already registered with that name.", name));
         }
-        extensions.put(name, extension);
+        extensions.put(name, wrap(extension));
     }
 
     public boolean hasExtension(String name) {
@@ -41,7 +48,11 @@ public class ExtensionsStorage {
     }
 
     public Map<String, Object> getAsMap() {
-        return extensions;
+        Map<String, Object> rawExtensions = new LinkedHashMap<String, Object>(extensions.size());
+        for (String name : extensions.keySet()) {
+            rawExtensions.put(name, extensions.get(name).get());
+        }
+        return rawExtensions;
     }
 
     public void checkExtensionIsNotReassigned(String name) {
@@ -54,41 +65,125 @@ public class ExtensionsStorage {
         return extensions.containsKey(methodName) && arguments.length == 1 && arguments[0] instanceof Closure;
     }
 
-    public Object configureExtension(String methodName, Object ... arguments) {
-        return ConfigureUtil.configure((Closure) arguments[0], extensions.get(methodName));
+    public <T> T configureExtension(String methodName, Object ... arguments) {
+        Closure closure = (Closure) arguments[0];
+        ClosureBackedAction<T> action = new ClosureBackedAction<T>(closure);
+        ExtensionHolder<T> extensionHolder = extensions.get(methodName);
+        return extensionHolder.configure(action);
+    }
+
+    public <T> void configureExtension(Class<T> type, Action<? super T> action) {
+        getHolderByType(type).configure(action);
     }
 
     public <T> T getByType(Class<T> type) {
-        Collection<Object> values = extensions.values();
-        List types = new LinkedList();
-        for (Object e : values) {
-            Class clazz = e.getClass();
-            types.add(clazz.getSimpleName());
-            if (type.isAssignableFrom(clazz)) {
-                return (T) e;
-            }
-        }
-        throw new UnknownDomainObjectException("Extension of type '" + type.getSimpleName() + "' does not exist. Currently registered extension types: " + types);
+        return getHolderByType(type).get();
     }
 
     public <T> T findByType(Class<T> type) {
-        Collection<Object> values = extensions.values();
-        for (Object e : values) {
-            if (type.isAssignableFrom(e.getClass())) {
-                return (T) e;
+        try {
+            return getHolderByType(type).get();
+        } catch (UnknownDomainObjectException e) {
+            return null;
+        }
+    }
+
+    private <T> ExtensionHolder<T> getHolderByType(Class<T> type) {
+        List<String> types = new LinkedList<String>();
+        for (ExtensionHolder extensionHolder : extensions.values()) {
+            Class<?> clazz = extensionHolder.getType();
+            types.add(clazz.getSimpleName());
+            if (type.isAssignableFrom(clazz)) {
+                return extensionHolder;
             }
         }
-        return null;
+        throw new UnknownDomainObjectException("Extension of type '" + type.getSimpleName() + "' does not exist. Currently registered extension types: " + types);
     }
 
     public Object getByName(String name) {
         if (!hasExtension(name)) {
             throw new UnknownDomainObjectException("Extension with name '" + name + "' does not exist. Currently registered extension names: " + extensions.keySet());
         }
-        return extensions.get(name);
+        return findByName(name);
     }
 
     public Object findByName(String name) {
-        return extensions.get(name);
+        ExtensionHolder extensionHolder = extensions.get(name);
+        return extensionHolder == null ? null : extensionHolder.get();
+    }
+
+    private <T> ExtensionHolder<T> wrap(T extension) {
+        if (isDeferredConfigurable(extension)) {
+            return new DeferredConfigurableExtensionHolder<T>(extension);
+        }
+        return new ExtensionHolder<T>(extension);
+    }
+
+    private <T> boolean isDeferredConfigurable(T extension) {
+        return extension.getClass().isAnnotationPresent(DeferredConfigurable.class);
+    }
+
+    private static class ExtensionHolder<T> {
+        protected final T extension;
+
+        private ExtensionHolder(T extension) {
+            this.extension = extension;
+        }
+
+        public Class<?> getType() {
+            return extension.getClass();
+        }
+
+        public T get() {
+            return extension;
+        }
+
+        public T configure(Closure configuration) {
+            return configure(new ClosureBackedAction<T>(configuration));
+        }
+
+        public T configure(Action<? super T> action) {
+            action.execute(extension);
+            return extension;
+        }
+    }
+
+    private static class DeferredConfigurableExtensionHolder<T> extends ExtensionHolder<T> {
+        private ActionBroadcast<T> actions = new ActionBroadcast<T>();
+        private boolean configured;
+
+        private DeferredConfigurableExtensionHolder(T extension) {
+            super(extension);
+        }
+
+        public T get() {
+            configureNow();
+            return extension;
+        }
+
+        @Override
+        public T configure(Action<? super T> action) {
+            configureLater(action);
+            return null;
+        }
+
+        private void configureLater(Action<? super T> action) {
+            if (configured) {
+                throw new IllegalStateException("The 'publishing' extension is already configured");
+            }
+            actions.add(action);
+        }
+
+        private void configureNow() {
+            if (!configured) {
+                configured = true;
+                try {
+                    actions.execute(extension);
+                } catch (ListenerNotificationException e) {
+                    throw new InvalidUserDataException("A problem occurred configuring the 'publishing' extension", e.getCause());
+                }
+            }
+        }
+
     }
 }
\ No newline at end of file
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 a4fbccd..4162959 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
@@ -331,8 +331,6 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return dependsOnProjects;
     }
 
-
-
     public ProjectStateInternal getState() {
         return state;
     }
@@ -468,12 +466,6 @@ public abstract class AbstractProject implements ProjectInternal, DynamicObjectA
         return this;
     }
 
-    public void ensureEvaluated() {
-        if (this.gradle.getStartParameter().isConfigureOnDemand()) {
-            this.evaluate();
-        }
-    }
-
     public TaskContainerInternal getTasks() {
         return taskContainer;
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectAccessListener.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectAccessListener.java
new file mode 100644
index 0000000..ab2c723
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectAccessListener.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.project;
+
+import org.gradle.initialization.ProjectAccessListener;
+
+/**
+* by Szczepan Faber, created at: 2/11/13
+*/
+public class DefaultProjectAccessListener implements ProjectAccessListener {
+
+    public void beforeRequestingTaskByPath(ProjectInternal targetProject) {
+        targetProject.evaluate();
+    }
+
+    public void beforeResolvingProjectDependency(ProjectInternal dependencyProject) {
+        dependencyProject.evaluate();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
index 70dc4bf..996720f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
@@ -15,6 +15,7 @@
  */
 package org.gradle.api.internal.project;
 
+import org.gradle.StartParameter;
 import org.gradle.api.internal.DependencyInjectingInstantiator;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
@@ -29,6 +30,9 @@ import org.gradle.internal.service.DefaultServiceRegistry;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.listener.ListenerManager;
 
+import java.util.LinkedList;
+import java.util.List;
+
 import static java.util.Arrays.asList;
 
 /**
@@ -44,11 +48,16 @@ public class GradleInternalServiceRegistry extends DefaultServiceRegistry implem
     }
 
     protected BuildExecuter createBuildExecuter() {
+        List<BuildConfigurationAction> configs = new LinkedList<BuildConfigurationAction>();
+        if (get(StartParameter.class).isConfigureOnDemand()) {
+            configs.add(new ProjectEvaluatingAction());
+        }
+        configs.add(new DefaultTasksBuildExecutionAction());
+        configs.add(new ExcludedTaskFilteringBuildConfigurationAction());
+        configs.add(new TaskNameResolvingBuildConfigurationAction());
+
         return new DefaultBuildExecuter(
-                asList(new OnlyWhenConfigureOnDemand(new ProjectEvaluatingAction(new TaskPathProjectEvaluator())),
-                        new DefaultTasksBuildExecutionAction(),
-                        new ExcludedTaskFilteringBuildConfigurationAction(),
-                        new TaskNameResolvingBuildConfigurationAction()),
+                configs,
                 asList(new DryRunBuildExecutionAction(),
                         new TaskCacheLockHandlingBuildExecuter(get(TaskArtifactStateCacheAccess.class)),
                         new SelectedTaskExecutionAction()));
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
index 381d7e1..b5a0fbd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
@@ -39,8 +39,6 @@ public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware
 
     Project evaluate();
 
-    void ensureEvaluated();
-
     TaskContainerInternal getTasks();
 
     TaskContainerInternal getImplicitTasks();
@@ -68,4 +66,6 @@ public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware
     ServiceRegistryFactory getServices();
 
     StandardOutputCapture getStandardOutputCapture();
+
+    ProjectStateInternal getState();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
index 2246e3e..712e621 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
@@ -46,6 +46,7 @@ import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 import org.gradle.api.internal.tasks.DefaultTaskContainerFactory;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.api.plugins.PluginContainer;
+import org.gradle.initialization.ProjectAccessListener;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 import org.gradle.internal.reflect.Instantiator;
@@ -103,7 +104,7 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
     }
 
     protected Factory<TaskContainerInternal> createTaskContainerInternal() {
-        return new DefaultTaskContainerFactory(get(Instantiator.class), get(ITaskFactory.class), project);
+        return new DefaultTaskContainerFactory(get(Instantiator.class), get(ITaskFactory.class), project, get(ProjectAccessListener.class));
     }
 
     protected ArtifactPublicationServices createArtifactPublicationServices() {
@@ -119,7 +120,8 @@ public class ProjectInternalServiceRegistry extends DefaultServiceRegistry imple
     }
 
     protected SoftwareComponentContainer createSoftwareComponentContainer() {
-        return new DefaultSoftwareComponentContainer(get(Instantiator.class));
+        Instantiator instantiator = get(Instantiator.class);
+        return instantiator.newInstance(DefaultSoftwareComponentContainer.class, instantiator);
     }
 
     protected DependencyResolutionServices createDependencyResolutionServices() {
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
index b465b93..146f248 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TaskExecutionServices.java
@@ -17,7 +17,6 @@ package org.gradle.api.internal.project;
 
 import org.gradle.StartParameter;
 import org.gradle.api.execution.TaskActionListener;
-import org.gradle.api.internal.DocumentationRegistry;
 import org.gradle.api.internal.changedetection.*;
 import org.gradle.api.internal.tasks.TaskExecuter;
 import org.gradle.api.internal.tasks.execution.*;
@@ -82,6 +81,6 @@ public class TaskExecutionServices extends DefaultServiceRegistry {
     protected TaskPlanExecutor createTaskExecutorFactory() {
         StartParameter startParameter = gradle.getStartParameter();
         TaskArtifactStateCacheAccess cacheAccess = get(TaskArtifactStateCacheAccess.class);
-        return new TaskPlanExecutorFactory(cacheAccess, startParameter.getParallelThreadCount(), get(DocumentationRegistry.class)).create();
+        return new TaskPlanExecutorFactory(cacheAccess, startParameter.getParallelThreadCount()).create();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
index 3f2725d..d5b18f4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
@@ -219,10 +219,11 @@ public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry impleme
     }
 
     protected BuildConfigurer createBuildConfigurer() {
-        return new DefaultBuildConfigurer(
-                new ProjectEvaluationConfigurer(),
-                new ProjectDependencies2TaskResolver(),
-                new ImplicitTasksConfigurer());
+        return new DefaultBuildConfigurer();
+    }
+
+    protected ProjectAccessListener createProjectAccessListener() {
+        return new DefaultProjectAccessListener();
     }
 
     protected ProfileEventAdapter createProfileEventAdapter() {
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 4e61e86..eb4cbde 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
@@ -27,6 +27,7 @@ import org.gradle.api.internal.DynamicObject;
 import org.gradle.api.internal.NamedDomainObjectContainerConfigureDelegate;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.initialization.ProjectAccessListener;
 import org.gradle.internal.reflect.Instantiator;
 import org.gradle.util.ConfigureUtil;
 import org.gradle.util.DeprecationLogger;
@@ -38,10 +39,12 @@ import java.util.Map;
 
 public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements TaskContainerInternal {
     private final ITaskFactory taskFactory;
+    private final ProjectAccessListener projectAccessListener;
 
-    public DefaultTaskContainer(ProjectInternal project, Instantiator instantiator, ITaskFactory taskFactory) {
+    public DefaultTaskContainer(ProjectInternal project, Instantiator instantiator, ITaskFactory taskFactory, ProjectAccessListener projectAccessListener) {
         super(Task.class, instantiator, project);
         this.taskFactory = taskFactory;
+        this.projectAccessListener = projectAccessListener;
     }
 
     public Task add(Map<String, ?> options) {
@@ -80,6 +83,14 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         return add(name);
     }
 
+    public Task maybeCreate(String name) {
+        Task task = findByName(name);
+        if (task != null) {
+            return task;
+        }
+        return create(name);
+    }
+
     public Task add(String name) {
         return add(GUtil.map(Task.TASK_NAME, name));
     }
@@ -109,10 +120,12 @@ public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements
         }
 
         String projectPath = StringUtils.substringBeforeLast(path, Project.PATH_SEPARATOR);
-        Project project = this.project.findProject(!GUtil.isTrue(projectPath) ? Project.PATH_SEPARATOR : projectPath);
+        ProjectInternal project = this.project.findProject(!GUtil.isTrue(projectPath) ? Project.PATH_SEPARATOR : projectPath);
         if (project == null) {
             return null;
         }
+        projectAccessListener.beforeRequestingTaskByPath(project);
+
         return project.getTasks().findByName(StringUtils.substringAfterLast(path, Project.PATH_SEPARATOR));
     }
 
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 edd84e0..95f7b62 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerFactory.java
@@ -16,22 +16,25 @@
 package org.gradle.api.internal.tasks;
 
 import org.gradle.api.Project;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.initialization.ProjectAccessListener;
 import org.gradle.internal.Factory;
 import org.gradle.internal.reflect.Instantiator;
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
 
 public class DefaultTaskContainerFactory implements Factory<TaskContainerInternal> {
     private final Instantiator instantiator;
     private final ITaskFactory taskFactory;
     private Project project;
+    public ProjectAccessListener projectAccessListener;
 
-    public DefaultTaskContainerFactory(Instantiator instantiator, ITaskFactory taskFactory, Project project) {
+    public DefaultTaskContainerFactory(Instantiator instantiator, ITaskFactory taskFactory, Project project, ProjectAccessListener projectAccessListener) {
         this.instantiator = instantiator;
         this.taskFactory = taskFactory;
         this.project = project;
+        this.projectAccessListener = projectAccessListener;
     }
 
     public TaskContainerInternal create() {
-        return instantiator.newInstance(DefaultTaskContainer.class, project, instantiator, taskFactory);
+        return instantiator.newInstance(DefaultTaskContainer.class, project, instantiator, taskFactory, projectAccessListener);
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleMarkupWriter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleMarkupWriter.java
new file mode 100644
index 0000000..b8eb52c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleMarkupWriter.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.xml;
+
+import org.gradle.internal.SystemProperties;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.LinkedList;
+
+/**
+ * <p>A streaming markup writer. Encodes characters and CDATA. Provides only basic state validation, and some simple indentation.</p>
+ *
+ * <p>This class also is-a {@link Writer}, and any characters written to this writer will be encoded as appropriate. Note, however, that
+ * calling {@link #close()} on this object does not close the backing stream.
+ * </p>
+ */
+public class SimpleMarkupWriter extends Writer {
+    private enum Context {
+        Outside, Text, CData, StartTag, ElementContent
+    }
+
+    private final Writer output;
+    private final LinkedList<String> elements = new LinkedList<String>();
+    private Context context = Context.Outside;
+    private int squareBrackets;
+    private final String indent;
+
+    protected SimpleMarkupWriter(Writer writer, String indent) throws IOException {
+        this.indent = indent;
+        this.output = writer;
+    }
+
+    @Override
+    public void write(char[] chars, int offset, int length) throws IOException {
+        characters(chars, offset, length);
+    }
+
+    @Override
+    public void flush() throws IOException {
+        output.flush();
+    }
+
+    @Override
+    public void close() throws IOException {
+        // Does nothing
+    }
+
+    public SimpleMarkupWriter characters(char[] characters) throws IOException {
+        characters(characters, 0, characters.length);
+        return this;
+    }
+
+    public SimpleMarkupWriter characters(char[] characters, int start, int count) throws IOException {
+        if (context == Context.CData) {
+            writeCDATA(characters, start, count);
+        } else {
+            maybeStartText();
+            writeXmlEncoded(characters, start, count);
+        }
+        return this;
+    }
+
+    public SimpleMarkupWriter characters(CharSequence characters) throws IOException {
+        if (context == Context.CData) {
+            writeCDATA(characters);
+        } else {
+            maybeStartText();
+            writeXmlEncoded(characters);
+        }
+        return this;
+    }
+
+    private void maybeStartText() throws IOException {
+        if (context == Context.Outside) {
+            throw new IllegalStateException("Cannot write text, as there are no started elements.");
+        }
+        if (context == Context.StartTag) {
+            writeRaw(">");
+        }
+        context = Context.Text;
+    }
+
+    private void maybeFinishStartTag() throws IOException {
+        if (context == Context.StartTag) {
+            writeRaw(">");
+            context = Context.ElementContent;
+        }
+    }
+
+    public SimpleMarkupWriter startElement(String name) throws IOException {
+        if (!isValidXmlName(name)) {
+            throw new IllegalArgumentException(String.format("Invalid element name: '%s'", name));
+        }
+        if (context == Context.CData) {
+            throw new IllegalStateException("Cannot start element, as current CDATA node has not been closed.");
+        }
+        maybeFinishStartTag();
+        if (indent != null) {
+            writeRaw(SystemProperties.getLineSeparator());
+            for (int i = 0; i < elements.size(); i++) {
+                writeRaw(indent);
+            }
+        }
+        context = Context.StartTag;
+        elements.add(name);
+        writeRaw("<");
+        writeRaw(name);
+        return this;
+    }
+
+    public SimpleMarkupWriter endElement() throws IOException {
+        if (context == Context.Outside) {
+            throw new IllegalStateException("Cannot end element, as there are no started elements.");
+        }
+        if (context == Context.CData) {
+            throw new IllegalStateException("Cannot end element, as current CDATA node has not been closed.");
+        }
+        if (context == Context.StartTag) {
+            writeRaw("/>");
+            elements.removeLast();
+        } else {
+            if (context != Context.Text && indent != null) {
+                writeRaw(SystemProperties.getLineSeparator());
+                for (int i = 1; i < elements.size(); i++) {
+                    writeRaw(indent);
+                }
+            }
+            writeRaw("</");
+            writeRaw(elements.removeLast());
+            writeRaw(">");
+        }
+        if (elements.isEmpty()) {
+            if (indent != null) {
+                writeRaw(SystemProperties.getLineSeparator());
+            }
+            output.flush();
+            context = Context.Outside;
+        } else {
+            context = Context.ElementContent;
+        }
+        return this;
+    }
+
+    private void writeCDATA(char[] cdata, int offset, int count) throws IOException {
+        int end = offset + count;
+        for (int i = offset; i < end; i++) {
+            writeCDATA(cdata[i]);
+        }
+    }
+
+    private void writeCDATA(CharSequence cdata) throws IOException {
+        int len = cdata.length();
+        for (int i = 0; i < len; i++) {
+            writeCDATA(cdata.charAt(i));
+        }
+    }
+
+    private void writeCDATA(char ch) throws IOException {
+        if (needsCDATAEscaping(ch)) {
+            writeRaw("]]><![CDATA[>");
+        } else if (!isLegalCharacter(ch)) {
+            writeRaw('?');
+        } else if (isRestrictedCharacter(ch)) {
+            writeRaw("]]>");
+            writeCharacterReference(ch);
+            writeRaw("<![CDATA[");
+        } else {
+            writeRaw(ch);
+        }
+    }
+
+    private void writeCharacterReference(char ch) throws IOException {
+        writeRaw("&#x");
+        writeRaw(Integer.toHexString(ch));
+        writeRaw(";");
+    }
+
+    private boolean needsCDATAEscaping(char ch) {
+        switch (ch) {
+            case ']':
+                squareBrackets++;
+                return false;
+            case '>':
+                if (squareBrackets >= 2) {
+                    squareBrackets = 0;
+                    return true;
+                }
+                return false;
+            default:
+                squareBrackets = 0;
+                return false;
+        }
+    }
+
+    public SimpleMarkupWriter startCDATA() throws IOException {
+        if (context == Context.CData) {
+            throw new IllegalStateException("Cannot start CDATA node, as current CDATA node has not been closed.");
+        }
+        maybeFinishStartTag();
+        writeRaw("<![CDATA[");
+        context = Context.CData;
+        squareBrackets = 0;
+        return this;
+    }
+
+    public SimpleMarkupWriter endCDATA() throws IOException {
+        if (context != Context.CData) {
+            throw new IllegalStateException("Cannot end CDATA node, as not currently in a CDATA node.");
+        }
+        writeRaw("]]>");
+        context = Context.Text;
+        return this;
+    }
+
+    public SimpleMarkupWriter attribute(String name, String value) throws IOException {
+        if (!isValidXmlName(name)) {
+            throw new IllegalArgumentException(String.format("Invalid attribute name: '%s'", name));
+        }
+        if (context != Context.StartTag) {
+            throw new IllegalStateException("Cannot write attribute [" + name + ":" + value + "]. You should write start element first.");
+        }
+
+        writeRaw(" ");
+        writeRaw(name);
+        writeRaw("=\"");
+        writeXmlAttributeEncoded(value);
+        writeRaw("\"");
+        return this;
+    }
+
+    private static boolean isValidXmlName(String name) {
+        int length = name.length();
+        if (length == 0) {
+            return false;
+        }
+        char ch = name.charAt(0);
+        if (!isValidNameStartChar(ch)) {
+            return false;
+        }
+        for (int i = 1; i < length; i++) {
+            ch = name.charAt(i);
+            if (!isValidNameChar(ch)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isValidNameChar(char ch) {
+        if (isValidNameStartChar(ch)) {
+            return true;
+        }
+        if (ch >= '0' && ch <= '9') {
+            return true;
+        }
+        if (ch == '-' || ch == '.' || ch == '\u00b7') {
+            return true;
+        }
+        if (ch >= '\u0300' && ch <= '\u036f') {
+            return true;
+        }
+        if (ch >= '\u203f' && ch <= '\u2040') {
+            return true;
+        }
+        return false;
+    }
+
+    private static boolean isValidNameStartChar(char ch) {
+        if (ch >= 'A' && ch <= 'Z') {
+            return true;
+        }
+        if (ch >= 'a' && ch <= 'z') {
+            return true;
+        }
+        if (ch == ':' || ch == '_') {
+            return true;
+        }
+        if (ch >= '\u00c0' && ch <= '\u00d6') {
+            return true;
+        }
+        if (ch >= '\u00d8' && ch <= '\u00f6') {
+            return true;
+        }
+        if (ch >= '\u00f8' && ch <= '\u02ff') {
+            return true;
+        }
+        if (ch >= '\u0370' && ch <= '\u037d') {
+            return true;
+        }
+        if (ch >= '\u037f' && ch <= '\u1fff') {
+            return true;
+        }
+        if (ch >= '\u200c' && ch <= '\u200d') {
+            return true;
+        }
+        if (ch >= '\u2070' && ch <= '\u218f') {
+            return true;
+        }
+        if (ch >= '\u2c00' && ch <= '\u2fef') {
+            return true;
+        }
+        if (ch >= '\u3001' && ch <= '\ud7ff') {
+            return true;
+        }
+        if (ch >= '\uf900' && ch <= '\ufdcf') {
+            return true;
+        }
+        if (ch >= '\ufdf0' && ch <= '\ufffd') {
+            return true;
+        }
+        return false;
+    }
+
+    private void writeRaw(char c) throws IOException {
+        output.write(c);
+    }
+
+    private boolean isLegalCharacter(final char c) {
+        if (c == 0) {
+            return false;
+        } else if (c <= 0xD7FF) {
+            return true;
+        } else if (c < 0xE000) {
+            return false;
+        } else if (c <= 0xFFFD) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isRestrictedCharacter(char c) {
+        if (c == 0x9 || c == 0xA || c == 0xD || c == 0x85) {
+            return false;
+        } else if (c <= 0x1F) {
+            return true;
+        } else if (c < 0x7F) {
+            return false;
+        } else if (c <= 0x9F) {
+            return true;
+        }
+        return false;
+    }
+
+    protected void writeRaw(String message) throws IOException {
+        output.write(message);
+    }
+
+    private void writeXmlEncoded(char[] message, int offset, int count) throws IOException {
+        int end = offset + count;
+        for (int i = offset; i < end; i++) {
+            writeXmlEncoded(message[i]);
+        }
+    }
+
+    private void writeXmlAttributeEncoded(CharSequence message) throws IOException {
+        assert message != null;
+        int len = message.length();
+        for (int i = 0; i < len; i++) {
+            writeXmlAttributeEncoded(message.charAt(i));
+        }
+    }
+
+    private void writeXmlAttributeEncoded(char ch) throws IOException {
+        if (ch == 9) {
+            writeRaw("	");
+        } else if (ch == 10) {
+            writeRaw("
");
+        } else if (ch == 13) {
+            writeRaw("
");
+        } else {
+            writeXmlEncoded(ch);
+        }
+    }
+
+    private void writeXmlEncoded(CharSequence message) throws IOException {
+        assert message != null;
+        int len = message.length();
+        for (int i = 0; i < len; i++) {
+            writeXmlEncoded(message.charAt(i));
+        }
+    }
+
+    private void writeXmlEncoded(char ch) throws IOException {
+        if (ch == '<') {
+            writeRaw("<");
+        } else if (ch == '>') {
+            writeRaw(">");
+        } else if (ch == '&') {
+            writeRaw("&");
+        } else if (ch == '"') {
+            writeRaw(""");
+        } else if (!isLegalCharacter(ch)) {
+            writeRaw('?');
+        } else if (isRestrictedCharacter(ch)) {
+            writeCharacterReference(ch);
+        } else {
+            writeRaw(ch);
+        }
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java
index b56150b..73bf959 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/SimpleXmlWriter.java
@@ -16,411 +16,32 @@
 
 package org.gradle.api.internal.xml;
 
-import org.gradle.internal.SystemProperties;
-
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
-import java.util.LinkedList;
 
 /**
- * <p>A streaming XML writer. Encodes characters and CDATA. Provides only basic state validation, and some simple indentation.</p>
- *
- * <p>This class also is-a Writer, and any characters written to this writer will be encoded as appropriate.</p>
+ * <p>A streaming XML writer.</p>
  *
  * by Szczepan Faber, created at: 12/3/12
  */
-public class SimpleXmlWriter extends Writer {
-    private enum Context {
-        Outside, Text, CData, StartTag, ElementContent
-    }
-
-    private final Writer output;
-    private final LinkedList<String> elements = new LinkedList<String>();
-    private Context context = Context.Outside;
-    private int squareBrackets;
-    private final String indent;
+public class SimpleXmlWriter extends SimpleMarkupWriter {
 
     public SimpleXmlWriter(OutputStream output) throws IOException {
         this(output, null);
     }
 
     public SimpleXmlWriter(OutputStream output, String indent) throws IOException {
-        this.indent = indent;
-        this.output = new OutputStreamWriter(output, "UTF-8");
-        writeXmlDeclaration("UTF-8", "1.1");
-    }
-
-    private void writeXmlDeclaration(String encoding, String ver) throws IOException {
-        writeRaw("<?xml version=\"");
-        writeRaw(ver);
-        writeRaw("\" encoding=\"");
-        writeRaw(encoding);
-        writeRaw("\"?>");
-    }
-
-    @Override
-    public void write(char[] chars, int offset, int length) throws IOException {
-        characters(chars, offset, length);
-    }
-
-    @Override
-    public void flush() throws IOException {
-        output.flush();
-    }
-
-    @Override
-    public void close() throws IOException {
-        // Does nothing
-    }
-
-    public SimpleXmlWriter characters(char[] characters) throws IOException {
-        characters(characters, 0, characters.length);
-        return this;
-    }
-
-    public SimpleXmlWriter characters(char[] characters, int start, int count) throws IOException {
-        if (context == Context.CData) {
-            writeCDATA(characters, start, count);
-        } else {
-            maybeStartText();
-            writeXmlEncoded(characters, start, count);
-        }
-        return this;
-    }
-
-    public SimpleXmlWriter characters(CharSequence characters) throws IOException {
-        if (context == Context.CData) {
-            writeCDATA(characters);
-        } else {
-            maybeStartText();
-            writeXmlEncoded(characters);
-        }
-        return this;
-    }
-
-    private void maybeStartText() throws IOException {
-        if (context == Context.Outside) {
-            throw new IllegalStateException("Cannot write text, as there are no started elements.");
-        }
-        if (context == Context.StartTag) {
-            writeRaw(">");
-        }
-        context = Context.Text;
-    }
-
-    private void maybeFinishStartTag() throws IOException {
-        if (context == Context.StartTag) {
-            writeRaw(">");
-            context = Context.ElementContent;
-        }
-    }
-
-    public SimpleXmlWriter startElement(String name) throws IOException {
-        if (!isValidXmlName(name)) {
-            throw new IllegalArgumentException(String.format("Invalid element name: '%s'", name));
-        }
-        if (context == Context.CData) {
-            throw new IllegalStateException("Cannot start element, as current CDATA node has not been closed.");
-        }
-        maybeFinishStartTag();
-        if (indent != null) {
-            writeRaw(SystemProperties.getLineSeparator());
-            for (int i = 0; i < elements.size(); i++) {
-                writeRaw(indent);
-            }
-        }
-        context = Context.StartTag;
-        elements.add(name);
-        writeRaw("<");
-        writeRaw(name);
-        return this;
-    }
-
-    public SimpleXmlWriter endElement() throws IOException {
-        if (context == Context.Outside) {
-            throw new IllegalStateException("Cannot end element, as there are no started elements.");
-        }
-        if (context == Context.CData) {
-            throw new IllegalStateException("Cannot end element, as current CDATA node has not been closed.");
-        }
-        if (context == Context.StartTag) {
-            writeRaw("/>");
-            elements.removeLast();
-        } else {
-            if (context != Context.Text && indent != null) {
-                writeRaw(SystemProperties.getLineSeparator());
-                for (int i = 1; i < elements.size(); i++) {
-                    writeRaw(indent);
-                }
-            }
-            writeRaw("</");
-            writeRaw(elements.removeLast());
-            writeRaw(">");
-        }
-        if (elements.isEmpty()) {
-            if (indent != null) {
-                writeRaw(SystemProperties.getLineSeparator());
-            }
-            output.flush();
-            context = Context.Outside;
-        } else {
-            context = Context.ElementContent;
-        }
-        return this;
-    }
-
-    private void writeCDATA(char[] cdata, int offset, int count) throws IOException {
-        int end = offset + count;
-        for (int i = offset; i < end; i++) {
-            writeCDATA(cdata[i]);
-        }
-    }
-
-    private void writeCDATA(CharSequence cdata) throws IOException {
-        int len = cdata.length();
-        for (int i = 0; i < len; i++) {
-            writeCDATA(cdata.charAt(i));
-        }
-    }
-
-    private void writeCDATA(char ch) throws IOException {
-        if (needsCDATAEscaping(ch)) {
-            writeRaw("]]><![CDATA[>");
-        } else if (!isLegalCharacter(ch)) {
-            writeRaw('?');
-        } else if (isRestrictedCharacter(ch)) {
-            writeRaw("]]>&#x");
-            writeRaw(Integer.toHexString(ch));
-            writeRaw(";<![CDATA[");
-        } else {
-            writeRaw(ch);
-        }
-    }
-
-    private boolean needsCDATAEscaping(char ch) {
-        switch (ch) {
-            case ']':
-                squareBrackets++;
-                return false;
-            case '>':
-                if (squareBrackets >= 2) {
-                    squareBrackets = 0;
-                    return true;
-                }
-                return false;
-            default:
-                squareBrackets = 0;
-                return false;
-        }
-    }
-
-    public SimpleXmlWriter startCDATA() throws IOException {
-        if (context == Context.CData) {
-            throw new IllegalStateException("Cannot start CDATA node, as current CDATA node has not been closed.");
-        }
-        maybeFinishStartTag();
-        writeRaw("<![CDATA[");
-        context = Context.CData;
-        squareBrackets = 0;
-        return this;
-    }
-
-    public SimpleXmlWriter endCDATA() throws IOException {
-        if (context != Context.CData) {
-            throw new IllegalStateException("Cannot end CDATA node, as not currently in a CDATA node.");
-        }
-        writeRaw("]]>");
-        context = Context.Text;
-        return this;
-    }
-
-    public SimpleXmlWriter attribute(String name, String value) throws IOException {
-        if (!isValidXmlName(name)) {
-            throw new IllegalArgumentException(String.format("Invalid attribute name: '%s'", name));
-        }
-        if (context != Context.StartTag) {
-            throw new IllegalStateException("Cannot write attribute [" + name + ":" + value + "]. You should write start element first.");
-        }
-
-        writeRaw(" ");
-        writeRaw(name);
-        writeRaw("=\"");
-        writeXmlAttributeEncoded(value);
-        writeRaw("\"");
-        return this;
-    }
-
-    private static boolean isValidXmlName(String name) {
-        int length = name.length();
-        if (length == 0) {
-            return false;
-        }
-        char ch = name.charAt(0);
-        if (!isValidNameStartChar(ch)) {
-            return false;
-        }
-        for (int i = 1; i < length; i++) {
-            ch = name.charAt(i);
-            if (!isValidNameChar(ch)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private static boolean isValidNameChar(char ch) {
-        if (isValidNameStartChar(ch)) {
-            return true;
-        }
-        if (ch >= '0' && ch <= '9') {
-            return true;
-        }
-        if (ch == '-' || ch == '.' || ch == '\u00b7') {
-            return true;
-        }
-        if (ch >= '\u0300' && ch <= '\u036f') {
-            return true;
-        }
-        if (ch >= '\u203f' && ch <= '\u2040') {
-            return true;
-        }
-        return false;
-    }
-
-    private static boolean isValidNameStartChar(char ch) {
-        if (ch >= 'A' && ch <= 'Z') {
-            return true;
-        }
-        if (ch >= 'a' && ch <= 'z') {
-            return true;
-        }
-        if (ch == ':' || ch == '_') {
-            return true;
-        }
-        if (ch >= '\u00c0' && ch <= '\u00d6') {
-            return true;
-        }
-        if (ch >= '\u00d8' && ch <= '\u00f6') {
-            return true;
-        }
-        if (ch >= '\u00f8' && ch <= '\u02ff') {
-            return true;
-        }
-        if (ch >= '\u0370' && ch <= '\u037d') {
-            return true;
-        }
-        if (ch >= '\u037f' && ch <= '\u1fff') {
-            return true;
-        }
-        if (ch >= '\u200c' && ch <= '\u200d') {
-            return true;
-        }
-        if (ch >= '\u2070' && ch <= '\u218f') {
-            return true;
-        }
-        if (ch >= '\u2c00' && ch <= '\u2fef') {
-            return true;
-        }
-        if (ch >= '\u3001' && ch <= '\ud7ff') {
-            return true;
-        }
-        if (ch >= '\uf900' && ch <= '\ufdcf') {
-            return true;
-        }
-        if (ch >= '\ufdf0' && ch <= '\ufffd') {
-            return true;
-        }
-        return false;
-    }
-
-    private void writeRaw(char c) throws IOException {
-        output.write(c);
-    }
-
-    private boolean isLegalCharacter(final char c) {
-        if (c == 0) {
-            return false;
-        } else if (c <= 0xD7FF) {
-            return true;
-        } else if (c < 0xE000) {
-            return false;
-        } else if (c <= 0xFFFD) {
-            return true;
-        }
-        return false;
-    }
-
-    private boolean isRestrictedCharacter(char c) {
-        if (c == 0x9 || c == 0xA || c == 0xD || c == 0x85) {
-            return false;
-        } else if (c <= 0x1F) {
-            return true;
-        } else if (c < 0x7F) {
-            return false;
-        } else if (c <= 0x9F) {
-            return true;
-        }
-        return false;
-    }
-
-    private void writeRaw(String message) throws IOException {
-        output.write(message);
-    }
-
-    private void writeXmlEncoded(char[] message, int offset, int count) throws IOException {
-        int end = offset + count;
-        for (int i = offset; i < end; i++) {
-            writeXmlEncoded(message[i]);
-        }
-    }
-
-    private void writeXmlAttributeEncoded(CharSequence message) throws IOException {
-        assert message != null;
-        int len = message.length();
-        for (int i = 0; i < len; i++) {
-            writeXmlAttributeEncoded(message.charAt(i));
-        }
-    }
-
-    private void writeXmlAttributeEncoded(char ch) throws IOException {
-        if (ch == 9) {
-            writeRaw("	");
-        } else if (ch == 10) {
-            writeRaw("
");
-        } else if (ch == 13) {
-            writeRaw("
");
-        } else {
-            writeXmlEncoded(ch);
-        }
+        this(new OutputStreamWriter(output, "UTF-8"), indent, "UTF-8");
     }
 
-    private void writeXmlEncoded(CharSequence message) throws IOException {
-        assert message != null;
-        int len = message.length();
-        for (int i = 0; i < len; i++) {
-            writeXmlEncoded(message.charAt(i));
-        }
+    public SimpleXmlWriter(Writer writer, String indent, String encoding) throws IOException {
+        super(writer, indent);
+        writeXmlDeclaration(encoding);
     }
 
-    private void writeXmlEncoded(char ch) throws IOException {
-        if (ch == '<') {
-            writeRaw("<");
-        } else if (ch == '>') {
-            writeRaw(">");
-        } else if (ch == '&') {
-            writeRaw("&");
-        } else if (ch == '"') {
-            writeRaw(""");
-        } else if (!isLegalCharacter(ch)) {
-            writeRaw('?');
-        } else if (isRestrictedCharacter(ch)) {
-            writeRaw("&#x");
-            writeRaw(Integer.toHexString(ch));
-            writeRaw(";");
-        } else {
-            writeRaw(ch);
-        }
+    private void writeXmlDeclaration(String encoding) throws IOException {
+        writeRaw(String.format("<?xml version=\"1.1\" encoding=\"%s\"?>", encoding));
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java
index ac92473..9d5b767 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/internal/xml/XmlTransformer.java
@@ -62,7 +62,7 @@ public class XmlTransformer implements Transformer<String, String> {
     }
 
     public void transform(File destination, final String encoding, final Action<? super Writer> generator) {
-        IoActions.writeFile(destination, encoding, new Action<Writer>() {
+        IoActions.writeTextFile(destination, encoding, new Action<Writer>() {
             public void execute(Writer writer) {
                 transform(writer, encoding, generator);
             }
@@ -70,7 +70,7 @@ public class XmlTransformer implements Transformer<String, String> {
     }
 
     public void transform(File destination, final Action<? super Writer> generator) {
-        IoActions.writeFile(destination, new Action<Writer>() {
+        IoActions.writeTextFile(destination, new Action<Writer>() {
             public void execute(Writer writer) {
                 transform(writer, generator);
             }
@@ -86,7 +86,7 @@ public class XmlTransformer implements Transformer<String, String> {
     public void transform(Writer destination, String encoding, Action<? super Writer> generator) {
         StringWriter stringWriter = new StringWriter();
         generator.execute(stringWriter);
-        transform(stringWriter.toString(), destination, encoding);
+        doTransform(stringWriter.toString()).writeTo(destination, encoding);
     }
 
     public String transform(String original) {
@@ -97,10 +97,6 @@ public class XmlTransformer implements Transformer<String, String> {
         doTransform(original).writeTo(destination);
     }
 
-    public void transform(String original, Writer destination, String encoding) {
-        doTransform(original).writeTo(destination, encoding);
-    }
-
     public void transform(String original, OutputStream destination) {
         doTransform(original).writeTo(destination);
     }
@@ -113,6 +109,10 @@ public class XmlTransformer implements Transformer<String, String> {
         doTransform(original).writeTo(destination);
     }
 
+    public void transform(Node original, File destination) {
+        doTransform(original).writeTo(destination);
+    }
+
     public void transform(DomNode original, Writer destination) {
         doTransform(original).writeTo(destination);
     }
@@ -181,6 +181,19 @@ public class XmlTransformer implements Transformer<String, String> {
             doWriteTo(writer, encoding);
         }
 
+        public void writeTo(File file) {
+            try {
+                OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
+                try {
+                    writeTo(outputStream);
+                } finally {
+                    outputStream.close();
+                }
+            } catch (IOException e) {
+                throw UncheckedException.throwAsUncheckedException(e);
+            }
+        }
+
         public void writeTo(OutputStream stream) {
             try {
                 Writer writer = new OutputStreamWriter(stream, "UTF-8");
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/invocation/Gradle.java b/subprojects/core/src/main/groovy/org/gradle/api/invocation/Gradle.java
index da96142..2588fad 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/invocation/Gradle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/invocation/Gradle.java
@@ -27,43 +27,52 @@ import org.gradle.api.internal.HasInternalProtocol;
 import java.io.File;
 
 /**
- * <p>Represents an invocation of Gradle.</p>
+ * Represents an invocation of Gradle.
  *
  * <p>You can obtain a {@code Gradle} instance by calling {@link Project#getGradle()}.</p>
  */
 @HasInternalProtocol
 public interface Gradle {
     /**
-     * <p>Returns the current Gradle version.</p>
+     * Returns the current Gradle version.
      *
      * @return The Gradle version. Never returns null.
      */
     String getGradleVersion();
 
     /**
-     * <p>Returns the Gradle user home directory. This directory is used to cache downloaded resources.</p>
+     * Returns the Gradle user home directory.
+     *
+     * This directory is used to cache downloaded resources.
      *
      * @return The user home directory. Never returns null.
      */
     File getGradleUserHomeDir();
 
     /**
-     * <p>Returns the Gradle home directory, if any. This directory is the directory containing the Gradle distribution
-     * executing this build.</p>
+     * Returns the Gradle home directory, if any.
+     *
+     * This directory is the directory containing the Gradle distribution executing this build.
+     * <p>
+     * When using the “Gradle Daemon”, this may not be the same Gradle distribution that the build was started with.
+     * If an existing daemon process is running that is deemed compatible (e.g. has the desired JVM characteristics)
+     * then this daemon may be used instead of starting a new process and it may have been started from a different “gradle home”.
+     * However, it is guaranteed to be the same version of Gradle. For more information on the Gradle Daemon, please consult
+     * <a href="http://www.gradle.org/docs/current/userguide/gradle_daemon.html">the user guide</a>.
      *
      * @return The home directory. May return null.
      */
     File getGradleHomeDir();
 
     /**
-     * <p>Returns the parent build of this build, if any.</p>
+     * Returns the parent build of this build, if any.
      *
      * @return The parent build. May return null.
      */
     Gradle getParent();
 
     /**
-     * <p>Returns the root project of this build.</p>
+     * Returns the root project of this build.
      *
      * @return The root project. Never returns null.
      * @throws IllegalStateException When called before the root project is available.
@@ -71,7 +80,9 @@ public interface Gradle {
     Project getRootProject() throws IllegalStateException;
 
     /**
-     * Adds an action to execute against the root project of this build. If the root project is already available, the action
+     * Adds an action to execute against the root project of this build.
+     *
+     * If the root project is already available, the action
      * is executed immediately. Otherwise, the action is executed when the root project becomes available.
      *
      * @param action The action to execute.
@@ -79,7 +90,9 @@ public interface Gradle {
     void rootProject(Action<? super Project> action);
 
     /**
-     * Adds an action to execute against all projects of this build. The action is executed immediately against all projects which are
+     * Adds an action to execute against all projects of this build.
+     *
+     * The action is executed immediately against all projects which are
      * already available. It is also executed as subsequent projects are added to this build.
      *
      * @param action The action to execute.
@@ -87,7 +100,7 @@ public interface Gradle {
     void allprojects(Action<? super Project> action);
 
     /**
-     * <p>Returns the {@link TaskExecutionGraph} for this build.</p>
+     * Returns the {@link TaskExecutionGraph} for this build.
      *
      * @return The task graph. Never returns null.
      */
@@ -124,25 +137,28 @@ public interface Gradle {
     void beforeProject(Closure closure);
 
     /**
-     * Adds a closure to be called immediately after a project is evaluated. The project is passed to the closure as the
-     * first parameter. The project evaluation failure, if any, is passed as the second parameter. Both parameters are
-     * optional.
+     * Adds a closure to be called immediately after a project is evaluated.
+     *
+     * The project is passed to the closure as the first parameter. The project evaluation failure, if any,
+     * is passed as the second parameter. Both parameters are optional.
      *
      * @param closure The closure to execute.
      */
     void afterProject(Closure closure);
 
     /**
-     * Adds a closure to be called when the build is started. This {@code Gradle} instance is passed to the closure as
-     * the first parameter.
+     * Adds a closure to be called when the build is started.
+     *
+     * This {@code Gradle} instance is passed to the closure as the first parameter.
      *
      * @param closure The closure to execute.
      */
     void buildStarted(Closure closure);
 
     /**
-     * Adds a closure to be called when the build settings have been loaded and evaluated. The settings object is
-     * fully configured and is ready to use to load the build projects. The
+     * Adds a closure to be called when the build settings have been loaded and evaluated.
+     *
+     * The settings object is fully configured and is ready to use to load the build projects. The
      * {@link org.gradle.api.initialization.Settings} object is passed to the closure as a parameter.
      *
      * @param closure The closure to execute.
@@ -151,6 +167,7 @@ public interface Gradle {
 
     /**
      * Adds a closure to be called when the projects for the build have been created from the settings.
+     *
      * None of the projects have been evaluated. This {@code Gradle} instance is passed to the closure as a parameter.
      * <p>
      * An example of hooking into the projectsLoaded to configure buildscript classpath from the init script.
@@ -173,16 +190,19 @@ public interface Gradle {
     void projectsLoaded(Closure closure);
 
     /**
-     * Adds a closure to be called when all projects for the build have been evaluated. The project objects are fully
-     * configured and are ready to use to populate the task graph. This {@code Gradle} instance is passed to
-     * the closure as a parameter.
+     * Adds a closure to be called when all projects for the build have been evaluated.
+     *
+     * The project objects are fully configured and are ready to use to populate the task graph.
+     * This {@code Gradle} instance is passed to the closure as a parameter.
      *
      * @param closure The closure to execute.
      */
     void projectsEvaluated(Closure closure);
 
     /**
-     * Adds a closure to be called when the build is completed. All selected tasks have been executed.
+     * Adds a closure to be called when the build is completed.
+     *
+     * All selected tasks have been executed.
      * A {@link org.gradle.BuildResult} instance is passed to the closure as a parameter.
      *
      * @param closure The closure to execute.
@@ -190,8 +210,9 @@ public interface Gradle {
     void buildFinished(Closure closure);
 
     /**
-     * <p>Adds a {@link BuildListener} to this Build instance. The listener is notified of events which occur during the
-     * execution of the build.</p>
+     * Adds a {@link BuildListener} to this Build instance.
+     *
+     * The listener is notified of events which occur during the execution of the build.
      *
      * @param buildListener The listener to add.
      */
@@ -201,25 +222,15 @@ public interface Gradle {
      * Adds the given listener to this build. The listener may implement any of the given listener interfaces:
      *
      * <ul>
-     *
      * <li>{@link org.gradle.BuildListener}
-     *
      * <li>{@link org.gradle.api.execution.TaskExecutionGraphListener}
-     *
      * <li>{@link org.gradle.api.ProjectEvaluationListener}
-     *
      * <li>{@link org.gradle.api.execution.TaskExecutionListener}
-     *
      * <li>{@link org.gradle.api.execution.TaskActionListener}
-     *
      * <li>{@link org.gradle.api.logging.StandardOutputListener}
-     *
      * <li>{@link org.gradle.api.tasks.testing.TestListener}
-     *
      * <li>{@link org.gradle.api.tasks.testing.TestOutputListener}
-     *
      * <li>{@link org.gradle.api.artifacts.DependencyResolutionListener}
-     *
      * </ul>
      *
      * @param listener The listener to add. Does nothing if this listener has already been added.
@@ -234,8 +245,12 @@ public interface Gradle {
     public void removeListener(Object listener);
 
     /**
-     * Uses the given object as a logger. The logger object may implement any of the listener interfaces supported by
-     * {@link #addListener(Object)}. Each listener interface has exactly one associated logger. When you call this
+     * Uses the given object as a logger.
+     *
+     * The logger object may implement any of the listener interfaces supported by
+     * {@link #addListener(Object)}.
+     * <p>
+     * Each listener interface has exactly one associated logger. When you call this
      * method with a logger of a given listener type, the new logger will replace whichever logger is currently
      * associated with the listener type. This allows you to selectively replace the standard logging which Gradle
      * provides with your own implementation, for certain types of events.
@@ -245,7 +260,9 @@ public interface Gradle {
     public void useLogger(Object logger);
 
     /**
-     * Returns this {@code Gradle} instance. This method is useful in init scripts to explicitly access Gradle
+     * Returns this {@code Gradle} instance.
+     *
+     * This method is useful in init scripts to explicitly access Gradle
      * properties and methods. For example, using <code>gradle.parent</code> can express your intent better than using
      * <code>parent</code>. This property also allows you to access Gradle properties from a scope where the property
      * may be hidden, such as, for example, from a method or closure.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/DeferredConfigurable.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/DeferredConfigurable.java
new file mode 100644
index 0000000..dbb04ff
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/DeferredConfigurable.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins;
+
+import org.gradle.api.Incubating;
+
+import java.lang.annotation.*;
+
+
+/**
+ * Indicates that the annotated object is designed to be configured only once, and that changes to configuration inputs made after configuration should not be allowed.
+ */
+ at Incubating
+ at Inherited
+ at Documented
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+public @interface DeferredConfigurable {}
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java
index 7d345a2..0c8edbb 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ExtensionContainer.java
@@ -16,6 +16,8 @@
 
 package org.gradle.api.plugins;
 
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
 import org.gradle.api.UnknownDomainObjectException;
 
 /**
@@ -93,6 +95,15 @@ public interface ExtensionContainer {
     Object findByName(String name);
 
     /**
+     * Looks for the extension of the specified type and configures it with the supplied action.
+     * @param type extension type
+     * @param action the configure action
+     * @throws UnknownDomainObjectException if no exception is found.
+     */
+    @Incubating
+    <T> void configure(Class<T> type, Action<? super T> action);
+
+    /**
      * The extra properties extension in this extension container.
      *
      * This extension is always present in the container, with the name “ext”.
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.java b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.java
index f4399c3..8eab018 100755
--- a/subprojects/core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.java
@@ -36,7 +36,7 @@ public interface ObjectConfigurationAction {
      * Adds a script to use to configure the target objects. You can call this method multiple times, to use multiple
      * scripts. Scripts and plugins are applied in the order that they are added.
      *
-     * @param script The script. Evaluated as for {@link org.gradle.api.Project#file(Object)}. However, note that
+     * @param script The script. Evaluated as per {@link org.gradle.api.Project#file(Object)}. However, note that
      * a URL can also be used, allowing the script to be fetched using HTTP, for example.
      * @return this
      */
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceHandler.java b/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceHandler.java
index 3db24e9..511edcd 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceHandler.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/resources/ResourceHandler.java
@@ -23,17 +23,17 @@ public interface ResourceHandler {
 
     /**
      * Creates resource that points to a gzip compressed file at the given path.
-     * The path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * The path is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      *
-     * @param path The path evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @param path The path evaluated as per {@link org.gradle.api.Project#file(Object)}.
      */
     ReadableResource gzip(Object path);
 
     /**
      * Creates resource that points to a bzip2 compressed file at the given path.
-     * The path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * The path is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      *
-     * @param path The path evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @param path The path evaluated as per {@link org.gradle.api.Project#file(Object)}.
      */
     ReadableResource bzip2(Object path);
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Delete.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Delete.java
index d2a1177..531ec76 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Delete.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Delete.java
@@ -69,7 +69,7 @@ public class Delete extends ConventionTask {
     }
 
     /**
-     * Adds some files to be deleted by this task. The given targets are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * Adds some files to be deleted by this task. The given targets are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param targets Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
      */
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 2fe35aa..3203072 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
@@ -71,7 +71,7 @@ public class SourceTask extends ConventionTask implements PatternFilterable {
     }
 
     /**
-     * Sets the source for this task. The given source object is evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * Sets the source for this task. The given source object is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param source The source.
      */
@@ -81,7 +81,7 @@ public class SourceTask extends ConventionTask implements PatternFilterable {
     }
 
     /**
-     * Adds some source to this task. The given source objects will be evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * Adds some source to this task. The given source objects will be evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      *
      * @param sources The source to add
      * @return this
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java
index 7250e27..1575a70 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java
@@ -43,7 +43,7 @@ public interface TaskInputs {
     /**
      * Registers some input files for this task.
      *
-     * @param paths The input files. The given paths are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * @param paths The input files. The given paths are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      * @return this
      */
     TaskInputs files(Object... paths);
@@ -51,7 +51,7 @@ public interface TaskInputs {
     /**
      * Registers some input file for this task.
      *
-     * @param path The input file. The given path is evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * @param path The input file. The given path is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      * @return this
      */
     TaskInputs file(Object path);
@@ -60,7 +60,7 @@ public interface TaskInputs {
      * Registers an input directory hierarchy. All files found under the given directory are treated as input files for
      * this task.
      *
-     * @param dirPath The directory. The path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @param dirPath The directory. The path is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      * @return this
      */
     TaskInputs dir(Object dirPath);
@@ -113,7 +113,7 @@ public interface TaskInputs {
      * Registers some source files for this task. Note that source files are also considered input files, so calling this method implies
      * a call to {@link #files(Object...)}.
      *
-     * @param paths The paths. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * @param paths The paths. These are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      * @return this
      */
     TaskInputs source(Object... paths);
@@ -122,7 +122,7 @@ public interface TaskInputs {
      * Registers some source files for this task. Note that source files are also considered input files, so calling this method implies
      * a call to {@link #files(Object...)}.
      *
-     * @param path The path. This is evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * @param path The path. This is evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      * @return this
      */
     TaskInputs source(Object path);
@@ -131,7 +131,7 @@ public interface TaskInputs {
      * Registers a source directory for this task. All files under this directory are treated as source files for this task. Note that
      * source files are also considered input files, so calling this method implies a call to {@link #dir(Object)}.
      *
-     * @param path The path. This is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @param path The path. This is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      * @return this
      */
     TaskInputs sourceDir(Object path);
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskOutputs.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskOutputs.java
index 9561cd6..da76c6f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskOutputs.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/TaskOutputs.java
@@ -69,7 +69,7 @@ public interface TaskOutputs {
     /**
      * Registers some output files for this task.
      *
-     * @param paths The output files. The given paths are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * @param paths The output files. The given paths are evaluated as per {@link org.gradle.api.Project#files(Object...)}.
      * @return this
      */
     TaskOutputs files(Object... paths);
@@ -77,7 +77,7 @@ public interface TaskOutputs {
     /**
      * Registers some output file for this task.
      *
-     * @param path The output file. The given path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @param path The output file. The given path is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      * @return this
      */
     TaskOutputs file(Object path);
@@ -85,7 +85,7 @@ public interface TaskOutputs {
     /**
      * Registers an output directory for this task.
      *
-     * @param path The output directory. The given path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @param path The output directory. The given path is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      * @return this
      */
     TaskOutputs dir(Object path);
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 773a775..d9e086e 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,8 @@
 
 package org.gradle.api.tasks;
 
+import org.gradle.api.Nullable;
+
 /**
  * {@code TaskState} provides information about the execution state of a {@link org.gradle.api.Task}. You can obtain a
  * {@code TaskState} instance by calling {@link org.gradle.api.Task#getState()}.
@@ -33,6 +35,7 @@ public interface TaskState {
      *
      * @return The exception, or null if the task did not fail.
      */
+    @Nullable
     Throwable getFailure();
 
     /**
@@ -61,5 +64,6 @@ public interface TaskState {
      *
      * @return the message. returns null if the task was not skipped.
      */
+    @Nullable
     String getSkipMessage();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
index 7e12fad..926975c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/Upload.java
@@ -18,21 +18,19 @@ package org.gradle.api.tasks;
 
 import groovy.lang.Closure;
 import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.Transformer;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.Module;
 import org.gradle.api.artifacts.PublishException;
 import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.artifacts.repositories.ArtifactRepository;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.Transformers;
 import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
 import org.gradle.api.internal.artifacts.ArtifactPublisher;
 import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
 import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
 import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
-import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 import org.gradle.util.ConfigureUtil;
 
 import javax.inject.Inject;
@@ -40,7 +38,6 @@ import java.io.File;
 import java.util.List;
 import java.util.Set;
 
-import static org.gradle.api.internal.Cast.cast;
 import static org.gradle.util.CollectionUtils.collect;
 
 /**
@@ -81,12 +78,8 @@ public class Upload extends ConventionTask {
                 ivyModuleDescriptorWriter.write(moduleDescriptor, descriptorDestination);
             }
 
-            List<DependencyResolver> resolvers = collect(repositories, new Transformer<DependencyResolver, ArtifactRepository>() {
-                public DependencyResolver transform(ArtifactRepository repository) {
-                    return cast(ArtifactRepositoryInternal.class, repository).createResolver();
-                }
-            });
-            artifactPublisher.publish(resolvers,  module, configurationsToPublish, descriptorDestination);
+            List<PublicationAwareRepository> publishRepositories = collect(repositories, Transformers.cast(PublicationAwareRepository.class));
+            artifactPublisher.publish(publishRepositories,  module, configurationsToPublish, descriptorDestination);
         } catch (Exception e) {
             throw new PublishException(String.format("Could not publish configuration '%s'", configuration.getName()), e);
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
index 877784b..fd88ab9 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
@@ -161,7 +161,7 @@ public abstract class AbstractArchiveTask extends AbstractCopyTask {
 
     /**
      * Specifies the destination directory *inside* the archive for the files.
-     * The destination is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * The destination is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      * Don't mix it up with {@link #getDestinationDir()} which specifies the output directory for the archive.
      *
      * @param destPath destination directory *inside* the archive for the files
@@ -174,7 +174,7 @@ public abstract class AbstractArchiveTask extends AbstractCopyTask {
 
     /**
      * Creates and configures a child {@code CopySpec} with a destination directory *inside* the archive for the files.
-     * The destination is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * The destination is evaluated as per {@link org.gradle.api.Project#file(Object)}.
      * Don't mix it up with {@link #getDestinationDir()} which specifies the output directory for the archive.
      *
      * @param destPath destination directory *inside* the archive for the files
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
index e65b9cd..f8af439 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
@@ -17,12 +17,12 @@
 package org.gradle.api.tasks.bundling;
 
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.archive.TarCopyAction;
 import org.gradle.api.internal.file.archive.TarCopySpecVisitor;
 import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
-import org.gradle.api.internal.file.archive.compression.Compressor;
+import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
 import org.gradle.api.internal.file.archive.compression.GzipArchiver;
 import org.gradle.api.internal.file.archive.compression.SimpleCompressor;
+import org.gradle.api.internal.file.copy.ArchiveCopyAction;
 import org.gradle.api.internal.file.copy.CopyActionImpl;
 
 import java.io.File;
@@ -35,10 +35,9 @@ import java.util.concurrent.Callable;
  */
 public class Tar extends AbstractArchiveTask {
     private final CopyActionImpl action;
-    private Compression compression;
+    private Compression compression = Compression.NONE;
 
     public Tar() {
-        setCompression(Compression.NONE);
         action = new TarCopyActionImpl(getServices().get(FileResolver.class));
         getConventionMapping().map("extension", new Callable<Object>(){
             public Object call() throws Exception {
@@ -69,7 +68,7 @@ public class Tar extends AbstractArchiveTask {
         this.compression = compression;
     }
 
-    private class TarCopyActionImpl extends CopyActionImpl implements TarCopyAction {
+    private class TarCopyActionImpl extends CopyActionImpl implements ArchiveCopyAction  {
         public TarCopyActionImpl(FileResolver fileResolver) {
             super(fileResolver, new TarCopySpecVisitor());
         }
@@ -78,7 +77,7 @@ public class Tar extends AbstractArchiveTask {
             return Tar.this.getArchivePath();
         }
 
-        public Compressor getCompressor() {
+        public ArchiveOutputStreamFactory getCompressor() {
             switch(compression) {
                 case BZIP2: return Bzip2Archiver.getCompressor();
                 case GZIP:  return GzipArchiver.getCompressor();
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java
index b5a5216..e7c515f 100644
--- a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java
@@ -16,37 +16,77 @@
 package org.gradle.api.tasks.bundling;
 
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.archive.ZipCopyAction;
 import org.gradle.api.internal.file.archive.ZipCopySpecVisitor;
-import org.gradle.api.internal.file.copy.ArchiveCopyAction;
 import org.gradle.api.internal.file.copy.CopyActionImpl;
+import org.gradle.api.internal.file.copy.ZipDeflatedCompressor;
+import org.gradle.api.internal.file.copy.ZipCompressor;
+import org.gradle.api.internal.file.copy.ZipStoredCompressor;
 
 import java.io.File;
 
 /**
  * Assembles a ZIP archive.
  * 
+ * The default is to compress the contents of the zip.
+ * 
  * @author Hans Dockter
  */
 public class Zip extends AbstractArchiveTask {
     public static final String ZIP_EXTENSION = "zip";
-    private final CopyActionImpl action;
+    private final ZipCopyActionImpl action;
+    private ZipEntryCompression entryCompression = ZipEntryCompression.DEFLATED;
 
     public Zip() {
         setExtension(ZIP_EXTENSION);
-        action = new ZipCopyAction(getServices().get(FileResolver.class));
+        action = new ZipCopyActionImpl(getServices().get(FileResolver.class));
+    }
+
+    /**
+     * Returns the compression level of the entries of the archive. If set to {@link ZipEntryCompression#DEFLATED} (the default), each entry is
+     * compressed using the DEFLATE algorithm. If set to {@link ZipEntryCompression#STORED} the entries of the archive are left uncompressed.
+     *
+     * @return the compression level of the archive contents.
+     */
+    public ZipEntryCompression getEntryCompression() {
+        return entryCompression;
+    }
+    
+    /**
+     * Sets the compression level of the entries of the archive. If set to {@link ZipEntryCompression#DEFLATED} (the default), each entry is
+     * compressed using the DEFLATE algorithm. If set to {@link ZipEntryCompression#STORED} the entries of the archive are left uncompressed.
+     *
+     * @param entryCompression {@code STORED} or {@code DEFLATED}
+     */
+    public void setEntryCompression(ZipEntryCompression entryCompression) {
+        this.entryCompression = entryCompression;
     }
 
-    protected CopyActionImpl getCopyAction() {
+    protected ZipCopyActionImpl getCopyAction() {
         return action;
     }
 
-    private class ZipCopyAction extends CopyActionImpl implements ArchiveCopyAction {
-        public ZipCopyAction(FileResolver fileResolver) {
+    /**
+     * Zip compress action implementation.
+     */
+    protected class ZipCopyActionImpl extends CopyActionImpl implements ZipCopyAction {
+        public ZipCopyActionImpl(FileResolver fileResolver) {
             super(fileResolver, new ZipCopySpecVisitor());
         }
 
         public File getArchivePath() {
             return Zip.this.getArchivePath();
         }
+
+        public ZipCompressor getCompressor() {
+            switch(entryCompression) {
+                case DEFLATED:
+                    return ZipDeflatedCompressor.INSTANCE;
+                case STORED:
+                    return ZipStoredCompressor.INSTANCE;
+                default:
+                    throw new IllegalArgumentException(String.format("Unknown Compression type %s", entryCompression));
+            }
+        }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/ZipEntryCompression.java b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/ZipEntryCompression.java
new file mode 100644
index 0000000..366fd84
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/api/tasks/bundling/ZipEntryCompression.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks.bundling;
+
+/**
+ * Specifies the compression level of an archives contents.
+ */
+public enum ZipEntryCompression {
+    /** Contents are not compressed */
+    STORED,
+
+    /** Contents are compressed using the 'deflate' algorithm */
+    DEFLATED
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
index 454c69b..a6e02fa 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/DefaultBuildConfigurer.java
@@ -16,28 +16,26 @@
 package org.gradle.configuration;
 
 import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.internal.Actions;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.logging.Logger;
-import org.gradle.api.logging.Logging;
+import org.gradle.util.SingleMessageLogger;
 
 public class DefaultBuildConfigurer implements BuildConfigurer {
-    private Action<Project> actions;
-    private final static Logger LOG = Logging.getLogger(DefaultBuildConfigurer.class);
-
-    public DefaultBuildConfigurer(Action<? super ProjectInternal>... actions) {
-        this.actions = Actions.castBefore(ProjectInternal.class, Actions.composite(actions));
-    }
 
     public void configure(GradleInternal gradle) {
+        gradle.addProjectEvaluationListener(new ImplicitTasksConfigurer());
+        gradle.addProjectEvaluationListener(new ProjectDependencies2TaskResolver());
         if (gradle.getStartParameter().isConfigureOnDemand()) {
-            LOG.lifecycle("Thanks for using the incubating configuration-on-demand mode. Enjoy it and let us know how it works for you.");
-            gradle.addProjectEvaluationListener(new ImplicitTasksConfigurer());
+            SingleMessageLogger.informAboutIncubating("Configuration on demand");
             gradle.getRootProject().evaluate();
         } else {
-            gradle.getRootProject().allprojects(actions);
+            gradle.getRootProject().allprojects((Action) new ConfigureProject());
+        }
+    }
+
+    static class ConfigureProject implements Action<ProjectInternal> {
+        public void execute(ProjectInternal projectInternal) {
+            projectInternal.evaluate();
         }
     }
 }
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
index 34586da..cab2353 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ImplicitTasksConfigurer.java
@@ -15,13 +15,12 @@
  */
 package org.gradle.configuration;
 
-import org.gradle.api.Action;
 import org.gradle.api.Project;
 import org.gradle.api.ProjectEvaluationListener;
 import org.gradle.api.ProjectState;
 
 //This one should go away once we complete the auto-apply plugins
-public class ImplicitTasksConfigurer implements Action<Project>, ProjectEvaluationListener {
+public class ImplicitTasksConfigurer implements ProjectEvaluationListener {
     public static final String HELP_GROUP = "help";
     public static final String HELP_TASK = "help";
     public static final String PROJECTS_TASK = "projects";
@@ -33,10 +32,6 @@ public class ImplicitTasksConfigurer implements Action<Project>, ProjectEvaluati
     public void beforeEvaluate(Project project) {}
 
     public void afterEvaluate(Project project, ProjectState state) {
-        execute(project);
-    }
-
-    public void execute(Project project) {
         project.getPlugins().apply("help-tasks");
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java
index e2bb2ff..5d0ee20 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/LifecycleProjectEvaluator.java
@@ -32,7 +32,8 @@ public class LifecycleProjectEvaluator implements ProjectEvaluator {
     }
 
     public void evaluate(ProjectInternal project, ProjectStateInternal state) {
-        if (state.getExecuted()) {
+        //TODO this is one of the places to look into thread safety when we implement parallel configuration
+        if (state.getExecuted() || state.getExecuting()) {
             return;
         }
 
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java
index 0ca6137..9ab5cd5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java
+++ b/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java
@@ -16,8 +16,9 @@
 
 package org.gradle.configuration;
 
-import org.gradle.api.Action;
 import org.gradle.api.Project;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.ProjectState;
 import org.gradle.api.Task;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,10 +26,12 @@ import org.slf4j.LoggerFactory;
 /**
  * @author Hans Dockter
  */
-public class ProjectDependencies2TaskResolver implements Action<Project> {
+public class ProjectDependencies2TaskResolver implements ProjectEvaluationListener {
     private static Logger logger = LoggerFactory.getLogger(ProjectDependencies2TaskResolver.class);
 
-    public void execute(Project project) {
+    public void beforeEvaluate(Project project) {}
+
+    public void afterEvaluate(Project project, ProjectState state) {
         for (Project dependsOnProject : project.getDependsOnProjects()) {
             logger.debug("Checking task dependencies for project: {} dependsOn: {}", project, dependsOnProject);
             for (Task task : project.getTasks()) {
diff --git a/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectEvaluationConfigurer.java b/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectEvaluationConfigurer.java
deleted file mode 100644
index 2822298..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/configuration/ProjectEvaluationConfigurer.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.configuration;
-
-import org.gradle.api.Action;
-import org.gradle.api.internal.project.ProjectInternal;
-
-public class ProjectEvaluationConfigurer implements Action<ProjectInternal> {
-    public void execute(ProjectInternal projectInternal) {
-        projectInternal.evaluate();
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/OnlyWhenConfigureOnDemand.java b/subprojects/core/src/main/groovy/org/gradle/execution/OnlyWhenConfigureOnDemand.java
deleted file mode 100644
index d6b7e16..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/execution/OnlyWhenConfigureOnDemand.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.execution;
-
-/**
- * Action that only executes the delegate when configure on demand mode is 'on'
- *
- * by Szczepan Faber, created at: 1/8/13
- */
-public class OnlyWhenConfigureOnDemand implements BuildConfigurationAction {
-
-    private final BuildConfigurationAction delegate;
-
-    public OnlyWhenConfigureOnDemand(BuildConfigurationAction delegate) {
-        this.delegate = delegate;
-    }
-
-    public void configure(BuildExecutionContext context) {
-        if (context.getGradle().getStartParameter().isConfigureOnDemand()) {
-            delegate.configure(context);
-        } else {
-            context.proceed();
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java
index 8fdde16..d036dec 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectEvaluatingAction.java
@@ -16,6 +16,7 @@
 
 package org.gradle.execution;
 
+import org.gradle.StartParameter;
 import org.gradle.api.internal.project.ProjectInternal;
 
 import java.util.List;
@@ -29,15 +30,23 @@ public class ProjectEvaluatingAction implements BuildConfigurationAction {
 
     private final TaskPathProjectEvaluator evaluator;
 
+    public ProjectEvaluatingAction() {
+        this(new TaskPathProjectEvaluator());
+    }
+
     public ProjectEvaluatingAction(TaskPathProjectEvaluator evaluator) {
         this.evaluator = evaluator;
     }
 
     public void configure(BuildExecutionContext context) {
-        List<String> taskNames = context.getGradle().getStartParameter().getTaskNames();
+        StartParameter param = context.getGradle().getStartParameter();
+        List<String> taskNames = param.getTaskNames();
         ProjectInternal project = context.getGradle().getDefaultProject();
 
-        project.evaluate();
+        if (param.getTaskNames().isEmpty()) {
+            //so that we don't miss out default tasks
+            project.evaluate();
+        }
 
         for (String path : taskNames) {
             evaluator.evaluateByPath(project, path);
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectFinderByTaskPath.java b/subprojects/core/src/main/groovy/org/gradle/execution/ProjectFinderByTaskPath.java
deleted file mode 100644
index 8e9201d..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/execution/ProjectFinderByTaskPath.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.execution;
-
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Project;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.util.NameMatcher;
-
-import java.util.Map;
-
-/**
- * by Szczepan Faber, created at: 1/3/13
- */
-public class ProjectFinderByTaskPath {
-
-    public ProjectInternal findProject(String taskPath, ProjectInternal startFrom) {
-        if (!taskPath.contains(Project.PATH_SEPARATOR)) {
-            throw new IllegalArgumentException("I can only find tasks based on a task path (e.g. containing ':'). However, '" + taskPath + "' was passed.");
-        }
-        String projectPath = StringUtils.substringBeforeLast(taskPath, Project.PATH_SEPARATOR);
-        projectPath = projectPath.length() == 0 ? Project.PATH_SEPARATOR : projectPath;
-        return findProjectNow(projectPath, startFrom);
-    }
-
-    private static ProjectInternal findProjectNow(String path, ProjectInternal startFrom) {
-        if (path.equals(Project.PATH_SEPARATOR)) {
-            return startFrom.getRootProject();
-        }
-        Project current = startFrom;
-        if (path.startsWith(Project.PATH_SEPARATOR)) {
-            current = current.getRootProject();
-            path = path.substring(1);
-        }
-        for (String pattern : path.split(Project.PATH_SEPARATOR)) {
-            Map<String, Project> children = current.getChildProjects();
-
-            NameMatcher matcher = new NameMatcher();
-            Project child = matcher.find(pattern, children);
-            if (child != null) {
-                current = child;
-                continue;
-            }
-
-            throw new ProjectLookupException(matcher.formatErrorMessage("project", current));
-        }
-
-        return (ProjectInternal) current;
-    }
-
-    public static class ProjectLookupException extends InvalidUserDataException {
-        public ProjectLookupException(String message) {
-            super(message);
-        }
-    }
-}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java
index e4808ad..45139f3 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskPathProjectEvaluator.java
@@ -18,26 +18,28 @@ package org.gradle.execution;
 
 import org.gradle.api.Project;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.execution.taskpath.ResolvedTaskPath;
+import org.gradle.execution.taskpath.TaskPathResolver;
 
 /**
  * by Szczepan Faber, created at: 1/8/13
  */
 public class TaskPathProjectEvaluator {
 
-    private final ProjectFinderByTaskPath finder;
+    private final TaskPathResolver taskPathResolver;
 
     public TaskPathProjectEvaluator() {
-        this(new ProjectFinderByTaskPath());
+        this(new TaskPathResolver());
     }
 
-    TaskPathProjectEvaluator(ProjectFinderByTaskPath finder) {
-        this.finder = finder;
+    TaskPathProjectEvaluator(TaskPathResolver taskPathResolver) {
+        this.taskPathResolver = taskPathResolver;
     }
 
     public void evaluateByPath(ProjectInternal project, String path) {
-        if (path.contains(Project.PATH_SEPARATOR)) {
-            ProjectInternal foundProject = finder.findProject(path, project);
-            foundProject.evaluate();
+        ResolvedTaskPath taskPath = taskPathResolver.resolvePath(path, project);
+        if (taskPath.isQualified()) {
+            taskPath.getProject().evaluate();
         } else {
             project.evaluate();
             for (Project sub : project.getSubprojects()) {
@@ -45,4 +47,4 @@ public class TaskPathProjectEvaluator {
             }
         }
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
index c6d5579..56a186b 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/TaskSelector.java
@@ -16,11 +16,11 @@
 package org.gradle.execution;
 
 import com.google.common.collect.SetMultimap;
-import org.apache.commons.lang.StringUtils;
-import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.execution.taskpath.ResolvedTaskPath;
+import org.gradle.execution.taskpath.TaskPathResolver;
 import org.gradle.util.NameMatcher;
 
 import java.util.Set;
@@ -28,7 +28,7 @@ import java.util.Set;
 public class TaskSelector {
     private final TaskNameResolver taskNameResolver;
     private final GradleInternal gradle;
-    private final ProjectFinderByTaskPath projectFinder = new ProjectFinderByTaskPath();
+    private final TaskPathResolver taskPathResolver = new TaskPathResolver();
 
     public TaskSelector(GradleInternal gradle) {
         this(gradle, new TaskNameResolver());
@@ -41,36 +41,27 @@ public class TaskSelector {
 
     public TaskSelection getSelection(String path) {
         SetMultimap<String, Task> tasksByName;
-        String baseName;
-        String prefix;
-
         ProjectInternal project = gradle.getDefaultProject();
+        ResolvedTaskPath taskPath = taskPathResolver.resolvePath(path, project);
 
-        if (path.contains(Project.PATH_SEPARATOR)) {
-            project = projectFinder.findProject(path, project);
-            baseName = StringUtils.substringAfterLast(path, Project.PATH_SEPARATOR);
-            prefix = project.getPath() + Project.PATH_SEPARATOR;
-
-            tasksByName = taskNameResolver.select(baseName, project);
+        if (taskPath.isQualified()) {
+            tasksByName = taskNameResolver.select(taskPath.getTaskName(), taskPath.getProject());
         } else {
-            baseName = path;
-            prefix = "";
-
             tasksByName = taskNameResolver.selectAll(path, project);
         }
 
-        Set<Task> tasks = tasksByName.get(baseName);
+        Set<Task> tasks = tasksByName.get(taskPath.getTaskName());
         if (!tasks.isEmpty()) {
             // An exact match
             return new TaskSelection(path, tasks);
         }
 
         NameMatcher matcher = new NameMatcher();
-        String actualName = matcher.find(baseName, tasksByName.keySet());
+        String actualName = matcher.find(taskPath.getTaskName(), tasksByName.keySet());
 
         if (actualName != null) {
             // A partial match
-            return new TaskSelection(prefix + actualName, tasksByName.get(actualName));
+            return new TaskSelection(taskPath.getPrefix() + actualName, tasksByName.get(actualName));
         }
 
         throw new TaskSelectionException(matcher.formatErrorMessage("task", project));
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java
index 1c02a73..912c5da 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlan.java
@@ -43,6 +43,7 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
     private Spec<? super Task> filter = Specs.satisfyAll();
 
     private TaskFailureHandler failureHandler = new RethrowingFailureHandler();
+    private final List<String> runningProjects = new ArrayList<String>();
 
     public void addToTaskGraph(Collection<? extends Task> tasks) {
         List<Task> queue = new ArrayList<Task>(tasks);
@@ -98,6 +99,7 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
         try {
             executionPlan.clear();
             failures.clear();
+            runningProjects.clear();
         } finally {
             lock.unlock();
         }
@@ -148,7 +150,44 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
         } finally {
             lock.unlock();
         }
+    }
 
+    public TaskInfo getTaskToExecute() {
+        lock.lock();
+        try {
+            while(true) {
+                TaskInfo nextMatching = null;
+                boolean allTasksComplete = true;
+                for (TaskInfo taskInfo : executionPlan.values()) {
+                    allTasksComplete = allTasksComplete && taskInfo.isComplete();
+                    if (taskInfo.isReady() && taskInfo.allDependenciesComplete() && !runningProjects.contains(taskInfo.getTask().getProject().getPath())) {
+                        nextMatching = taskInfo;
+                        break;
+                    }
+                }
+                if (allTasksComplete) {
+                    return null;
+                }
+                if (nextMatching == null) {
+                    try {
+                        condition.await();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                } else {
+                    if (nextMatching.allDependenciesSuccessful()) {
+                        nextMatching.startExecution();
+                        runningProjects.add(nextMatching.getTask().getProject().getPath());
+                        return nextMatching;
+                    } else {
+                        nextMatching.skipExecution();
+                        condition.signalAll();
+                    }
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
     }
 
     private TaskInfo getNextReadyAndMatching(Spec<TaskInfo> criteria) {
@@ -168,6 +207,7 @@ class DefaultTaskExecutionPlan implements TaskExecutionPlan {
             }
 
             taskInfo.finishExecution();
+            runningProjects.remove(taskInfo.getTask().getProject().getPath());
             condition.signalAll();
         } finally {
             lock.unlock();
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 419c848..29af40c 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
@@ -20,17 +20,18 @@ import org.gradle.api.Project;
 import org.gradle.api.Task;
 import org.gradle.api.execution.TaskExecutionListener;
 import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess;
-import org.gradle.api.specs.Spec;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
 
 import java.util.ArrayList;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
+import static org.gradle.util.Clock.prettyTime;
+
 class ParallelTaskPlanExecutor extends DefaultTaskPlanExecutor {
-    private static final Logger LOGGER = LoggerFactory.getLogger(ParallelTaskPlanExecutor.class);
+    private static final Logger LOGGER = Logging.getLogger(ParallelTaskPlanExecutor.class);
 
     private final List<Thread> executorThreads = new ArrayList<Thread>();
     private final TaskArtifactStateCacheAccess stateCacheAccess;
@@ -60,14 +61,10 @@ class ParallelTaskPlanExecutor extends DefaultTaskPlanExecutor {
     private void doProcess(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
         List<Project> projects = getAllProjects(taskExecutionPlan);
         int numExecutors = Math.min(executorCount, projects.size());
+        numExecutors = Math.min(numExecutors, 4);
 
         for (int i = 0; i < numExecutors; i++) {
             TaskExecutorWorker worker = new TaskExecutorWorker(taskExecutionPlan, taskListener);
-
-            for (int j = i; j < projects.size(); j += numExecutors) {
-                worker.addProject(projects.get(j));
-            }
-
             executorThreads.add(new Thread(worker));
         }
 
@@ -88,8 +85,8 @@ class ParallelTaskPlanExecutor extends DefaultTaskPlanExecutor {
     private class TaskExecutorWorker implements Runnable {
         private final TaskExecutionPlan taskExecutionPlan;
         private final TaskExecutionListener taskListener;
-
-        private final List<Project> projects = new ArrayList<Project>();
+        private long busyMs;
+        private long waitedForCacheMs;
 
         private TaskExecutorWorker(TaskExecutionPlan taskExecutionPlan, TaskExecutionListener taskListener) {
             this.taskExecutionPlan = taskExecutionPlan;
@@ -97,35 +94,28 @@ class ParallelTaskPlanExecutor extends DefaultTaskPlanExecutor {
         }
 
         public void run() {
-            TaskInfo taskInfo;
-            while ((taskInfo = taskExecutionPlan.getTaskToExecute(getTaskSpec())) != null) {
-                executeTaskWithCacheLock(taskInfo);
+            long start = System.currentTimeMillis();
+            TaskInfo task;
+            while((task = taskExecutionPlan.getTaskToExecute()) != null) {
+                executeTaskWithCacheLock(task);
             }
-
-            LOGGER.info(Thread.currentThread() + " stopping");
+            long total = System.currentTimeMillis() - start;
+            LOGGER.info("Parallel worker [{}] stopped, busy: {}, idle: {}, waited for cache: {}", Thread.currentThread(), prettyTime(busyMs), prettyTime(total - busyMs), prettyTime(waitedForCacheMs));
         }
 
         private void executeTaskWithCacheLock(final TaskInfo taskInfo) {
             final String taskPath = taskInfo.getTask().getPath();
             LOGGER.info(taskPath + " (" + Thread.currentThread() + " - start");
+            final long start = System.currentTimeMillis();
             stateCacheAccess.useCache("Executing " + taskPath, new Runnable() {
                 public void run() {
+                    waitedForCacheMs += System.currentTimeMillis() - start;
                     processTask(taskInfo, taskExecutionPlan, taskListener);
                 }
             });
-            LOGGER.info(taskPath + " (" + Thread.currentThread() + ") - complete");
-        }
-
-        public void addProject(Project project) {
-            projects.add(project);
-        }
+            busyMs += System.currentTimeMillis() - start;
 
-        private Spec<TaskInfo> getTaskSpec() {
-            return new Spec<TaskInfo>() {
-                public boolean isSatisfiedBy(TaskInfo element) {
-                    return projects.contains(element.getTask().getProject());
-                }
-            };
+            LOGGER.info(taskPath + " (" + Thread.currentThread() + ") - complete");
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.java
index 3119e02..7020ba2 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskExecutionPlan.java
@@ -49,4 +49,7 @@ public interface TaskExecutionPlan {
      * @return The list of all available tasks. This includes tasks that have not yet been executed, as well as tasks that have been processed.
      */
     List<Task> getTasks();
+
+    //TODO SF this should replace completely getTaskToExecute(), inherit and expand existing unit test coverage
+    TaskInfo getTaskToExecute();
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.java
index 201350b..e988f2c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskgraph/TaskPlanExecutorFactory.java
@@ -16,34 +16,24 @@
 
 package org.gradle.execution.taskgraph;
 
-import org.gradle.api.internal.DocumentationRegistry;
 import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess;
 import org.gradle.internal.Factory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.gradle.util.SingleMessageLogger;
 
 public class TaskPlanExecutorFactory implements Factory<TaskPlanExecutor> {
 
-    private static final Logger LOGGER = LoggerFactory.getLogger(TaskPlanExecutorFactory.class);
-
     private final TaskArtifactStateCacheAccess taskArtifactStateCacheAccess;
     private final int parallelThreads;
-    private final DocumentationRegistry documentationRegistry;
 
-    public TaskPlanExecutorFactory(TaskArtifactStateCacheAccess taskArtifactStateCacheAccess, int parallelThreads, DocumentationRegistry documentationRegistry) {
+    public TaskPlanExecutorFactory(TaskArtifactStateCacheAccess taskArtifactStateCacheAccess, int parallelThreads) {
         this.taskArtifactStateCacheAccess = taskArtifactStateCacheAccess;
         this.parallelThreads = parallelThreads;
-        this.documentationRegistry = documentationRegistry;
     }
 
     public TaskPlanExecutor create() {
         ExecutionOptions options = new ExecutionOptions(parallelThreads);
         if (options.executeProjectsInParallel()) {
-            String parallelWarningMessage = String.format(
-                    "Parallel project execution is an \"incubating\" feature (%s). Many builds will not run correctly with this option.",
-                    documentationRegistry.getFeatureLifecycle()
-            );
-            LOGGER.warn(parallelWarningMessage);
+            SingleMessageLogger.informAboutIncubating("Parallel project execution");
             return new ParallelTaskPlanExecutor(taskArtifactStateCacheAccess, options.numberOfParallelThreads());
         }
         return new DefaultTaskPlanExecutor();
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPath.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPath.java
new file mode 100644
index 0000000..d6e43ff
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPath.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution.taskpath;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.NameMatcher;
+
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 1/3/13
+ */
+public class ProjectFinderByTaskPath {
+
+    public ProjectInternal findProject(String projectPath, ProjectInternal startFrom) {
+        if (projectPath.equals(Project.PATH_SEPARATOR)) {
+            return startFrom.getRootProject();
+        }
+        Project current = startFrom;
+        if (projectPath.startsWith(Project.PATH_SEPARATOR)) {
+            current = current.getRootProject();
+            projectPath = projectPath.substring(1);
+        }
+        for (String pattern : projectPath.split(Project.PATH_SEPARATOR)) {
+            Map<String, Project> children = current.getChildProjects();
+
+            NameMatcher matcher = new NameMatcher();
+            Project child = matcher.find(pattern, children);
+            if (child != null) {
+                current = child;
+                continue;
+            }
+
+            throw new ProjectLookupException(matcher.formatErrorMessage("project", current));
+        }
+
+        return (ProjectInternal) current;
+    }
+
+    public static class ProjectLookupException extends InvalidUserDataException {
+        public ProjectLookupException(String message) {
+            super(message);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ResolvedTaskPath.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ResolvedTaskPath.java
new file mode 100644
index 0000000..031ddef
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/ResolvedTaskPath.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution.taskpath;
+
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * by Szczepan Faber, created at: 1/29/13
+ */
+public class ResolvedTaskPath {
+    private final String prefix;
+    private final String taskName;
+    private final ProjectInternal project;
+    private final boolean isQualified;
+
+    public ResolvedTaskPath(String prefix, String taskName, ProjectInternal project) {
+        this.prefix = prefix;
+        this.taskName = taskName;
+        this.project = project;
+        this.isQualified = prefix.length() > 0;
+    }
+
+    public boolean isQualified() {
+        return isQualified;
+    }
+
+    public String getPrefix() {
+        return prefix;
+    }
+
+    public String getTaskName() {
+        return taskName;
+    }
+
+    /**
+     * @return for qualified path it returns the path the task lives in.
+     * For unqualified path it returns the project the task path was searched from.
+     */
+    public ProjectInternal getProject() {
+        return project;
+    }
+
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/TaskPathResolver.java b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/TaskPathResolver.java
new file mode 100644
index 0000000..a419d55
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/execution/taskpath/TaskPathResolver.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution.taskpath;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * by Szczepan Faber, created at: 1/29/13
+ */
+public class TaskPathResolver {
+
+    private final ProjectFinderByTaskPath projectFinder;
+
+    TaskPathResolver(ProjectFinderByTaskPath projectFinder) {
+        this.projectFinder = projectFinder;
+    }
+
+    public TaskPathResolver() {
+        this(new ProjectFinderByTaskPath());
+    }
+
+    /**
+     * @param path the task path, e.g. 'someTask', 'sT', ':sT', ':foo:bar:sT'
+     * @param startFrom the starting project the task should be found recursively
+     * @return resolved task path
+     */
+    public ResolvedTaskPath resolvePath(String path, ProjectInternal startFrom) {
+        ProjectInternal project;
+        String taskName; //eg. 'someTask' or 'sT'
+        String prefix; //eg. '', ':' or ':foo:bar'
+
+        if (path.contains(Project.PATH_SEPARATOR)) {
+            int idx = path.lastIndexOf(Project.PATH_SEPARATOR);
+            taskName = path.substring(idx + 1);
+            prefix = path.substring(0, idx+1);
+            String projectPath = Project.PATH_SEPARATOR.equals(prefix) ? prefix : path.substring(0, idx);
+            project = projectFinder.findProject(projectPath, startFrom);
+        } else {
+            project = startFrom;
+            taskName = path;
+            prefix = "";
+        }
+        return new ResolvedTaskPath(prefix, taskName, project);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLayoutParameters.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLayoutParameters.java
new file mode 100644
index 0000000..9f32a3c
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildLayoutParameters.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.initialization;
+
+import java.io.File;
+
+/**
+ * by Szczepan Faber, created at: 2/18/13
+ */
+public class BuildLayoutParameters {
+
+    private Boolean searchUpwards;
+    private File projectDir;
+    private File gradleUserHomeDir;
+
+    public BuildLayoutParameters setSearchUpwards(boolean searchUpwards) {
+        this.searchUpwards = searchUpwards;
+        return this;
+    }
+
+    public BuildLayoutParameters setProjectDir(File projectDir) {
+        this.projectDir = projectDir;
+        return this;
+    }
+
+    public BuildLayoutParameters setGradleUserHomeDir(File gradleUserHomeDir) {
+        this.gradleUserHomeDir = gradleUserHomeDir;
+        return this;
+    }
+
+    public File getProjectDir() {
+        return projectDir;
+    }
+
+    public File getGradleUserHomeDir() {
+        return gradleUserHomeDir;
+    }
+
+    public Boolean getSearchUpwards() {
+        return searchUpwards;
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
index b0d5349..d118ae6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
@@ -20,6 +20,7 @@ import org.gradle.BuildAdapter;
 import org.gradle.GradleLauncher;
 import org.gradle.StartParameter;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.GradleInternal;
 import org.gradle.api.internal.plugins.EmbeddableJavaProject;
 import org.gradle.api.invocation.Gradle;
 import org.gradle.cache.CacheBuilder;
@@ -98,7 +99,7 @@ public class BuildSourceBuilder {
         return BuildSourceBuilder.class.getResource(DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE);
     }
 
-    private static class BuildSrcBuildListener extends BuildAdapter {
+    private static class BuildSrcBuildListener extends BuildAdapter implements ModelConfigurationListener {
         private EmbeddableJavaProject projectInfo;
         private Set<File> classpath;
         private final boolean rebuild;
@@ -112,16 +113,15 @@ public class BuildSourceBuilder {
             gradle.getRootProject().apply(WrapUtil.toMap("from", getDefaultScript()));
         }
 
-        @Override
-        public void projectsEvaluated(Gradle gradle) {
+        public Collection<File> getRuntimeClasspath() {
+            return classpath;
+        }
+
+        public void onConfigure(GradleInternal gradle) {
             projectInfo = gradle.getRootProject().getConvention().getPlugin(EmbeddableJavaProject.class);
             gradle.getStartParameter().setTaskNames(rebuild ? projectInfo.getRebuildTasks() : projectInfo.getBuildTasks());
             classpath = projectInfo.getRuntimeClasspath().getFiles();
         }
-
-        public Collection<File> getRuntimeClasspath() {
-            return classpath;
-        }
     }
 
     private static class BuildSrcUpdateFactory implements Factory<DefaultClassPath> {
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 c717720..0be7a9a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultCommandLineConverter.java
@@ -19,7 +19,6 @@ import org.gradle.CacheUsage;
 import org.gradle.RefreshOptions;
 import org.gradle.StartParameter;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.initialization.Settings;
 import org.gradle.api.internal.file.BaseDirFileResolver;
 import org.gradle.api.internal.file.FileResolver;
 import org.gradle.cli.*;
@@ -33,13 +32,10 @@ import java.util.Map;
  * @author Hans Dockter
  */
 public class DefaultCommandLineConverter extends AbstractCommandLineConverter<StartParameter> {
-    private static final String NO_SEARCH_UPWARDS = "u";
-    private static final String PROJECT_DIR = "p";
     private static final String NO_PROJECT_DEPENDENCY_REBUILD = "a";
     private static final String BUILD_FILE = "b";
     public static final String INIT_SCRIPT = "I";
     private static final String SETTINGS_FILE = "c";
-    public static final String GRADLE_USER_HOME = "g";
     private static final String CACHE = "C";
     private static final String DRY_RUN = "m";
     private static final String NO_OPT = "no-opt";
@@ -56,22 +52,24 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
     private static final String PARALLEL = "parallel";
     private static final String PARALLEL_THREADS = "parallel-threads";
 
+    private static final String CONFIGURE_ON_DEMAND = "configure-on-demand";
+
     private final CommandLineConverter<LoggingConfiguration> loggingConfigurationCommandLineConverter = new LoggingCommandLineConverter();
     private final SystemPropertiesCommandLineConverter systemPropertiesCommandLineConverter = new SystemPropertiesCommandLineConverter();
     private final ProjectPropertiesCommandLineConverter projectPropertiesCommandLineConverter = new ProjectPropertiesCommandLineConverter();
+    private final LayoutCommandLineConverter layoutCommandLineConverter = new LayoutCommandLineConverter();
 
     public void configure(CommandLineParser parser) {
         loggingConfigurationCommandLineConverter.configure(parser);
         systemPropertiesCommandLineConverter.configure(parser);
         projectPropertiesCommandLineConverter.configure(parser);
+        layoutCommandLineConverter.configure(parser);
+
         parser.allowMixedSubcommandsAndOptions();
-        parser.option(NO_SEARCH_UPWARDS, "no-search-upward").hasDescription(String.format("Don't search in parent folders for a %s file.", Settings.DEFAULT_SETTINGS_FILE));
         parser.option(CACHE, "cache").hasArgument().hasDescription("Specifies how compiled build scripts should be cached. Possible values are: 'rebuild' and 'on'. Default value is 'on'")
                     .deprecated("Use '--rerun-tasks' or '--recompile-scripts' instead");
         parser.option(PROJECT_CACHE_DIR).hasArgument().hasDescription("Specifies the project-specific cache directory. Defaults to .gradle in the root project directory.");
         parser.option(DRY_RUN, "dry-run").hasDescription("Runs the builds with all task actions disabled.");
-        parser.option(PROJECT_DIR, "project-dir").hasArgument().hasDescription("Specifies the start directory for Gradle. Defaults to current directory.");
-        parser.option(GRADLE_USER_HOME, "gradle-user-home").hasArgument().hasDescription("Specifies the gradle user home directory.");
         parser.option(INIT_SCRIPT, "init-script").hasArguments().hasDescription("Specifies an initialization script.");
         parser.option(SETTINGS_FILE, "settings-file").hasArgument().hasDescription("Specifies the settings file.");
         parser.option(BUILD_FILE, "build-file").hasArgument().hasDescription("Specifies the build file.");
@@ -87,6 +85,7 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         parser.option(REFRESH_DEPENDENCIES).hasDescription("Refresh the state of dependencies.");
         parser.option(PARALLEL).hasDescription("Build projects in parallel. Gradle will attempt to determine the optimal number of executor threads to use.").incubating();
         parser.option(PARALLEL_THREADS).hasArgument().hasDescription("Build projects in parallel, using the specified number of executor threads.").incubating();
+        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();
     }
 
     @Override
@@ -104,16 +103,18 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
         Map<String, String> projectProperties = projectPropertiesCommandLineConverter.convert(options);
         startParameter.getProjectProperties().putAll(projectProperties);
 
-        if (options.hasOption(NO_SEARCH_UPWARDS)) {
-            startParameter.setSearchUpwards(false);
+        BuildLayoutParameters layout = new BuildLayoutParameters().setProjectDir(startParameter.getCurrentDir());
+        layoutCommandLineConverter.convert(options, layout);
+        if (layout.getGradleUserHomeDir() != null) {
+            startParameter.setGradleUserHomeDir(layout.getGradleUserHomeDir());
         }
-
-        if (options.hasOption(PROJECT_DIR)) {
-            startParameter.setProjectDir(resolver.resolve(options.option(PROJECT_DIR).getValue()));
+        if (layout.getProjectDir() != null) {
+            startParameter.setProjectDir(layout.getProjectDir());
         }
-        if (options.hasOption(GRADLE_USER_HOME)) {
-            startParameter.setGradleUserHomeDir(resolver.resolve(options.option(GRADLE_USER_HOME).getValue()));
+        if (layout.getSearchUpwards() != null) {
+            startParameter.setSearchUpwards(layout.getSearchUpwards());
         }
+
         if (options.hasOption(BUILD_FILE)) {
             startParameter.setBuildFile(resolver.resolve(options.option(BUILD_FILE).getValue()));
         }
@@ -198,6 +199,10 @@ public class DefaultCommandLineConverter extends AbstractCommandLineConverter<St
             }
         }
 
+        if (options.hasOption(CONFIGURE_ON_DEMAND)) {
+            startParameter.setConfigureOnDemand(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 d7b4a4c..c853773 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
@@ -143,9 +143,10 @@ public class DefaultGradleLauncher extends GradleLauncher {
 
         if (!gradle.getStartParameter().isConfigureOnDemand()) {
             buildListener.projectsEvaluated(gradle);
-            modelConfigurationListener.onConfigure(gradle);
         }
 
+        modelConfigurationListener.onConfigure(gradle);
+
         if (upTo == Stage.Configure) {
             return;
         }
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 6116dc3..088fb61 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
@@ -19,9 +19,7 @@ package org.gradle.initialization;
 import org.gradle.*;
 import org.gradle.api.internal.ExceptionAnalyser;
 import org.gradle.api.internal.GradleInternal;
-import org.gradle.internal.reflect.Instantiator;
 import org.gradle.api.internal.project.GlobalServicesRegistry;
-import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.api.internal.project.TopLevelBuildServiceRegistry;
 import org.gradle.api.logging.Logging;
 import org.gradle.api.logging.StandardOutputListener;
@@ -30,6 +28,8 @@ import org.gradle.cli.CommandLineConverter;
 import org.gradle.configuration.BuildConfigurer;
 import org.gradle.execution.BuildExecuter;
 import org.gradle.initialization.layout.BuildLayoutFactory;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.invocation.DefaultGradle;
 import org.gradle.listener.ListenerManager;
 import org.gradle.logging.LoggingManagerInternal;
@@ -100,7 +100,7 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
     }
 
     private DefaultGradleLauncher doNewInstance(StartParameter startParameter, BuildRequestMetaData requestMetaData) {
-        TopLevelBuildServiceRegistry serviceRegistry = new TopLevelBuildServiceRegistry(sharedServices, startParameter);
+        final TopLevelBuildServiceRegistry serviceRegistry = new TopLevelBuildServiceRegistry(sharedServices, startParameter);
         serviceRegistry.add(BuildRequestMetaData.class, requestMetaData);
         serviceRegistry.add(BuildClientMetaData.class, requestMetaData.getClient());
         ListenerManager listenerManager = serviceRegistry.get(ListenerManager.class);
@@ -162,4 +162,5 @@ public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
             services.close();
         }
     }
+
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java b/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
index 2c6cbf7..7987192 100644
--- a/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/DependencyResolutionLogger.java
@@ -20,37 +20,41 @@ import org.gradle.api.artifacts.ResolvableDependencies;
 import org.gradle.logging.ProgressLogger;
 import org.gradle.logging.ProgressLoggerFactory;
 
+import java.util.LinkedList;
+
 // TODO:DAZ Think about a better way to do thread-safety here, maybe
 public class DependencyResolutionLogger implements DependencyResolutionListener {
+    private final ThreadLocal<LinkedList<ProgressLogger>> progressLoggers = new ThreadLocal<LinkedList<ProgressLogger>>() {
+        protected LinkedList<ProgressLogger> initialValue() {
+            return new LinkedList<ProgressLogger>();
+        }
+    };
     private final ProgressLoggerFactory loggerFactory;
-    private final ThreadLocal<ProgressLogger> progressLogger = new ThreadLocal<ProgressLogger>();
 
     public DependencyResolutionLogger(ProgressLoggerFactory loggerFactory) {
         this.loggerFactory = loggerFactory;
     }
 
+    //TODO SF add concurrent unit test coverage
     public void beforeResolve(ResolvableDependencies dependencies) {
-        checkLogger(false);
+        LinkedList<ProgressLogger> loggers = progressLoggers.get();
+        progressLoggers.set(loggers);
         ProgressLogger logger = loggerFactory.newOperation(DependencyResolutionLogger.class);
         logger.setDescription(String.format("Resolve %s", dependencies));
         logger.setShortDescription(String.format("Resolving %s", dependencies));
         logger.started();
-        progressLogger.set(logger);
+        loggers.add(logger);
     }
 
     public void afterResolve(ResolvableDependencies dependencies) {
-        checkLogger(true);
-        progressLogger.get().completed();
-        progressLogger.remove();
-    }
-
-    private void checkLogger(boolean shouldExist) {
-        ProgressLogger logger = progressLogger.get();
-        if (shouldExist && logger == null) {
-            throw new IllegalStateException("Logging operation not started");
+        LinkedList<ProgressLogger> loggers = progressLoggers.get();
+        if (loggers.isEmpty()) {
+            throw new IllegalStateException("Logging operation was not started or it has already completed.");
         }
-        if (!shouldExist && logger != null) {
-            throw new IllegalStateException("Logging operation already in progress");
+        ProgressLogger logger = loggers.removeLast();
+        logger.completed();
+        if (loggers.isEmpty()) {
+            progressLoggers.remove();
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/LayoutCommandLineConverter.java b/subprojects/core/src/main/groovy/org/gradle/initialization/LayoutCommandLineConverter.java
new file mode 100644
index 0000000..db5ba26
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/LayoutCommandLineConverter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.initialization;
+
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.internal.file.BaseDirFileResolver;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.cli.AbstractCommandLineConverter;
+import org.gradle.cli.CommandLineArgumentException;
+import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
+import org.gradle.internal.SystemProperties;
+import org.gradle.internal.nativeplatform.filesystem.FileSystems;
+import org.gradle.util.GFileUtils;
+
+/**
+ * by Szczepan Faber, created at: 2/18/13
+ */
+public class LayoutCommandLineConverter extends AbstractCommandLineConverter<BuildLayoutParameters> {
+
+    public static final String GRADLE_USER_HOME = "g";
+    private static final String NO_SEARCH_UPWARDS = "u";
+    private static final String PROJECT_DIR = "p";
+
+    protected BuildLayoutParameters newInstance() {
+        return new BuildLayoutParameters().setProjectDir(GFileUtils.canonicalise(SystemProperties.getCurrentDir()));
+    }
+
+    public BuildLayoutParameters convert(ParsedCommandLine options, BuildLayoutParameters target) throws CommandLineArgumentException {
+        FileResolver resolver = new BaseDirFileResolver(FileSystems.getDefault(), target.getProjectDir());
+        if (options.hasOption(NO_SEARCH_UPWARDS)) {
+            target.setSearchUpwards(false);
+        }
+        if (options.hasOption(PROJECT_DIR)) {
+            target.setProjectDir(resolver.resolve(options.option(PROJECT_DIR).getValue()));
+        }
+        if (options.hasOption(GRADLE_USER_HOME)) {
+            target.setGradleUserHomeDir(resolver.resolve(options.option(GRADLE_USER_HOME).getValue()));
+        }
+        return target;
+    }
+
+    public void configure(CommandLineParser parser) {
+        parser.option(NO_SEARCH_UPWARDS, "no-search-upward").hasDescription(String.format("Don't search in parent folders for a %s file.", Settings.DEFAULT_SETTINGS_FILE));
+        parser.option(PROJECT_DIR, "project-dir").hasArgument().hasDescription("Specifies the start directory for Gradle. Defaults to current directory.");
+        parser.option(LayoutCommandLineConverter.GRADLE_USER_HOME, "gradle-user-home").hasArgument().hasDescription("Specifies the gradle user home directory.");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectAccessListener.java b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectAccessListener.java
new file mode 100644
index 0000000..b4fa573
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/initialization/ProjectAccessListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.initialization;
+
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * Internal interface, used by our configuration on demand mode.
+ *
+ * by Szczepan Faber, created at: 2/5/13
+ */
+public interface ProjectAccessListener {
+    void beforeRequestingTaskByPath(ProjectInternal targetProject);
+    void beforeResolvingProjectDependency(ProjectInternal dependencyProject);
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java b/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
index a96c1b5..c76f2d1 100644
--- a/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
+++ b/subprojects/core/src/main/groovy/org/gradle/listener/ActionBroadcast.java
@@ -20,6 +20,12 @@ import org.gradle.api.Action;
 public class ActionBroadcast<T> implements Action<T> {
     private final ListenerBroadcast<Action> broadcast = new ListenerBroadcast<Action>(Action.class);
 
+    public ActionBroadcast() {}
+
+    public ActionBroadcast(Iterable<Action> actions) {
+        broadcast.addAll(actions);
+    }
+
     public void execute(T t) {
         broadcast.getSource().execute(t);
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/process/ProcessForkOptions.java b/subprojects/core/src/main/groovy/org/gradle/process/ProcessForkOptions.java
index 3b1075c..a2b7eb1 100755
--- a/subprojects/core/src/main/groovy/org/gradle/process/ProcessForkOptions.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/ProcessForkOptions.java
@@ -52,7 +52,7 @@ public interface ProcessForkOptions {
     File getWorkingDir();
 
     /**
-     * Sets the working directory for the process. The supplied argument is evaluated as for {@link
+     * Sets the working directory for the process. The supplied argument is evaluated as per {@link
      * org.gradle.api.Project#file(Object)}.
      *
      * @param dir The working directory. Must not be null.
@@ -60,7 +60,7 @@ public interface ProcessForkOptions {
     void setWorkingDir(Object dir);
 
     /**
-     * Sets the working directory for the process. The supplied argument is evaluated as for {@link
+     * Sets the working directory for the process. The supplied argument is evaluated as per {@link
      * org.gradle.api.Project#file(Object)}.
      *
      * @param dir The working directory. Must not be null.
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
old mode 100644
new mode 100755
index a1bfd97..20a330f
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
@@ -222,7 +222,6 @@ public class DefaultExecHandle implements ExecHandle, ProcessSettings {
         }
         lock.lock();
         try {
-            ProcessParentingInitializer.intitialize();
             if (!stateIn(ExecHandleState.INIT)) {
                 throw new IllegalStateException(String.format("Cannot start process '%s' because it has already been started", displayName));
             }
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
old mode 100644
new mode 100755
index baac7dc..7b86a21
--- a/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
+++ b/subprojects/core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
@@ -61,6 +61,7 @@ public class ExecHandleRunner implements Runnable {
     }
 
     public void run() {
+        ProcessParentingInitializer.intitialize();
         ProcessBuilder processBuilder = processBuilderFactory.createProcessBuilder(execHandle);
         try {
             Process process;
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/EvalutationOperation.java b/subprojects/core/src/main/groovy/org/gradle/profile/EvalutationOperation.java
new file mode 100644
index 0000000..b886e07
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/EvalutationOperation.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.profile;
+
+import org.gradle.api.Project;
+
+public class EvalutationOperation extends ContinuousOperation {
+    private final Project project;
+
+    public EvalutationOperation(Project project) {
+        this.project = project;
+    }
+
+    public String getPath(){
+        return project.getPath();
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/HTMLProfileReport.groovy b/subprojects/core/src/main/groovy/org/gradle/profile/HTMLProfileReport.groovy
deleted file mode 100644
index f2f7d45..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/profile/HTMLProfileReport.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.profile
-
-import java.text.SimpleDateFormat
-
-import groovy.text.SimpleTemplateEngine
-import org.gradle.reporting.TextReportRenderer
-import org.gradle.reporting.DurationFormatter
-
-class HTMLProfileReport extends TextReportRenderer<BuildProfile> {
-    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd - HH:mm:ss");
-    private static final DurationFormatter DURATION_FORMAT = new DurationFormatter()
-
-    protected void writeTo(BuildProfile profile, Writer out) {
-        def templateStream = getClass().getResourceAsStream('/org/gradle/profile/ProfileTemplate.html')
-        def template
-        try {
-            SimpleTemplateEngine engine = new SimpleTemplateEngine()
-            template = engine.createTemplate(new InputStreamReader(templateStream))
-        } finally {
-            templateStream.close()
-        }
-
-        def binding = ['build': profile, 'time': DURATION_FORMAT, 'date': DATE_FORMAT]
-        def result = template.make(binding)
-        result.writeTo(out)
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java
index a958cb7..9f5f6e4 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProfileReportRenderer.java
@@ -15,16 +15,21 @@
  */
 package org.gradle.profile;
 
-import org.gradle.reporting.DomReportRenderer;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+import org.gradle.reporting.DurationFormatter;
 import org.gradle.reporting.HtmlReportRenderer;
+import org.gradle.reporting.ReportRenderer;
 import org.gradle.reporting.TabbedPageRenderer;
-import org.gradle.reporting.TextDomReportRenderer;
-import org.w3c.dom.Element;
+import org.gradle.util.CollectionUtils;
 
 import java.io.File;
+import java.io.IOException;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.Comparator;
+import java.util.List;
 
+//TODO SF add coverage
 public class ProfileReportRenderer {
     public void writeTo(BuildProfile buildProfile, File file) {
         HtmlReportRenderer renderer = new HtmlReportRenderer();
@@ -34,6 +39,7 @@ public class ProfileReportRenderer {
         renderer.requireResource(getClass().getResource("style.css"));
         renderer.renderer(new ProfilePageRenderer()).writeTo(buildProfile, file);
     }
+    private static final DurationFormatter DURATION_FORMAT = new DurationFormatter();
 
     private static class ProfilePageRenderer extends TabbedPageRenderer<BuildProfile> {
         static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd - HH:mm:ss");
@@ -44,20 +50,158 @@ public class ProfileReportRenderer {
         }
 
         @Override
-        protected DomReportRenderer<BuildProfile> getHeaderRenderer() {
-            return new DomReportRenderer<BuildProfile>() {
+        protected ReportRenderer<BuildProfile, SimpleHtmlWriter> getHeaderRenderer() {
+            return new ReportRenderer<BuildProfile, SimpleHtmlWriter>() {
                 @Override
-                public void render(BuildProfile model, Element parent) {
-                    Element header = appendWithId(parent, "div", "header");
-                    appendWithText(header, "p", String.format("Profiled with tasks: %s", model.getTaskDescription()));
-                    appendWithText(header, "p", String.format("Run on: %s", DATE_FORMAT.format(model.getBuildStarted())));
+                public void render(BuildProfile model, SimpleHtmlWriter htmlWriter) throws IOException {
+                    htmlWriter.startElement("div").attribute("id", "header")
+                        .startElement("p").characters(String.format("Profiled with tasks: %s", model.getTaskDescription())).endElement()
+                        .startElement("p").characters(String.format("Run on: %s", DATE_FORMAT.format(model.getBuildStarted()))).endElement()
+                    .endElement();
                 }
             };
         }
 
         @Override
-        protected DomReportRenderer<BuildProfile> getContentRenderer() {
-            return new TextDomReportRenderer<BuildProfile>(new HTMLProfileReport());
+        protected  ReportRenderer<BuildProfile, SimpleHtmlWriter> getContentRenderer() {
+            return new ReportRenderer<BuildProfile, SimpleHtmlWriter>() {
+                @Override
+                public void render(BuildProfile model, SimpleHtmlWriter htmlWriter) throws IOException {
+                    htmlWriter.startElement("div").attribute("id", "tabs")
+                        .startElement("ul").attribute("class", "tabLinks")
+                            .startElement("li").startElement("a").attribute("href", "#tab0").characters("Summary").endElement().endElement()
+                            .startElement("li").startElement("a").attribute("href", "#tab1").characters("Configuration").endElement().endElement()
+                            .startElement("li").startElement("a").attribute("href", "#tab2").characters("Dependency Resolution").endElement().endElement()
+                            .startElement("li").startElement("a").attribute("href", "#tab3").characters("Task Execution").endElement().endElement()
+                        .endElement();
+                        htmlWriter.startElement("div").attribute("class", "tab").attribute("id", "tab0");
+                            htmlWriter.startElement("h2").characters("Summary").endElement();
+                            htmlWriter.startElement("table");
+                                htmlWriter.startElement("thead");
+                                    htmlWriter.startElement("tr");
+                                        htmlWriter.startElement("th").characters("Description").endElement();
+                                        htmlWriter.startElement("th").attribute("class", "numeric").characters("Duration").endElement();
+                                    htmlWriter.endElement();
+                                htmlWriter.endElement();
+                                htmlWriter.startElement("tr");
+                                    htmlWriter.startElement("td").characters("Total Build Time").endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getElapsedTotal())).endElement();
+                                htmlWriter.endElement();
+                                htmlWriter.startElement("tr");
+                                    htmlWriter.startElement("td").characters("Startup").endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getElapsedStartup())).endElement();
+                                htmlWriter.endElement();
+                                htmlWriter.startElement("tr");
+                                    htmlWriter.startElement("td").characters("Settings and BuildSrc").endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getElapsedSettings())).endElement();
+                                htmlWriter.endElement();
+                                htmlWriter.startElement("tr");
+                                    htmlWriter.startElement("td").characters("Loading Projects").endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getElapsedProjectsLoading())).endElement();
+                                htmlWriter.endElement();
+                                htmlWriter.startElement("tr");
+                                    htmlWriter.startElement("td").characters("Configuring Projects").endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getElapsedAfterProjectsEvaluated())).endElement();
+                                htmlWriter.endElement();
+                                htmlWriter.startElement("tr");
+                                    htmlWriter.startElement("td").characters("Task Execution").endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getElapsedTotalExecutionTime())).endElement();
+                                htmlWriter.endElement();
+                            htmlWriter.endElement();
+                        htmlWriter.endElement();
+                        htmlWriter.startElement("div").attribute("class", "tab").attribute("id", "tab1");
+                            htmlWriter.startElement("h2").characters("Configuration").endElement();
+                            htmlWriter.startElement("table");
+                                htmlWriter.startElement("thead");
+                                    htmlWriter.startElement("tr");
+                                        htmlWriter.startElement("th").characters("Project").endElement();
+                                        htmlWriter.startElement("th").attribute("class", "numeric").characters("Duration").endElement();
+                                    htmlWriter.endElement();
+                                htmlWriter.endElement();
+                                htmlWriter.startElement("tr");
+                                    htmlWriter.startElement("td").characters("All projects").endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getProjectConfiguration().getElapsedTime())).endElement();
+                                htmlWriter.endElement();
+                                final List<Operation> operations = CollectionUtils.sort(model.getProjectConfiguration().getOperations(), new Comparator<Operation>() {
+                                    public int compare(Operation o1, Operation o2) {
+                                        return Long.valueOf(o2.getElapsedTime()).compareTo(Long.valueOf(o1.getElapsedTime()));
+                                    }
+                                });
+                                for (Operation operation : operations) {
+                                    EvalutationOperation evalOperation = (EvalutationOperation)operation;
+                                    htmlWriter.startElement("tr");
+                                        htmlWriter.startElement("td").characters(evalOperation.getPath()).endElement();
+                                        htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(evalOperation.getElapsedTime())).endElement();
+                                    htmlWriter.endElement();
+                                }
+                            htmlWriter.endElement()
+                        .endElement();
+                        htmlWriter.startElement("div").attribute("class", "tab").attribute("id", "tab2");
+                            htmlWriter.startElement("h2").characters("Dependency Resolution").endElement()
+                                .startElement("table")
+                                    .startElement("thead");
+                                    htmlWriter.startElement("tr");
+                                        htmlWriter.startElement("th").characters("Dependencies").endElement();
+                                        htmlWriter.startElement("th").attribute("class", "numeric").characters("Duration").endElement();
+                                    htmlWriter.endElement();
+                                htmlWriter.endElement();
+                                htmlWriter.startElement("tr");
+                                    htmlWriter.startElement("td").characters("All dependencies").endElement();
+                                    htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(model.getDependencySets().getElapsedTime())).endElement();
+                                htmlWriter.endElement();
+
+                                final List<DependencyResolveProfile> dependencyResolveProfiles = CollectionUtils.sort(model.getDependencySets().getOperations(), new Comparator<DependencyResolveProfile>() {
+                                        public int compare(DependencyResolveProfile p1, DependencyResolveProfile p2) {
+                                        return Long.valueOf(p2.getElapsedTime()).compareTo(Long.valueOf(p1.getElapsedTime()));
+                                    }
+                                    });
+                                for (DependencyResolveProfile profile : dependencyResolveProfiles) {
+                                    htmlWriter.startElement("tr");
+                                        htmlWriter.startElement("td").characters(profile.getPath()).endElement();
+                                        htmlWriter.startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(profile.getElapsedTime())).endElement();
+                                    htmlWriter.endElement();
+                                }
+                            htmlWriter.endElement()
+                        .endElement();
+                        htmlWriter.startElement("div").attribute("class", "tab").attribute("id", "tab3");
+                            htmlWriter.startElement("h2").characters("Task Execution").endElement()
+                            .startElement("table")
+                                .startElement("thead")
+                                    .startElement("tr")
+                                        .startElement("th").characters("Task").endElement()
+                                        .startElement("th").attribute("class", "numeric").characters("Duration").endElement()
+                                        .startElement("th").characters("Result").endElement()
+                                    .endElement()
+                                .endElement();
+                                final List<ProjectProfile> projects = CollectionUtils.sort(model.getProjects(), new Comparator<ProjectProfile>() {
+                                        public int compare(ProjectProfile p1, ProjectProfile p2) {
+                                        return Long.valueOf(p2.getTasks().getElapsedTime()).compareTo(p1.getTasks().getElapsedTime());
+                                    }
+                                });
+                                for (ProjectProfile project : projects) {
+                                   htmlWriter.startElement("tr")
+                                        .startElement("td").characters(project.getPath()).endElement()
+                                        .startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(project.getTasks().getElapsedTime())).endElement()
+                                        .startElement("td").characters("(total)").endElement()
+                                    .endElement();
+                                    final List<TaskExecution> taskExecutions = CollectionUtils.sort(project.getTasks().getOperations(), new Comparator<TaskExecution>() {
+                                        public int compare(TaskExecution p1, TaskExecution p2) {
+                                            return Long.valueOf(p2.getElapsedTime()).compareTo(Long.valueOf(p1.getElapsedTime()));
+                                        }
+                                    });
+                                    for (TaskExecution taskExecution : taskExecutions) {
+                                        htmlWriter.startElement("tr")
+                                            .startElement("td").attribute("class", "indentPath").characters(taskExecution.getPath()).endElement()
+                                            .startElement("td").attribute("class", "numeric").characters(DURATION_FORMAT.format(taskExecution.getElapsedTime())).endElement()
+                                            .startElement("td").characters(taskExecution.getState().getSkipped() ? taskExecution.getState().getSkipMessage() : (taskExecution.getState().getDidWork()) ? "" : "Did No Work").endElement()
+                                        .endElement();
+                                    }
+                                }
+                            htmlWriter.endElement()
+                        .endElement()
+                    .endElement();
+                }
+            };
         }
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java b/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
index 7f95477..e7abbe5 100644
--- a/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
+++ b/subprojects/core/src/main/groovy/org/gradle/profile/ProjectProfile.java
@@ -25,14 +25,11 @@ public class ProjectProfile {
     private final Project project;
     private ProjectState state;
     private HashMap<Task, TaskExecution> tasks = new HashMap<Task, TaskExecution>();
-    private final ContinuousOperation evaluation = new ContinuousOperation() {
-        public String getPath() {
-            return project.getPath();
-        }
-    };
+    private final ContinuousOperation evaluation;
 
     public ProjectProfile(Project project) {
         this.project = project;
+        this.evaluation = new EvalutationOperation(project);
     }
 
     /**
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/CodePanelRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/CodePanelRenderer.java
index 06b0747..b233147 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/CodePanelRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/CodePanelRenderer.java
@@ -15,14 +15,16 @@
  */
 package org.gradle.reporting;
 
-import org.w3c.dom.Element;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
 
-public class CodePanelRenderer extends DomReportRenderer<String> {
+import java.io.IOException;
+
+public class CodePanelRenderer extends ReportRenderer<String, SimpleHtmlWriter> {
     @Override
-    public void render(String text, Element parent) {
+    public void render(String text, SimpleHtmlWriter htmlWriter) throws IOException {
         // Wrap in a <span>, to work around CSS problem in IE
-        Element span = append(parent, "span");
-        span.setAttribute("class", "code");
-        appendWithText(span, "pre", text);
+        htmlWriter.startElement("span").attribute("class", "code")
+            .startElement("pre").characters(text).endElement()
+        .endElement();
     }
 }
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/DomReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/DomReportRenderer.java
deleted file mode 100644
index c478bf8..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/DomReportRenderer.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.reporting;
-
-import org.w3c.dom.Element;
-
-public abstract class DomReportRenderer<T> {
-    /**
-     * Renders the report for the given model as children of the given DOM element.
-     */
-    public abstract void render(T model, Element parent);
-
-    protected Element append(Element parent, String name) {
-        Element element = parent.getOwnerDocument().createElement(name);
-        parent.appendChild(element);
-        return element;
-    }
-
-    protected Element appendWithId(Element parent, String name, String id) {
-        Element element = parent.getOwnerDocument().createElement(name);
-        parent.appendChild(element);
-        element.setAttribute("id", id);
-        return element;
-    }
-
-    protected Element appendWithText(Element parent, String name, Object textContent) {
-        Element element = parent.getOwnerDocument().createElement(name);
-        parent.appendChild(element);
-        appendText(element, textContent);
-        return element;
-    }
-
-    protected void appendText(Element element, Object textContent) {
-        element.appendChild(element.getOwnerDocument().createTextNode(textContent.toString()));
-    }
-
-    protected Element appendLink(Element parent, String href, Object textContent) {
-        Element element = appendWithText(parent, "a", textContent);
-        element.setAttribute("href", href);
-        return element;
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java
index d717748..d55c587 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/HtmlReportRenderer.java
@@ -16,56 +16,30 @@
 package org.gradle.reporting;
 
 import org.apache.commons.lang.StringUtils;
-import org.gradle.internal.SystemProperties;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
 import org.gradle.util.GFileUtils;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
 
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.dom.DOMSource;
-import javax.xml.transform.stream.StreamResult;
-import java.io.*;
+import java.io.File;
+import java.io.Writer;
 import java.net.URL;
 import java.util.HashSet;
 import java.util.Set;
 
 public class HtmlReportRenderer {
-    private DocumentBuilder documentBuilder;
-    private Transformer transformer;
     private final Set<URL> resources = new HashSet<URL>();
 
     public void requireResource(URL resource) {
         resources.add(resource);
     }
 
-    public <T> TextReportRenderer<T> renderer(final DomReportRenderer<T> renderer) {
+    public <T> TextReportRenderer<T> renderer(final ReportRenderer<T, SimpleHtmlWriter> renderer) {
         return renderer(new TextReportRenderer<T>() {
             @Override
             protected void writeTo(T model, Writer writer) throws Exception {
-                if (documentBuilder == null) {
-                    documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
-                }
-                Document document = documentBuilder.newDocument();
-
-                Element html = document.createElement("html");
-                document.appendChild(html);
-                renderer.render(model, html);
-
-                if (transformer == null) {
-                    TransformerFactory factory = TransformerFactory.newInstance();
-                    transformer = factory.newTransformer();
-                    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
-                    transformer.setOutputProperty(OutputKeys.METHOD, "html");
-                    transformer.setOutputProperty(OutputKeys.MEDIA_TYPE, "text/html");
-                }
-
-                writer.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
-                writer.write(SystemProperties.getLineSeparator());
-                transformer.transform(new DOMSource(document), new StreamResult(writer));
+                SimpleHtmlWriter htmlWriter = new SimpleHtmlWriter(writer, "");
+                htmlWriter.startElement("html");
+                renderer.render(model, htmlWriter);
+                htmlWriter.endElement();
             }
         });
     }
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/ReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/ReportRenderer.java
new file mode 100644
index 0000000..328acff
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/ReportRenderer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.reporting;
+
+import java.io.IOException;
+
+public abstract class ReportRenderer<T, E> {
+    /**
+     * Renders the report for the given model as children of the given DOM element.
+     */
+    public abstract void render(T model, E parent) throws IOException;
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java
index be536eb..43c638c 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TabbedPageRenderer.java
@@ -15,13 +15,14 @@
  */
 package org.gradle.reporting;
 
+import org.gradle.api.internal.html.SimpleHtmlWriter;
 import org.gradle.util.GradleVersion;
-import org.w3c.dom.Element;
 
+import java.io.IOException;
 import java.text.DateFormat;
 import java.util.Date;
 
-public abstract class TabbedPageRenderer<T> extends DomReportRenderer<T> {
+public abstract class TabbedPageRenderer<T> extends ReportRenderer<T, SimpleHtmlWriter> {
     private T model;
 
     protected T getModel() {
@@ -30,47 +31,36 @@ public abstract class TabbedPageRenderer<T> extends DomReportRenderer<T> {
 
     protected abstract String getTitle();
 
+    protected abstract  ReportRenderer<T, SimpleHtmlWriter> getHeaderRenderer();
+
+    protected abstract  ReportRenderer<T, SimpleHtmlWriter> getContentRenderer();
+
     protected String getPageTitle() {
         return getTitle();
     }
 
-    protected abstract DomReportRenderer<T> getHeaderRenderer();
-
-    protected abstract DomReportRenderer<T> getContentRenderer();
-
     @Override
-    public void render(T model, Element parent) {
+    public void render(final T model, SimpleHtmlWriter htmlWriter) throws IOException {
         this.model = model;
+        htmlWriter.startElement("head")
+            .startElement("meta").attribute("httpEquiv", "Content-Type").attribute("content", "text/html; charset=utf-8").endElement()
+            .startElement("title").characters(getPageTitle()).endElement()
+            .startElement("link").attribute("href", "base-style.css").attribute("rel", "stylesheet").attribute("type", "text/css").endElement()
+            .startElement("link").attribute("href", "style.css").attribute("rel", "stylesheet").attribute("type", "text/css").endElement()
+            .startElement("script").attribute("src", "report.js").attribute("type", "text/javascript").characters("").endElement() //html does not like <a name="..."/>
+        .endElement();
 
-        // <head>
-        Element head = append(parent, "head");
-        appendWithText(head, "title", getPageTitle());
-        Element link = append(head, "link");
-        link.setAttribute("rel", "stylesheet");
-        link.setAttribute("href", "base-style.css");
-        link.setAttribute("type", "text/css");
-        link = append(head, "link");
-        link.setAttribute("rel", "stylesheet");
-        link.setAttribute("href", "style.css");
-        link.setAttribute("type", "text/css");
-        Element script = append(head, "script");
-        script.setAttribute("src", "report.js");
-        script.setAttribute("type", "text/javascript");
-
-        // <body>
-        Element body = append(parent, "body");
-
-        // Content
-        Element content = appendWithId(body, "div", "content");
-        appendWithText(content, "h1", getTitle());
-        getHeaderRenderer().render(model, content);
-        getContentRenderer().render(model, content);
-
-        // Footer
-        Element footer = appendWithId(content, "div", "footer");
-        Element footerText = append(footer, "p");
-        appendText(footerText, "Generated by ");
-        appendLink(footerText, "http://www.gradle.org/", String.format("Gradle %s", GradleVersion.current().getVersion()));
-        appendText(footerText, String.format(" at %s", DateFormat.getDateTimeInstance().format(new Date())));
+        htmlWriter.startElement("body")
+            .startElement("div").attribute("id", "content")
+            .startElement("h1").characters(getTitle()).endElement();
+            getHeaderRenderer().render(model, htmlWriter);
+            getContentRenderer().render(model, htmlWriter);
+            htmlWriter.startElement("div").attribute("id", "footer")
+                .startElement("p").characters("Generated by ")
+                    .startElement("a").attribute("href", "http://www.gradle.org").characters(String.format("Gradle %s", GradleVersion.current().getVersion())).endElement()
+                    .characters(String.format(" at %s", DateFormat.getDateTimeInstance().format(new Date())))
+                .endElement()
+            .endElement()
+        .endElement();
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TabsRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TabsRenderer.java
index 744f01b..6583475 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/TabsRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TabsRenderer.java
@@ -15,15 +15,16 @@
  */
 package org.gradle.reporting;
 
-import org.w3c.dom.Element;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
-public class TabsRenderer<T> extends DomReportRenderer<T> {
+public class TabsRenderer<T> extends ReportRenderer<T, SimpleHtmlWriter> {
     private final List<TabDefinition> tabs = new ArrayList<TabDefinition>();
 
-    public void add(String title, DomReportRenderer<T> contentRenderer) {
+    public void add(String title, ReportRenderer<T, SimpleHtmlWriter> contentRenderer) {
         tabs.add(new TabDefinition(title, contentRenderer));
     }
 
@@ -32,28 +33,34 @@ public class TabsRenderer<T> extends DomReportRenderer<T> {
     }
 
     @Override
-    public void render(T model, Element parent) {
-        Element tabs = appendWithId(parent, "div", "tabs");
-        Element ul = append(tabs, "ul");
-        ul.setAttribute("class", "tabLinks");
-        for (int i = 0; i < this.tabs.size(); i++) {
-            TabDefinition tab = this.tabs.get(i);
-            Element li = append(ul, "li");
-            Element a = appendWithText(li, "a", tab.title);
-            String tabId = String.format("tab%s", i);
-            a.setAttribute("href", "#" + tabId);
-            Element tabDiv = appendWithId(tabs, "div", tabId);
-            tabDiv.setAttribute("class", "tab");
-            appendWithText(tabDiv, "h2", tab.title);
-            tab.renderer.render(model, tabDiv);
-        }
+    public void render(T model, SimpleHtmlWriter htmlWriterWriter) throws IOException {
+        htmlWriterWriter.startElement("div").attribute("id", "tabs");
+            htmlWriterWriter.startElement("ul").attribute("class", "tabLinks");
+                for (int i = 0; i < this.tabs.size(); i++) {
+                    TabDefinition tab = this.tabs.get(i);
+                    String tabId = String.format("tab%s", i);
+                    htmlWriterWriter.startElement("li");
+                        htmlWriterWriter.startElement("a").attribute("href", "#" + tabId).characters(tab.title).endElement();
+                    htmlWriterWriter.endElement();
+                }
+            htmlWriterWriter.endElement();
+
+            for (int i = 0; i < this.tabs.size(); i++) {
+                TabDefinition tab = this.tabs.get(i);
+                String tabId = String.format("tab%s", i);
+                htmlWriterWriter.startElement("div").attribute("id", tabId).attribute("class", "tab");
+                    htmlWriterWriter.startElement("h2").characters(tab.title).endElement();
+                    tab.renderer.render(model, htmlWriterWriter);
+                htmlWriterWriter.endElement();
+            }
+        htmlWriterWriter.endElement();
     }
 
     private class TabDefinition {
         final String title;
-        final DomReportRenderer<T> renderer;
+        final ReportRenderer<T, SimpleHtmlWriter> renderer;
 
-        private TabDefinition(String title, DomReportRenderer<T> renderer) {
+        private TabDefinition(String title, ReportRenderer<T, SimpleHtmlWriter> renderer) {
             this.title = title;
             this.renderer = renderer;
         }
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TextDomReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TextDomReportRenderer.java
deleted file mode 100644
index cb6e3f0..0000000
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/TextDomReportRenderer.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.reporting;
-
-import org.gradle.internal.UncheckedException;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import javax.xml.parsers.DocumentBuilderFactory;
-import java.io.ByteArrayInputStream;
-import java.io.StringWriter;
-
-public class TextDomReportRenderer<T> extends DomReportRenderer<T> {
-    private final TextReportRenderer<T> renderer;
-
-    public TextDomReportRenderer(TextReportRenderer<T> renderer) {
-        this.renderer = renderer;
-    }
-
-    @Override
-    public void render(T model, Element parent) {
-        try {
-            StringWriter writer = new StringWriter();
-            renderer.writeTo(model, writer);
-            Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(writer.toString().getBytes()));
-            NodeList children = document.getDocumentElement().getChildNodes();
-            for (int i = 0; i < children.getLength(); i++) {
-                Node adopted = parent.getOwnerDocument().importNode(children.item(i), true);
-                parent.appendChild(adopted);
-            }
-        } catch (Exception e) {
-            throw UncheckedException.throwAsUncheckedException(e);
-        }
-    }
-}
diff --git a/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java b/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
index c2c4303..6832c1a 100644
--- a/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
+++ b/subprojects/core/src/main/groovy/org/gradle/reporting/TextReportRenderer.java
@@ -31,7 +31,7 @@ public abstract class TextReportRenderer<T> {
      * Renders the report for the given model to a file.
      */
     public void writeTo(final T model, File file) {
-        IoActions.writeFile(file, "utf-8", new ErroringAction<Writer>() {
+        IoActions.writeTextFile(file, "utf-8", new ErroringAction<Writer>() {
             @Override
             protected void doExecute(Writer writer) throws Exception {
                 writeTo(model, writer);
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java b/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
index 0823741..a3ef83e 100755
--- a/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/DeprecationLogger.java
@@ -15,220 +15,4 @@
  */
 package org.gradle.util;
 
-import org.apache.commons.lang.StringUtils;
-import org.codehaus.groovy.runtime.StackTraceUtils;
-import org.gradle.internal.Factory;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-public class DeprecationLogger {
-    private static final Logger LOGGER = LoggerFactory.getLogger(DeprecationLogger.class);
-    private static final Set<String> PLUGINS = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> TASKS = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> METHODS = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> DYNAMIC_PROPERTIES = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> PROPERTIES = Collections.synchronizedSet(new HashSet<String>());
-    private static final Set<String> NAMED_PARAMETERS = Collections.synchronizedSet(new HashSet<String>());
-    
-    private static final ThreadLocal<Boolean> ENABLED = new ThreadLocal<Boolean>() {
-        @Override
-        protected Boolean initialValue() {
-            return true;
-        }
-    };
-
-    private static final ThreadLocal<Boolean> LOG_TRACE = new ThreadLocal<Boolean>() {
-        @Override
-        protected Boolean initialValue() {
-            return false;
-        }
-    };
-
-    public static final String ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME = "org.gradle.deprecation.trace";
-
-    private static String deprecationMessage;
-    private static Lock deprecationMessageLock = new ReentrantLock();
-
-    private static String getDeprecationMessage() {
-        if (deprecationMessage == null) {
-            deprecationMessageLock.lock();
-            try {
-                if (deprecationMessage == null) {
-                    String messageBase = "has been deprecated and is scheduled to be removed in";
-                    String when;
-
-                    GradleVersion currentVersion = GradleVersion.current();
-                    int versionMajor = currentVersion.getMajor();
-                    if (versionMajor == -1) { // don't understand version number
-                        when = "the next major version of Gradle";
-                    } else {
-                        when = String.format("Gradle %d.0", versionMajor + 1);
-                    }
-
-                    deprecationMessage = String.format("%s %s", messageBase, when);
-                }
-            } finally {
-                deprecationMessageLock.unlock();
-            }
-        }
-
-        return deprecationMessage;
-    }
-
-    public static void reset() {
-        PLUGINS.clear();
-        METHODS.clear();
-        PROPERTIES.clear();
-        NAMED_PARAMETERS.clear();
-        DYNAMIC_PROPERTIES.clear();
-    }
-
-    public static void nagUserOfReplacedPlugin(String pluginName, String replacement) {
-        if (isEnabled() && PLUGINS.add(pluginName)) {
-            LOGGER.warn(String.format(
-                    "The %s plugin %S. Please use the %s plugin instead.",
-                    pluginName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
-    }
-
-    public static void nagUserOfReplacedTaskType(String taskName, String replacement) {
-        if (isEnabled() && TASKS.add(taskName)) {
-            LOGGER.warn(String.format(
-                    "The %s task type %s. Please use the %s instead.",
-                    taskName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
-    }
-    
-    public static void nagUserOfReplacedMethod(String methodName, String replacement) {
-        if (isEnabled() && METHODS.add(methodName)) {
-            LOGGER.warn(String.format(
-                    "The %s method %s. Please use the %s method instead.",
-                    methodName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
-    }
-
-    public static void nagUserOfReplacedProperty(String propertyName, String replacement) {
-        if (isEnabled() && PROPERTIES.add(propertyName)) {
-            LOGGER.warn(String.format(
-                    "The %s property %s. Please use the %s property instead.",
-                    propertyName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
-    }
-
-    public static void nagUserOfDiscontinuedMethod(String methodName) {
-        if (isEnabled() && METHODS.add(methodName)) {
-            LOGGER.warn(String.format("The %s method %s.",
-                    methodName, getDeprecationMessage()));
-            logTraceIfNecessary();
-        }
-    }
-
-    public static void nagUserOfDiscontinuedProperty(String propertyName, String advice) {
-        if (isEnabled() && PROPERTIES.add(propertyName)) {
-            LOGGER.warn(String.format("The %s property %s. %s",
-                    propertyName, getDeprecationMessage(), advice));
-            logTraceIfNecessary();
-        }
-    }
-
-    public static void nagUserOfReplacedNamedParameter(String parameterName, String replacement) {
-        if (isEnabled() && NAMED_PARAMETERS.add(parameterName)) {
-            LOGGER.warn(String.format(
-                    "The %s named parameter %s. Please use the %s named parameter instead.",
-                    parameterName, getDeprecationMessage(), replacement));
-            logTraceIfNecessary();
-        }
-    }
-
-    /**
-     * Try to avoid using this nagging method. The other methods use a consistent wording for when things will be removed.
-     */
-    public static void nagUserWith(String message) {
-        if (isEnabled() && METHODS.add(message)) {
-            LOGGER.warn(message);
-            logTraceIfNecessary();
-        }
-    }
-
-    /**
-     * Avoid using this method, use the variant with an explanation instead.
-     */
-    public static void nagUserOfDeprecated(String thing) {
-        nagUserWith(String.format("%s %s", thing, getDeprecationMessage()));
-    }
-
-    public static void nagUserOfDeprecated(String thing, String explanation) {
-        nagUserWith(String.format("%s %s. %s.", thing, getDeprecationMessage(), explanation));
-    }
-
-    public static void nagUserOfDeprecatedBehaviour(String behaviour) {
-        nagUserOfDeprecated(String.format("%s. This behaviour", behaviour));
-    }
-
-    public static <T> T whileDisabled(Factory<T> factory) {
-        ENABLED.set(false);
-        try {
-            return factory.create();
-        } finally {
-            ENABLED.set(true);
-        }
-    }
-
-    public static void whileDisabled(Runnable action) {
-        ENABLED.set(false);
-        try {
-            action.run();
-        } finally {
-            ENABLED.set(true);
-        }
-    }
-
-    private static boolean isTraceLoggingEnabled() {
-        return Boolean.getBoolean(ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME) || LOG_TRACE.get();
-    }
-
-    private static void logTraceIfNecessary() {
-        if (isTraceLoggingEnabled()) {
-            StackTraceElement[] stack = StackTraceUtils.sanitize(new Exception()).getStackTrace();
-            for (StackTraceElement frame : stack) {
-                if (!frame.getClassName().startsWith(DeprecationLogger.class.getName())) {
-                    LOGGER.warn("    {}", frame.toString());
-                }
-            }
-        }
-    }
-
-    private static boolean isEnabled() {
-        return ENABLED.get();
-    }
-    
-    public static void setLogTrace(boolean flag) {
-        LOG_TRACE.set(flag);
-    }
-
-    public static void nagUserAboutDynamicProperty(String propertyName, Object target, Object value) {
-        if (!isEnabled()) {
-            return;
-        }
-        nagUserOfDeprecated("Creating properties on demand (a.k.a. dynamic properties)", "Please read http://gradle.org/docs/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html for information on the replacement for dynamic properties");
-
-        String propertyWithClass = target.getClass().getName() + "." + propertyName;
-        if (DYNAMIC_PROPERTIES.add(propertyWithClass)) {
-            String propertyWithTarget = String.format("\"%s\" on \"%s\"", propertyName, target);
-            String theValue = (value==null)? "null" : StringUtils.abbreviate(value.toString(), 25);
-            nagUserWith(String.format("Deprecated dynamic property: %s, value: \"%s\".", propertyWithTarget, theValue));
-        } else {
-            nagUserWith(String.format("Deprecated dynamic property \"%s\" created in multiple locations.", propertyName));
-        }
-    }
-}
\ No newline at end of file
+public class DeprecationLogger extends SingleMessageLogger {}
\ No newline at end of file
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/SingleMessageLogger.java b/subprojects/core/src/main/groovy/org/gradle/util/SingleMessageLogger.java
new file mode 100644
index 0000000..647345f
--- /dev/null
+++ b/subprojects/core/src/main/groovy/org/gradle/util/SingleMessageLogger.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.util;
+
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.groovy.runtime.StackTraceUtils;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.internal.Factory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class SingleMessageLogger {
+
+    private static final Logger LOGGER = Logging.getLogger(DeprecationLogger.class);
+    private static final Set<String> PLUGINS = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> TASKS = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> METHODS = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> DYNAMIC_PROPERTIES = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> PROPERTIES = Collections.synchronizedSet(new HashSet<String>());
+    private static final Set<String> NAMED_PARAMETERS = Collections.synchronizedSet(new HashSet<String>());
+
+    private static final ThreadLocal<Boolean> ENABLED = new ThreadLocal<Boolean>() {
+        @Override
+        protected Boolean initialValue() {
+            return true;
+        }
+    };
+
+    private static final ThreadLocal<Boolean> LOG_TRACE = new ThreadLocal<Boolean>() {
+        @Override
+        protected Boolean initialValue() {
+            return false;
+        }
+    };
+
+    public static final String ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME = "org.gradle.deprecation.trace";
+
+    private static String deprecationMessage;
+    private static Lock deprecationMessageLock = new ReentrantLock();
+    public static final String INCUBATION_MESSAGE = "%s is an incubating feature. Enjoy it and let us know how it works for you.";
+
+    private static String getDeprecationMessage() {
+        if (deprecationMessage == null) {
+            deprecationMessageLock.lock();
+            try {
+                if (deprecationMessage == null) {
+                    String messageBase = "has been deprecated and is scheduled to be removed in";
+                    String when;
+
+                    GradleVersion currentVersion = GradleVersion.current();
+                    int versionMajor = currentVersion.getMajor();
+                    if (versionMajor == -1) { // don't understand version number
+                        when = "the next major version of Gradle";
+                    } else {
+                        when = String.format("Gradle %d.0", versionMajor + 1);
+                    }
+
+                    deprecationMessage = String.format("%s %s", messageBase, when);
+                }
+            } finally {
+                deprecationMessageLock.unlock();
+            }
+        }
+
+        return deprecationMessage;
+    }
+
+    public static void reset() {
+        PLUGINS.clear();
+        METHODS.clear();
+        PROPERTIES.clear();
+        NAMED_PARAMETERS.clear();
+        DYNAMIC_PROPERTIES.clear();
+    }
+
+    public static void nagUserOfReplacedPlugin(String pluginName, String replacement) {
+        if (isEnabled() && PLUGINS.add(pluginName)) {
+            LOGGER.warn(String.format(
+                    "The %s plugin %S. Please use the %s plugin instead.",
+                    pluginName, getDeprecationMessage(), replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfReplacedTaskType(String taskName, String replacement) {
+        if (isEnabled() && TASKS.add(taskName)) {
+            LOGGER.warn(String.format(
+                    "The %s task type %s. Please use the %s instead.",
+                    taskName, getDeprecationMessage(), replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfReplacedMethod(String methodName, String replacement) {
+        if (isEnabled() && METHODS.add(methodName)) {
+            LOGGER.warn(String.format(
+                    "The %s method %s. Please use the %s method instead.",
+                    methodName, getDeprecationMessage(), replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfReplacedProperty(String propertyName, String replacement) {
+        if (isEnabled() && PROPERTIES.add(propertyName)) {
+            LOGGER.warn(String.format(
+                    "The %s property %s. Please use the %s property instead.",
+                    propertyName, getDeprecationMessage(), replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfDiscontinuedMethod(String methodName) {
+        if (isEnabled() && METHODS.add(methodName)) {
+            LOGGER.warn(String.format("The %s method %s.",
+                    methodName, getDeprecationMessage()));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfDiscontinuedProperty(String propertyName, String advice) {
+        if (isEnabled() && PROPERTIES.add(propertyName)) {
+            LOGGER.warn(String.format("The %s property %s. %s",
+                    propertyName, getDeprecationMessage(), advice));
+            logTraceIfNecessary();
+        }
+    }
+
+    public static void nagUserOfReplacedNamedParameter(String parameterName, String replacement) {
+        if (isEnabled() && NAMED_PARAMETERS.add(parameterName)) {
+            LOGGER.warn(String.format(
+                    "The %s named parameter %s. Please use the %s named parameter instead.",
+                    parameterName, getDeprecationMessage(), replacement));
+            logTraceIfNecessary();
+        }
+    }
+
+    /**
+     * Try to avoid using this nagging method. The other methods use a consistent wording for when things will be removed.
+     */
+    public static void nagUserWith(String message) {
+        inform(LogLevel.WARN, message);
+        logTraceIfNecessary();
+    }
+
+    /**
+     * Try to avoid using this nagging method. The other methods use a consistent wording for when things will be removed.
+     */
+    public static void inform(LogLevel level, String message) {
+        if (isEnabled() && METHODS.add(message)) {
+            LOGGER.log(level, message);
+        }
+    }
+
+    /**
+     * Avoid using this method, use the variant with an explanation instead.
+     */
+    public static void nagUserOfDeprecated(String thing) {
+        nagUserWith(String.format("%s %s", thing, getDeprecationMessage()));
+    }
+
+    public static void nagUserOfDeprecated(String thing, String explanation) {
+        nagUserWith(String.format("%s %s. %s.", thing, getDeprecationMessage(), explanation));
+    }
+
+    public static void nagUserOfDeprecatedBehaviour(String behaviour) {
+        nagUserOfDeprecated(String.format("%s. This behaviour", behaviour));
+    }
+
+    public static <T> T whileDisabled(Factory<T> factory) {
+        ENABLED.set(false);
+        try {
+            return factory.create();
+        } finally {
+            ENABLED.set(true);
+        }
+    }
+
+    public static void whileDisabled(Runnable action) {
+        ENABLED.set(false);
+        try {
+            action.run();
+        } finally {
+            ENABLED.set(true);
+        }
+    }
+
+    private static boolean isTraceLoggingEnabled() {
+        return Boolean.getBoolean(ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME) || LOG_TRACE.get();
+    }
+
+    private static void logTraceIfNecessary() {
+        if (isTraceLoggingEnabled()) {
+            StackTraceElement[] stack = StackTraceUtils.sanitize(new Exception()).getStackTrace();
+            for (StackTraceElement frame : stack) {
+                if (!frame.getClassName().startsWith(DeprecationLogger.class.getName())) {
+                    LOGGER.warn("    {}", frame.toString());
+                }
+            }
+        }
+    }
+
+    private static boolean isEnabled() {
+        return ENABLED.get();
+    }
+
+    public static void setLogTrace(boolean flag) {
+        LOG_TRACE.set(flag);
+    }
+
+    public static void nagUserAboutDynamicProperty(String propertyName, Object target, Object value) {
+        if (!isEnabled()) {
+            return;
+        }
+        nagUserOfDeprecated("Creating properties on demand (a.k.a. dynamic properties)", "Please read http://gradle.org/docs/current/dsl/org.gradle.api.plugins.ExtraPropertiesExtension.html for information on the replacement for dynamic properties");
+
+        String propertyWithClass = target.getClass().getName() + "." + propertyName;
+        if (DYNAMIC_PROPERTIES.add(propertyWithClass)) {
+            String propertyWithTarget = String.format("\"%s\" on \"%s\"", propertyName, target);
+            String theValue = (value==null)? "null" : StringUtils.abbreviate(value.toString(), 25);
+            nagUserWith(String.format("Deprecated dynamic property: %s, value: \"%s\".", propertyWithTarget, theValue));
+        } else {
+            nagUserWith(String.format("Deprecated dynamic property \"%s\" created in multiple locations.", propertyName));
+        }
+    }
+
+    public static void informAboutIncubating(String incubatingFeature) {
+        inform(LogLevel.LIFECYCLE, String.format(INCUBATION_MESSAGE, incubatingFeature));
+    }
+}
diff --git a/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java b/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java
index c32214e..3ad64b6 100644
--- a/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java
+++ b/subprojects/core/src/main/groovy/org/gradle/util/VersionNumber.java
@@ -26,6 +26,8 @@ import java.util.regex.Pattern;
 
 /**
  * Represents, parses, and compares version numbers following a major.minor.micro-qualifier format.
+ * Note that this class considers "2.10.0" less than "2.10.0-something", presumably to make it easier to
+ * test for "less than any 2.10 version" and "greater than any 2.10 version".
  */
 public class VersionNumber implements Comparable<VersionNumber> {
     public static final VersionNumber UNKNOWN = new VersionNumber(0, 0, 0, null);
diff --git a/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt b/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt
index 5924cf4..1d22590 100644
--- a/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt
+++ b/subprojects/core/src/main/resources/org/gradle/configuration/default-imports.txt
@@ -9,6 +9,8 @@ import org.gradle.api.artifacts.specs.*
 import org.gradle.api.publish.*
 import org.gradle.api.publish.ivy.*
 import org.gradle.api.publish.ivy.tasks.*
+import org.gradle.api.publish.maven.*
+import org.gradle.api.publish.maven.tasks.*
 import org.gradle.api.execution.*
 import org.gradle.api.file.*
 import org.gradle.api.resources.*
diff --git a/subprojects/core/src/main/resources/org/gradle/profile/ProfileTemplate.html b/subprojects/core/src/main/resources/org/gradle/profile/ProfileTemplate.html
deleted file mode 100644
index 5c3531b..0000000
--- a/subprojects/core/src/main/resources/org/gradle/profile/ProfileTemplate.html
+++ /dev/null
@@ -1,115 +0,0 @@
-<body>
-<div id="tabs">
-    <ul class="tabLinks">
-        <li><a href="#tab0">Summary</a></li>
-        <li><a href="#tab1">Configuration</a></li>
-        <li><a href="#tab2">Dependency Resolution</a></li>
-        <li><a href="#tab3">Task Execution</a></li>
-    </ul>
-    <div class="tab" id="tab0">
-        <h2>Summary</h2>
-        <table>
-            <thead><tr><th>Description</th><th class="numeric">Duration</th></tr></thead>
-            <tr>
-                <td>Total Build Time</td>
-                <td class="numeric">${time.format(build.elapsedTotal)}</td>
-            </tr>
-            <tr>
-                <td>Startup</td>
-                <td class="numeric">${time.format(build.elapsedStartup)}</td>
-            </tr>
-            <tr>
-                <td>Settings and BuildSrc</td>
-                <td class="numeric">${time.format(build.elapsedSettings)}</td>
-            </tr>
-            <tr>
-                <td>Loading Projects</td>
-                <td class="numeric">${time.format(build.elapsedProjectsLoading)}</td>
-            </tr>
-            <tr>
-                <td>Configuring Projects</td>
-                <td class="numeric">${time.format(build.elapsedProjectsEvaluated)}</td>
-            </tr>
-            <tr>
-                <td>Task Execution</td>
-                <td class="numeric">${time.format(build.elapsedTotalExecutionTime)}</td>
-            </tr>
-        </table>
-    </div>
-    <div class="tab" id="tab1">
-        <h2>Configuration</h2>
-        <table>
-            <thead><tr><th>Project</th><th class="numeric">Duration</th></tr></thead>
-            <tr>
-                <td>All projects</td>
-                <td class="numeric">${time.format(build.projectConfiguration.elapsedTime)}</td>
-            </tr>
-            <%
-            def projects = build.projectConfiguration.operations
-            projects.sort { it.elapsedTime }
-            projects = projects.reverse()
-
-            for (def project : projects) {
-            %>
-            <tr>
-                <td>$project.path</td>
-                <td class="numeric">${time.format(project.elapsedTime)}</td>
-            </tr>
-            <% } %>
-        </table>
-    </div>
-    <div class="tab" id="tab2">
-        <h2>Dependency Resolution</h2>
-        <table>
-            <thead><tr><th>Dependencies</th><th class="numeric">Duration</th></tr></thead>
-            <tr>
-                <td>All dependencies</td>
-                <td class="numeric">${time.format(build.dependencySets.elapsedTime)}</td>
-            </tr>
-            <%
-            def dependencySets = build.dependencySets.operations
-            dependencySets.sort { it.elapsedTime }
-            dependencySets = dependencySets.reverse()
-            for (def dependencySet : dependencySets) {
-            %>
-            <tr>
-                <td>$dependencySet.path</td>
-                <td class="numeric">${time.format(dependencySet.elapsedTime)}</td>
-            </tr>
-            <% } %>
-        </table>
-    </div>
-    <div class="tab" id="tab3">
-        <h2>Task Execution</h2>
-        <table>
-            <thead><tr><th>Task</th><th class="numeric">Duration</th><th>Result</th></tr></thead>
-            <%
-            projects = build.projects
-            projects.sort { it.tasks.elapsedTime }
-            projects = projects.reverse()
-            for (def project : projects) {
-            %>
-            <tr>
-                <td>Project $project.path</td>
-                <td class="numeric">${time.format(project.tasks.elapsedTime)}</td>
-                <td>(total)</td>
-            </tr>
-            <%
-            def profiles = project.tasks.operations
-            profiles.sort { it.elapsedTime }
-            profiles = profiles.reverse()
-            for (def task : profiles) {
-            %>
-            <tr>
-                <td class="indentPath">${task.path}</td>
-                <td class="numeric">${time.format(task.elapsedTime)}</td>
-                <td><%= task.state.getSkipped() ? task.state.skipMessage : (task.state.didWork ? '' : 'Did No Work')%></td>
-            </tr>
-            <%
-            }
-            %>
-            <% } %>
-        </table>
-    </div>
-</div>
-</body>
diff --git a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
index 9928867..fccd6e5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/StartParameterTest.groovy
@@ -20,157 +20,225 @@ import org.gradle.api.logging.LogLevel
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
-import org.junit.Test
+import spock.lang.Specification
 
-import static org.gradle.util.Matchers.*
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.*
+import static org.gradle.util.Matchers.isSerializable
+import static org.junit.Assert.assertThat
 
 /**
  * @author Hans Dockter
  */
-class StartParameterTest {
-    @Rule
-    public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
-    @Rule
-    public SetSystemProperties systemProperties = new SetSystemProperties()
-
-    @Test public void testNewInstance() {
-        StartParameter testObj = new StartParameter()
-        testObj.settingsFile = 'settingsfile' as File
-        testObj.buildFile = 'buildfile' as File
-        testObj.taskNames = ['a']
-        testObj.buildProjectDependencies = true
-        testObj.currentDir = new File('a')
-        testObj.searchUpwards = false
-        testObj.projectProperties = [a: 'a']
-        testObj.systemPropertiesArgs = [b: 'b']
-        testObj.gradleUserHomeDir = new File('b')
-        testObj.initScripts = [new File('init script'), new File("/path/to/another init script")]
-        testObj.cacheUsage = CacheUsage.ON
-        testObj.logLevel = LogLevel.WARN
-        testObj.colorOutput = false
-        testObj.continueOnFailure = true
-        testObj.rerunTasks = true;
-        testObj.refreshDependencies = true;
-        testObj.recompileScripts = true;
-
-        StartParameter startParameter = testObj.newInstance()
-        assertEquals(testObj, startParameter)
+class StartParameterTest extends Specification {
+    @Rule private TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    @Rule private SetSystemProperties systemProperties = new SetSystemProperties()
+
+    void "new instance has correct state"() {
+        def parameter = new StartParameter()
+        parameter.settingsFile = 'settingsfile' as File
+        parameter.buildFile = 'buildfile' as File
+        parameter.taskNames = ['a']
+        parameter.buildProjectDependencies = true
+        parameter.currentDir = new File('a')
+        parameter.searchUpwards = false
+        parameter.projectProperties = [a: 'a']
+        parameter.systemPropertiesArgs = [b: 'b']
+        parameter.gradleUserHomeDir = new File('b')
+        parameter.initScripts = [new File('init script'), new File("/path/to/another init script")]
+        parameter.cacheUsage = CacheUsage.ON
+        parameter.logLevel = LogLevel.WARN
+        parameter.colorOutput = false
+        parameter.continueOnFailure = true
+        parameter.rerunTasks = true
+        parameter.refreshDependencies = true
+        parameter.recompileScripts = true
+        parameter.configureOnDemand = true
+
+        when:
+        def newInstance = parameter.newInstance()
+
+        then:
+        parameter == newInstance
+
+        when:
+        newInstance.continueOnFailure = false
+
+        then:
+        parameter != newInstance
     }
 
-    @Test public void testDefaultValues() {
-        StartParameter parameter = new StartParameter();
-        assertThat(parameter.gradleUserHomeDir, equalTo(StartParameter.DEFAULT_GRADLE_USER_HOME))
-        assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
-
-        assertThat(parameter.buildFile, nullValue())
-        assertThat(parameter.settingsFile, nullValue())
-
-        assertThat(parameter.logLevel, equalTo(LogLevel.LIFECYCLE))
-        assertTrue(parameter.colorOutput)
-        assertThat(parameter.taskNames, isEmpty())
-        assertThat(parameter.excludedTaskNames, isEmpty())
-        assertThat(parameter.projectProperties, isEmptyMap())
-        assertThat(parameter.systemPropertiesArgs, isEmptyMap())
-        assertFalse(parameter.dryRun)
-        assertFalse(parameter.continueOnFailure)
-        assertThat(parameter.refreshOptions, equalTo(RefreshOptions.NONE))
-        assertThat(parameter.rerunTasks, equalTo(false))
-        assertThat(parameter.recompileScripts, equalTo(false))
-        assertFalse(parameter.refreshDependencies)
+    void "mutable collections are not shared"() {
+        def parameter = new StartParameter()
+        parameter.taskNames = ['a']
+        parameter.excludedTaskNames = ['foo']
+        parameter.projectProperties = [a: 'a']
+        parameter.systemPropertiesArgs = [b: 'b']
+        parameter.initScripts = [new File('init script'), new File("/path/to/another init script")]
+
+        when:
+        def newInstance = parameter.newInstance()
+
+        then:
+        !parameter.initScripts.is(newInstance.initScripts)
+        !parameter.taskNames.is(newInstance.taskNames)
+        !parameter.excludedTaskNames.is(newInstance.excludedTaskNames)
+        !parameter.projectProperties.is(newInstance.projectProperties)
+        !parameter.systemPropertiesArgs.is(newInstance.systemPropertiesArgs)
+
+        and:
+        parameter.initScripts == newInstance.initScripts
+        parameter.taskNames == newInstance.taskNames
+        parameter.excludedTaskNames == newInstance.excludedTaskNames
+        parameter.projectProperties == newInstance.projectProperties
+        parameter.systemPropertiesArgs == newInstance.systemPropertiesArgs
+    }
+
+    void "default values"() {
+        def parameter = new StartParameter()
+
+        expect:
+        parameter.gradleUserHomeDir == StartParameter.DEFAULT_GRADLE_USER_HOME
+        parameter.currentDir == new File(System.getProperty("user.dir")).getCanonicalFile()
+
+        parameter.buildFile == null
+        parameter.settingsFile == null
+
+        parameter.logLevel == LogLevel.LIFECYCLE
+        parameter.colorOutput
+        parameter.taskNames.empty
+        parameter.excludedTaskNames.empty
+        parameter.projectProperties.isEmpty()
+        parameter.systemPropertiesArgs.isEmpty()
+        !parameter.dryRun
+        !parameter.continueOnFailure
+        parameter.refreshOptions == RefreshOptions.NONE
+        !parameter.rerunTasks
+        !parameter.recompileScripts
+        !parameter.refreshDependencies
+
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testDefaultWithGradleUserHomeSystemProp() {
-        File gradleUserHome = tmpDir.file("someGradleUserHomePath")
+    void "uses gradle user home system property"() {
+        def gradleUserHome = tmpDir.file("someGradleUserHomePath")
         System.setProperty(StartParameter.GRADLE_USER_HOME_PROPERTY_KEY, gradleUserHome.absolutePath)
-        StartParameter parameter = new StartParameter();
-        assertThat(parameter.gradleUserHomeDir, equalTo(gradleUserHome))
+
+        when:
+        def parameter = new StartParameter()
+        then:
+        parameter.gradleUserHomeDir == gradleUserHome
     }
 
-    @Test public void testSetCurrentDir() {
+    void "canonicalizes current dir"() {
         StartParameter parameter = new StartParameter()
         File dir = new File('current')
+
+        when:
         parameter.currentDir = dir
 
-        assertThat(parameter.currentDir, equalTo(dir.canonicalFile))
+        then:
+        parameter.currentDir == dir.canonicalFile
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testSetBuildFile() {
+    void "can configure build file"() {
         StartParameter parameter = new StartParameter()
         File file = new File('test/build file')
+
+        when:
         parameter.buildFile = file
 
-        assertThat(parameter.buildFile, equalTo(file.canonicalFile))
-        assertThat(parameter.currentDir, equalTo(file.canonicalFile.parentFile))
+        then:
+        parameter.buildFile == file.canonicalFile
+        parameter.currentDir == file.canonicalFile.parentFile
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testSetNullBuildFile() {
+    void "can configure null build file"() {
         StartParameter parameter = new StartParameter()
         parameter.buildFile = new File('test/build file')
+
+        when:
         parameter.buildFile = null
 
-        assertThat(parameter.buildFile, nullValue())
-        assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
-        assertThat(parameter.initScripts, equalTo(Collections.emptyList()))
+        then:
+        parameter.buildFile == null
+        parameter.currentDir == new File(System.getProperty("user.dir")).getCanonicalFile()
+        parameter.initScripts.empty
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testSetProjectDir() {
+    void "can configure project dir"() {
         StartParameter parameter = new StartParameter()
         File file = new File('test/project dir')
+
+        when:
         parameter.projectDir = file
 
-        assertThat(parameter.currentDir, equalTo(file.canonicalFile))
+        then:
+        parameter.currentDir == file.canonicalFile
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testSetNullProjectDir() {
+    void "can configure null project dir"() {
         StartParameter parameter = new StartParameter()
         parameter.projectDir = new File('test/project dir')
+
+        when:
         parameter.projectDir = null
 
-        assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
+        then:
+        parameter.currentDir == new File(System.getProperty("user.dir")).getCanonicalFile()
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testSetSettingsFile() {
+    void "can configure settings file"() {
         StartParameter parameter = new StartParameter()
         File file = new File('some dir/settings file')
+
+        when:
         parameter.settingsFile = file
 
-        assertThat(parameter.currentDir, equalTo(file.canonicalFile.parentFile))
-        assertThat(parameter.settingsFile, equalTo(file.canonicalFile))
+        then:
+        parameter.currentDir == file.canonicalFile.parentFile
+        parameter.settingsFile == file.canonicalFile
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testSetNullSettingsFile() {
+    void "can configure null settings file"() {
         StartParameter parameter = new StartParameter()
+
+        when:
         parameter.settingsFile = null
 
-        assertThat(parameter.settingsFile, nullValue())
+        then:
+        parameter.settingsFile == null
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testUseEmptySettingsScript() {
-        StartParameter parameter = new StartParameter();
+    void "can use empty settings script"() {
+        StartParameter parameter = new StartParameter()
+
+        when:
         parameter.useEmptySettings()
-        assertThat(parameter.settingsFile, nullValue())
-        assertThat(parameter.searchUpwards, equalTo(false))
+
+        then:
+        parameter.settingsFile == null
+        !parameter.searchUpwards
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testSetNullUserHomeDir() {
+    void "can configure null user home dir"() {
         StartParameter parameter = new StartParameter()
+
+        when:
         parameter.gradleUserHomeDir = null
-        assertThat(parameter.gradleUserHomeDir, equalTo(StartParameter.DEFAULT_GRADLE_USER_HOME))
+
+        then:
+        parameter.gradleUserHomeDir == StartParameter.DEFAULT_GRADLE_USER_HOME
         assertThat(parameter, isSerializable())
     }
 
-    @Test public void testNewBuild() {
+    void "creates parameter for new build"() {
         StartParameter parameter = new StartParameter()
 
         // Copied properties
@@ -178,6 +246,7 @@ class StartParameterTest {
         parameter.cacheUsage = CacheUsage.REBUILD
         parameter.logLevel = LogLevel.DEBUG
         parameter.colorOutput = false
+        parameter.configureOnDemand = true
 
         // Non-copied
         parameter.currentDir = new File("other")
@@ -193,66 +262,84 @@ class StartParameterTest {
 
         assertThat(parameter, isSerializable())
 
-        StartParameter newParameter = parameter.newBuild();
-
-        assertThat(newParameter, not(equalTo(parameter)));
-
-        assertThat(newParameter.gradleUserHomeDir, equalTo(parameter.gradleUserHomeDir));
-        assertThat(newParameter.cacheUsage, equalTo(parameter.cacheUsage));
-        assertThat(newParameter.logLevel, equalTo(parameter.logLevel));
-        assertThat(newParameter.colorOutput, equalTo(parameter.colorOutput));
-        assertThat(newParameter.continueOnFailure, equalTo(parameter.continueOnFailure))
-        assertThat(newParameter.refreshDependencies, equalTo(parameter.refreshDependencies))
-        assertThat(newParameter.rerunTasks, equalTo(parameter.rerunTasks))
-        assertThat(newParameter.recompileScripts, equalTo(parameter.recompileScripts))
-
-        assertThat(newParameter.buildFile, nullValue())
-        assertThat(newParameter.taskNames, isEmpty())
-        assertThat(newParameter.excludedTaskNames, isEmpty())
-        assertThat(newParameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
-        assertFalse(newParameter.dryRun)
+        when:
+        StartParameter newParameter = parameter.newBuild()
+
+        then:
+        newParameter != parameter
+
+        newParameter.configureOnDemand == parameter.configureOnDemand
+        newParameter.gradleUserHomeDir == parameter.gradleUserHomeDir
+        newParameter.cacheUsage == parameter.cacheUsage
+        newParameter.logLevel == parameter.logLevel
+        newParameter.colorOutput == parameter.colorOutput
+        newParameter.continueOnFailure == parameter.continueOnFailure
+        newParameter.refreshDependencies == parameter.refreshDependencies
+        newParameter.rerunTasks == parameter.rerunTasks
+        newParameter.recompileScripts == parameter.recompileScripts
+
+        newParameter.buildFile == null
+        newParameter.taskNames.empty
+        newParameter.excludedTaskNames.empty
+        newParameter.currentDir == new File(System.getProperty("user.dir")).getCanonicalFile()
+        !newParameter.dryRun
         assertThat(newParameter, isSerializable())
     }
     
-    void testMergingSystemProperties() {
-        def p = { args = null ->
-            def sp = new StartParameter()
-            if (args) {
-                sp.systemPropertyArgs = args
-            }
-            sp.mergedSystemProperties.sort()
-        }
-        
+    void "system properties are merged"() {
+        def parameter = new StartParameter()
+
         System.properties.clear()
-        System.properties.a = "1"
-    
-        assert p(b: "2") == [a: "1", b: "2"]
-        assert p(a: "2", b: "3") == [a: "2", b: "3"]
-        assert p() == [a: "1"]
+        System.properties.a = "sys a"
+        System.properties.c = "sys c"
+
+        parameter.systemPropertiesArgs= [a: 'a', b: 'b']
+
+        expect:
+        parameter.mergedSystemProperties.sort() == [a: 'a', b: 'b', c: 'sys c']
     }
 
-    @Test
-    public void testGetAllInitScripts() {
+    void "gets all init scripts"() {
         def gradleUserHomeDir = tmpDir.testDirectory.createDir("gradleUserHomeDie")
         def gradleHomeDir = tmpDir.testDirectory.createDir("gradleHomeDir")
         StartParameter parameter = new StartParameter()
 
+        when:
         parameter.gradleUserHomeDir = gradleUserHomeDir
         parameter.gradleHomeDir = gradleHomeDir
 
-        assert parameter.allInitScripts.empty
+        then:
+        parameter.allInitScripts.empty
 
+        when:
         def userMainInit = gradleUserHomeDir.createFile("init.gradle")
-        assert parameter.allInitScripts == [userMainInit]
+        then:
+        parameter.allInitScripts == [userMainInit]
 
+        when:
         def userInit1 = gradleUserHomeDir.createFile("init.d/1.gradle")
         def userInit2 = gradleUserHomeDir.createFile("init.d/2.gradle")
 
-        assert parameter.allInitScripts == [userMainInit, userInit1, userInit2]
+        then:
+        parameter.allInitScripts == [userMainInit, userInit1, userInit2]
 
+        when:
         def distroInit1 = gradleHomeDir.createFile("init.d/1.gradle")
         def distroInit2 = gradleHomeDir.createFile("init.d/2.gradle")
 
-        assert parameter.allInitScripts == [userMainInit, userInit1, userInit2, distroInit1, distroInit2]
+        then:
+        parameter.allInitScripts == [userMainInit, userInit1, userInit2, distroInit1, distroInit2]
+    }
+
+    def "knows if parallel feature was configured"() {
+        def parameter = new StartParameter()
+        assert !parameter.parallelThreadCountConfigured
+
+        when:
+        parameter.setParallelThreadCount(15)
+
+        then:
+        parameter.parallelThreadCount == 15
+        parameter.parallelThreadCountConfigured
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
index 1b465d1..75d6cf1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/AbstractNamedDomainObjectContainerTest.groovy
@@ -48,6 +48,15 @@ class AbstractNamedDomainObjectContainerTest {
     }
 
     @Test
+    public void canUseMaybeCreateToFindOrCreateObjectWithName() {
+        def created = container.maybeCreate('obj')
+        assertThat(container.getByName('obj'), equalTo(['obj']))
+
+        def fetched = container.maybeCreate('obj')
+        assertThat(fetched, sameInstance(created))
+    }
+
+    @Test
     public void failsToAddObjectWhenObjectWithSameNameAlreadyInContainer() {
         container.create('obj')
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/ConfigureByMapActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/ConfigureByMapActionTest.groovy
index 38d42d9..acc0bdd 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/ConfigureByMapActionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/ConfigureByMapActionTest.groovy
@@ -93,6 +93,18 @@ class ConfigureByMapActionTest extends Specification {
         e.property == 'unknown'
     }
 
+    def "equality"() {
+        expect:
+        action([:]) == action([:])
+        action([:], []) == action([:], [])
+        action(p1: "v1") == action(p1: "v1")
+        action(p1: "v1") != action(p1: "v2")
+        action(p1: "v1") != action(p2: "v1")
+        action(p1: "v1", ["a"]) == action(p1: "v1", ["a"])
+        action(p1: "v1", ["a"]) != action(p1: "v1", ["b"])
+        action(p1: "v1", ["a"]) != action(p1: "v1")
+    }
+
 }
 
 class Bean {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerDslTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerDslTest.groovy
new file mode 100644
index 0000000..5c3b2a5
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerDslTest.groovy
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal
+
+import org.gradle.api.Named
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.util.HelperUtil
+
+import spock.lang.Specification
+
+class DefaultPolymorphicDomainObjectContainerDslTest extends Specification {
+    def fred = new DefaultPerson(name: "Fred")
+    def barney = new DefaultPerson(name: "Barney")
+    def agedFred = new DefaultAgeAwarePerson(name: "Fred", age: 42)
+    def agedBarney = new DefaultAgeAwarePerson(name: "Barney", age: 42)
+
+    def project = HelperUtil.createRootProject()
+    def instantiator = project.services.get(Instantiator)
+    def container = instantiator.newInstance(DefaultPolymorphicDomainObjectContainer, Person, instantiator)
+
+    def setup() {
+        project.extensions.add("container", container)
+    }
+
+    interface Person extends Named {}
+
+    static class DefaultPerson implements Person {
+        String name
+        String toString() { name }
+
+
+        boolean equals(DefaultPerson other) {
+            name == other.name
+        }
+
+        int hashCode() {
+            name.hashCode()
+        }
+    }
+
+    interface AgeAwarePerson extends Person {
+        int getAge()
+    }
+
+    static class DefaultAgeAwarePerson extends DefaultPerson implements AgeAwarePerson {
+        int age
+
+        boolean equals(DefaultAgeAwarePerson other) {
+            name == other.name && age == other.age
+        }
+
+        int hashCode() {
+            name.hashCode() * 31 + age
+        }
+    }
+
+    def "create elements with default type"() {
+        container.registerDefaultFactory({ new DefaultPerson(name: it) } as NamedDomainObjectFactory )
+
+        when:
+        project.container {
+            Fred
+            Barney {}
+        }
+
+        then:
+        container.size() == 2
+        container.findByName("Fred") == fred
+        container.findByName("Barney") == barney
+        container.asDynamicObject.getProperty("Fred") == fred
+        container.asDynamicObject.getProperty("Barney") == barney
+    }
+
+    def "configure elements with default type"() {
+
+    }
+
+    def "create elements with specified type"() {
+        container.registerFactory(Person, { new DefaultPerson(name: it) } as NamedDomainObjectFactory)
+        container.registerFactory(AgeAwarePerson, { new DefaultAgeAwarePerson(name: it, age: 42) } as NamedDomainObjectFactory)
+
+        when:
+        project.container {
+            Fred(Person) {}
+            Barney(AgeAwarePerson) {}
+        }
+
+        then:
+        container.size() == 2
+        container.findByName("Fred") == fred
+        container.findByName("Barney") == agedBarney
+        container.asDynamicObject.getProperty("Fred") == fred
+        container.asDynamicObject.getProperty("Barney") == agedBarney
+
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerTest.groovy
new file mode 100644
index 0000000..1f1ad01
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultPolymorphicDomainObjectContainerTest.groovy
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal
+
+import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Named
+import org.gradle.api.NamedDomainObjectFactory
+import org.gradle.internal.reflect.DirectInstantiator
+
+import spock.lang.Specification
+
+class DefaultPolymorphicDomainObjectContainerTest extends Specification {
+    def fred = new DefaultPerson(name: "fred")
+    def barney = new DefaultPerson(name: "barney")
+    def agedFred = new DefaultAgeAwarePerson(name: "fred", age: 42)
+    def agedBarney = new DefaultAgeAwarePerson(name: "barney", age: 42)
+
+    def container = new DefaultPolymorphicDomainObjectContainer<Person>(Person, new DirectInstantiator())
+
+    interface Person extends Named {}
+
+    static class DefaultPerson implements Person {
+        String name
+        String toString() { name }
+
+
+        boolean equals(DefaultPerson other) {
+            name == other.name
+        }
+
+        int hashCode() {
+            name.hashCode()
+        }
+    }
+
+    interface AgeAwarePerson extends Person {
+        int getAge()
+    }
+
+    static class DefaultAgeAwarePerson extends DefaultPerson implements AgeAwarePerson {
+        int age
+
+        boolean equals(DefaultAgeAwarePerson other) {
+            name == other.name && age == other.age
+        }
+
+        int hashCode() {
+            name.hashCode() * 31 + age
+        }
+    }
+
+    def "add elements"() {
+        when:
+        container.add(fred)
+        container.add(barney)
+
+        then:
+        container == [fred, barney] as Set
+    }
+
+    def "create elements without specifying type"() {
+        container.registerDefaultFactory({ new DefaultPerson(name: it) } as NamedDomainObjectFactory )
+
+        when:
+        container.create("fred")
+        container.create("barney")
+
+        then:
+        container.size() == 2
+        container.findByName("fred") == fred
+        container.findByName("barney") == barney
+        container.asDynamicObject.getProperty("fred") == fred
+        container.asDynamicObject.getProperty("barney") == barney
+    }
+
+    def "throws meaningful exception if it doesn't support creating domain objects without specifying a type"() {
+        container = new DefaultPolymorphicDomainObjectContainer<Person>(Person, new DirectInstantiator())
+
+        when:
+        container.create("fred")
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "This container does not support creating domain objects without specifying a type."
+    }
+
+    def "create elements with specified type"() {
+        container.registerFactory(Person, { new DefaultPerson(name: it) } as NamedDomainObjectFactory)
+        container.registerFactory(AgeAwarePerson, { new DefaultAgeAwarePerson(name: it, age: 42) } as NamedDomainObjectFactory)
+
+        when:
+        container.create("fred", Person)
+        container.create("barney", AgeAwarePerson)
+
+        then:
+        container.size() == 2
+        container.findByName("fred") == fred
+        container.findByName("barney") == agedBarney
+        container.asDynamicObject.getProperty("fred") == fred
+        container.asDynamicObject.getProperty("barney") == agedBarney
+    }
+
+    def "throws meaningful exception if it doesn't support creating domain objects with the specified type"() {
+        container = new DefaultPolymorphicDomainObjectContainer<Person>(Person, new DirectInstantiator())
+
+        when:
+        container.create("fred", Person)
+
+        then:
+        InvalidUserDataException e = thrown()
+        e.message == "This container does not support creating domain objects of type " +
+                "'org.gradle.api.internal.DefaultPolymorphicDomainObjectContainerTest\$Person'."
+    }
+
+    def "throws meaningful exception if factory element type is not a subtype of container element type"() {
+        when:
+        container.registerFactory(String, {} as NamedDomainObjectFactory)
+
+        then:
+        IllegalArgumentException e = thrown()
+        e.message == "Factory element type 'java.lang.String' is not a subtype of container element type " +
+                "'org.gradle.api.internal.DefaultPolymorphicDomainObjectContainerTest\$Person'"
+    }
+
+    def "fires events when elements are added"() {
+        Action<Person> action = Mock()
+
+        given:
+        container.all(action)
+
+        when:
+        container.addAll([fred, barney])
+
+        then:
+        1 * action.execute(fred)
+        1 * action.execute(barney)
+        0 * action._
+    }
+
+    def "can find all elements that match closure"() {
+        given:
+        container.addAll([fred, barney])
+
+        expect:
+        container.findAll { it != fred } == [barney] as Set
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
index 4e41756..2723ceb 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultArtifactRepositoryContainerTest.groovy
@@ -16,13 +16,12 @@
 
 package org.gradle.api.internal.artifacts
 
-import org.apache.ivy.plugins.resolver.FileSystemResolver
+import org.apache.ivy.plugins.resolver.DependencyResolver
 import org.gradle.api.Action
 import org.gradle.api.artifacts.ArtifactRepositoryContainer
 import org.gradle.api.artifacts.UnknownRepositoryException
 import org.gradle.api.artifacts.repositories.ArtifactRepository
 import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal
-import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository
 import org.gradle.internal.reflect.DirectInstantiator
 import org.gradle.internal.reflect.Instantiator
 import spock.lang.Specification
@@ -45,18 +44,20 @@ class DefaultArtifactRepositoryContainerTest extends Specification {
     }
 
     List setupNotation(int i, repositoryFactory = repositoryFactory) {
-        setupNotation("repoNotation$i", "repo$i", "resolver$i", repositoryFactory)
+        setupNotation("repoNotation$i", i == 1 ? "repository" : "repository${i-1}", "resolver$i", repositoryFactory)
     }
 
     List setupNotation(notation, repoName, resolverName, repositoryFactory = repositoryFactory) {
         def repo = Mock(ArtifactRepositoryInternal) { getName() >> repoName }
-        def resolver = new FileSystemResolver()
-        def resolverRepo = Spy(FixedResolverArtifactRepository, constructorArgs: [resolver])
+        def resolver = Mock(DependencyResolver)
+        def resolverRepo = Mock(ArtifactRepositoryInternal)
 
         interaction {
             1 * repositoryFactory.createRepository(notation) >> repo
             1 * repositoryFactory.toResolver(repo) >> resolver
             1 * repositoryFactory.createResolverBackedRepository(resolver) >> resolverRepo
+            1 * resolverRepo.setName(repoName)
+            _ * resolverRepo.getName() >> repoName
             1 * resolverRepo.onAddToContainer(container)
         }
 
@@ -70,7 +71,7 @@ class DefaultArtifactRepositoryContainerTest extends Specification {
 
         expect:
         container.addLast(repo1Notation).is resolver1
-        assert container.findByName(resolver1.name) != null
+        assert container.findByName("repository") != null
         container.addLast(repo2Notation)
         container == [resolverRepo1, resolverRepo2]
     }
@@ -85,31 +86,30 @@ class DefaultArtifactRepositoryContainerTest extends Specification {
         container.addLast(repo2Notation)
 
         then:
-        container*.name == ["repository", "repository2"]
+        container*.name == ["repository", "repository1"]
     }
 
     def testAddResolverWithClosure() {
         given:
         def repo = Mock(ArtifactRepositoryInternal) { getName() >> "name" }
-        def resolver = new FileSystemResolver()
-        def resolverRepo = Spy(FixedResolverArtifactRepository, constructorArgs: [resolver])
+        def resolver = Mock(DependencyResolver)
+        def resolverRepo = Mock(ArtifactRepositoryInternal)
 
         interaction {
             1 * repositoryFactory.createRepository(resolver) >> repo
             1 * repositoryFactory.toResolver(repo) >> resolver
             1 * repositoryFactory.createResolverBackedRepository(resolver) >> resolverRepo
+            _ * resolverRepo.name >> "bar"
             1 * resolverRepo.onAddToContainer(container)
         }
 
         when:
         container.add(resolver) {
-            transactional = "foo"
             name = "bar"
         }
 
         then:
-        resolver.transactional == "foo"
-        resolverRepo.name == "bar"
+        1 * resolver.setName("bar")
     }
 
     def testAddBefore() {
@@ -212,18 +212,6 @@ class DefaultArtifactRepositoryContainerTest extends Specification {
         container == [resolverRepo1, resolverRepo2]
     }
 
-    public void testAddWithUnnamedResolver() {
-        given:
-        def (repo1Notation, repo1, resolver1, resolverRepo1) = setupNotation(1)
-        resolver1.name = null
-
-        when:
-        container.addLast(repo1Notation)
-
-        then:
-        resolver1.name == 'repository'
-    }
-
     def testGetThrowsExceptionForUnknownResolver() {
         when:
         container.getByName("unknown")
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpecTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpecTest.groovy
deleted file mode 100644
index 6a4b075..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ModuleVersionSelectorStrictSpecTest.groovy
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts
-
-import spock.lang.Specification
-
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
-
-/**
- * by Szczepan Faber, created at: 9/10/12
- */
-class ModuleVersionSelectorStrictSpecTest extends Specification {
-
-    def "knows if matches the id"() {
-        def selector = newSelector("org", "util", "1.0")
-        def matching = newId("org", "util", "1.0")
-
-        def differentGroup = newId("xorg", "util", "1.0")
-        def differentName = newId("org", "xutil", "1.0")
-        def differentVersion = newId("org", "xutil", "x1.0")
-
-        expect:
-        selector.matchesStrictly(matching)
-
-        !selector.matchesStrictly(differentGroup)
-        !selector.matchesStrictly(differentName)
-        !selector.matchesStrictly(differentVersion)
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectDependenciesBuildInstructionTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectDependenciesBuildInstructionTest.java
deleted file mode 100644
index ef655ad..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/ProjectDependenciesBuildInstructionTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.artifacts;
-
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.not;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author Hans Dockter
- */
-public class ProjectDependenciesBuildInstructionTest {
-    @Test
-    public void initWithNull() {
-        ProjectDependenciesBuildInstruction buildInstruction = new ProjectDependenciesBuildInstruction(false);
-        assertThat(buildInstruction.isRebuild(), equalTo(false));
-    }
-
-    @Test
-    public void initWithEmptyList() {
-        ProjectDependenciesBuildInstruction buildInstruction = new ProjectDependenciesBuildInstruction(true);
-        assertThat(buildInstruction.isRebuild(), equalTo(true));
-    }
-
-    @Test
-    public void equality() {
-        assertThat(new ProjectDependenciesBuildInstruction(false),
-                equalTo(new ProjectDependenciesBuildInstruction(false)));
-        assertThat(new ProjectDependenciesBuildInstruction(true),
-                equalTo(new ProjectDependenciesBuildInstruction(true)));
-
-        assertThat(new ProjectDependenciesBuildInstruction(false),
-                not(equalTo(new ProjectDependenciesBuildInstruction(true))));
-    }
-
-    @Test
-    public void hashCodeEquality() {
-        assertThat(new ProjectDependenciesBuildInstruction(false).hashCode(),
-                equalTo(new ProjectDependenciesBuildInstruction(false).hashCode()));
-        assertThat(new ProjectDependenciesBuildInstruction(true).hashCode(),
-                equalTo(new ProjectDependenciesBuildInstruction(true).hashCode()));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencySpec.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencySpec.groovy
new file mode 100644
index 0000000..653f467
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencySpec.groovy
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.artifacts.dependencies
+
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.DependencyArtifact
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.internal.artifacts.DefaultExcludeRule
+import org.gradle.util.HelperUtil
+import org.gradle.util.WrapUtil
+import spock.lang.Specification
+
+class AbstractModuleDependencySpec extends Specification {
+
+    private dependency = createDependency("org.gradle", "gradle-core", "4.4-beta2")
+
+    private static createDependency(String group, String name, String version) {
+        createDependency(group, name, version, null)
+    }
+
+    private static createDependency(String group, String name, String version, String configuration) {
+        new DefaultExternalModuleDependency(group, name, version, configuration)
+    }
+
+    void "has reasonable default values"() {
+        expect:
+        dependency.transitive
+        dependency.artifacts.isEmpty()
+        dependency.excludeRules.isEmpty()
+        dependency.configuration == Dependency.DEFAULT_CONFIGURATION
+    }
+
+    void "can exclude dependencies"() {
+        def excludeArgs1 = WrapUtil.toMap("group", "aGroup")
+        def excludeArgs2 = WrapUtil.toMap("module", "aModule")
+
+        when:
+        dependency.exclude(excludeArgs1)
+        dependency.exclude(excludeArgs2)
+
+        then:
+        dependency.excludeRules.size() == 2
+        dependency.excludeRules.contains(new DefaultExcludeRule("aGroup", null))
+        dependency.excludeRules.contains(new DefaultExcludeRule(null, "aModule"))
+    }
+
+    void "can add artifacts"() {
+        DependencyArtifact artifact1 = createAnonymousArtifact()
+        DependencyArtifact artifact2 = createAnonymousArtifact()
+
+        when:
+        dependency.addArtifact(artifact1)
+        dependency.addArtifact(artifact2)
+
+        then:
+        dependency.artifacts.size() == 2
+        dependency.artifacts.contains(artifact1)
+        dependency.artifacts.contains(artifact2)
+    }
+
+    void "knows if is equal to"() {
+        expect:
+        createDependency("group1", "name1", "version1") == createDependency("group1", "name1", "version1")
+        createDependency("group1", "name1", "version1").hashCode() == createDependency("group1", "name1", "version1").hashCode()
+        createDependency("group1", "name1", "version1") != createDependency("group1", "name1", "version2")
+        createDependency("group1", "name1", "version1") != createDependency("group1", "name2", "version1")
+        createDependency("group1", "name1", "version1") != createDependency("group2", "name1", "version1")
+        createDependency("group1", "name1", "version1") != createDependency("group2", "name1", "version1")
+        createDependency("group1", "name1", "version1", "depConf1") != createDependency("group1", "name1", "version1", "depConf2")
+    }
+
+    private DependencyArtifact createAnonymousArtifact() {
+        return new DefaultDependencyArtifact(HelperUtil.createUniqueId(), "type", "org", "classifier", "url")
+    }
+
+    public static void assertDeepCopy(ModuleDependency dependency, ModuleDependency copiedDependency) {
+        assert copiedDependency.group == dependency.group
+        assert copiedDependency.name == dependency.name
+        assert copiedDependency.version == dependency.version
+        assert copiedDependency.configuration == dependency.configuration
+        assert copiedDependency.transitive == dependency.transitive
+        assert copiedDependency.artifacts == dependency.artifacts
+        assert copiedDependency.excludeRules == dependency.excludeRules
+
+        assert !copiedDependency.artifacts.is(dependency.artifacts)
+        assert !copiedDependency.excludeRules.is(dependency.excludeRules)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
index 7dffb32..58d2472 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
@@ -22,28 +22,25 @@ import org.gradle.api.artifacts.ModuleDependency;
 import org.gradle.api.internal.artifacts.DefaultExcludeRule;
 import org.gradle.util.HelperUtil;
 import org.gradle.util.JUnit4GroovyMockery;
-
-import static org.gradle.util.Matchers.*;
-
 import org.gradle.util.WrapUtil;
-
-import static org.hamcrest.Matchers.*;
-
 import org.jmock.integration.junit4.JMock;
 import org.jmock.integration.junit4.JUnit4Mockery;
-
-import static org.junit.Assert.*;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Map;
 
+import static org.gradle.util.Matchers.isEmpty;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
 /**
  * @author Hans Dockter
  */
 @RunWith(JMock.class)
 abstract public class AbstractModuleDependencyTest {
+    //TODO SF rework the remaining coverage of this hierarchy in the spirit of AbstractModuleDependencySpec and DefaultProjectDependencyTest
 
     protected abstract AbstractModuleDependency getDependency();
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java
index 8e9cd24..6a3e98a 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java
@@ -16,14 +16,13 @@
 
 package org.gradle.api.internal.artifacts.dependencies;
 
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.util.HelperUtil;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
 /**
  * @author Hans Dockter
  */
@@ -34,8 +33,6 @@ public class DefaultClientModuleTest extends AbstractModuleDependencyTest {
 
     DefaultClientModule clientModule;
 
-    DefaultDependencyDescriptor expectedDependencyDescriptor;
-
     protected AbstractModuleDependency getDependency() {
         return clientModule;
     }
@@ -51,7 +48,6 @@ public class DefaultClientModuleTest extends AbstractModuleDependencyTest {
     @Before
     public void setUp() {
         clientModule = new DefaultClientModule(TEST_GROUP, TEST_NAME, TEST_VERSION);
-        expectedDependencyDescriptor = HelperUtil.getTestDescriptor();
     }
 
     @Test
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
index d7458bf..d1b657c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
@@ -16,9 +16,7 @@
 
 package org.gradle.api.internal.artifacts.dependencies;
 
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.util.HelperUtil;
 import org.jmock.integration.junit4.JMock;
 import org.jmock.lib.legacy.ClassImposteriser;
 import org.junit.Before;
@@ -38,8 +36,6 @@ public class DefaultExternalModuleDependencyTest extends AbstractModuleDependenc
     private static final String TEST_NAME = "gradle-core";
     private static final String TEST_VERSION = "4.4-beta2";
 
-    protected DefaultDependencyDescriptor expectedDependencyDescriptor;
-
     private DefaultExternalModuleDependency moduleDependency;
 
     public AbstractModuleDependency getDependency() {
@@ -57,7 +53,6 @@ public class DefaultExternalModuleDependencyTest extends AbstractModuleDependenc
     @Before public void setUp() {
         moduleDependency = new DefaultExternalModuleDependency(TEST_GROUP, TEST_NAME, TEST_VERSION);
         context.setImposteriser(ClassImposteriser.INSTANCE);
-        expectedDependencyDescriptor = HelperUtil.getTestDescriptor();
     }
 
     @Test
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
new file mode 100644
index 0000000..31bedb9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.groovy
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.artifacts.dependencies
+
+import org.gradle.api.artifacts.ExternalDependency
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.internal.artifacts.DependencyResolveContext
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext
+import org.gradle.initialization.ProjectAccessListener
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+import static org.gradle.api.internal.artifacts.dependencies.AbstractModuleDependencySpec.assertDeepCopy
+import static org.gradle.util.Matchers.strictlyEqual
+import static org.junit.Assert.assertThat
+
+class DefaultProjectDependencyTest extends Specification {
+
+    ProjectInternal project = HelperUtil.createRootProject()
+    ProjectAccessListener listener = Mock()
+
+    private projectDependency = new DefaultProjectDependency(project, null, false)
+
+    void setup() {
+        project.version = "1.2"
+        project.group = "org.gradle"
+    }
+
+    void "provides dependency information"() {
+        expect:
+        projectDependency.transitive
+        projectDependency.name == project.name
+        projectDependency.group == "org.gradle"
+        projectDependency.version == "1.2"
+    }
+
+    void "knows the configuration"() {
+        def conf = project.configurations.create("conf1")
+
+        when:
+        projectDependency = new DefaultProjectDependency(project, "conf1", null, true)
+
+        then:
+        projectDependency.projectConfiguration == conf
+    }
+
+    void "transitive resolution resolves all dependencies"() {
+        def context = Mock(DependencyResolveContext)
+
+        def superConf = project.configurations.add("superConf")
+        def conf = project.configurations.add("conf")
+        conf.extendsFrom(superConf)
+
+        def dep1 = Mock(ProjectDependency)
+        def dep2 = Mock(ExternalDependency)
+        conf.dependencies.add(dep1)
+        superConf.dependencies.add(dep2)
+
+        projectDependency = new DefaultProjectDependency(project, "conf", null, true)
+
+        when:
+        projectDependency.resolve(context)
+
+        then:
+        1 * context.transitive >> true
+        1 * context.add(dep1)
+        1 * context.add(dep2)
+        0 * _
+    }
+
+    void "if resolution context is not transitive it will not contain all dependencies"() {
+        def context = Mock(DependencyResolveContext)
+        projectDependency = new DefaultProjectDependency(project, null, true)
+
+        when:
+        projectDependency.resolve(context)
+
+        then:
+        1 * context.transitive >> false
+        0 * _
+    }
+
+    void "if dependency is not transitive the resolution context will not contain all dependencies"() {
+        def context = Mock(DependencyResolveContext)
+        projectDependency = new DefaultProjectDependency(project, null, true)
+        projectDependency.setTransitive(false)
+
+        when:
+        projectDependency.resolve(context)
+
+        then:
+        0 * _
+    }
+
+    void "is Buildable"() {
+        def context = Mock(TaskDependencyResolveContext)
+
+        def conf = project.configurations.add('conf')
+        def listener = Mock(ProjectAccessListener)
+        projectDependency = new DefaultProjectDependency(project, 'conf', listener, true)
+
+        when:
+        projectDependency.buildDependencies.resolve(context)
+
+        then:
+        1 * context.add(conf)
+        1 * context.add({it.is(conf.allArtifacts)})
+        1 * listener.beforeResolvingProjectDependency(project)
+        0 * _
+    }
+
+    void "does not build project dependencies if configured so"() {
+        def context = Mock(TaskDependencyResolveContext)
+        project.configurations.add('conf')
+        projectDependency = new DefaultProjectDependency(project, 'conf', listener, false)
+
+        when:
+        projectDependency.buildDependencies.resolve(context)
+
+        then:
+        0 * _
+    }
+
+    void "knows when content is equal"() {
+        def d1 = createProjectDependency()
+        def d2 = createProjectDependency()
+
+        expect:
+        d1.contentEquals(d2)
+    }
+
+    void "knows when content is not euqal"() {
+        def d1 = createProjectDependency()
+        def d2 = createProjectDependency()
+        d2.setTransitive(false)
+
+        expect:
+        !d1.contentEquals(d2)
+    }
+
+    void "can copy"() {
+        def d1 = createProjectDependency()
+        def copy = d1.copy()
+
+        expect:
+        assertDeepCopy(d1, copy)
+        d1.dependencyProject == copy.dependencyProject
+    }
+
+    private createProjectDependency() {
+        def out = new DefaultProjectDependency(project, "conf", listener, true)
+        out.addArtifact(new DefaultDependencyArtifact("name", "type", "ext", "classifier", "url"))
+        out
+    }
+
+    void "knows if is equal"() {
+        expect:
+        assertThat(new DefaultProjectDependency(project, listener, true),
+                strictlyEqual(new DefaultProjectDependency(project, listener, true)))
+
+        assertThat(new DefaultProjectDependency(project, "conf1", listener, false),
+                strictlyEqual(new DefaultProjectDependency(project, "conf1", listener, false)))
+
+        when:
+        def base = new DefaultProjectDependency(project, "conf1", listener, true)
+        def differentConf = new DefaultProjectDependency(project, "conf2", listener, true)
+        def differentBuildDeps = new DefaultProjectDependency(project, "conf1", listener, false)
+        def differentProject = new DefaultProjectDependency(Mock(ProjectInternal), "conf1", listener, true)
+
+        then:
+        base != differentConf
+        base != differentBuildDeps
+        base != differentProject
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
deleted file mode 100644
index 69d3668..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.artifacts.dependencies;
-
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.internal.artifacts.DependencyResolveContext;
-import org.gradle.api.internal.artifacts.ProjectDependenciesBuildInstruction;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal;
-import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.tasks.TaskContainer;
-import org.gradle.api.tasks.TaskDependency;
-import org.jmock.Expectations;
-import org.jmock.integration.junit4.JMock;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-import static org.gradle.util.Matchers.isEmpty;
-import static org.gradle.util.Matchers.strictlyEqual;
-import static org.gradle.util.WrapUtil.toList;
-import static org.gradle.util.WrapUtil.toSet;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
-/**
- * @author Hans Dockter
- */
- at RunWith(JMock.class)
-public class DefaultProjectDependencyTest extends AbstractModuleDependencyTest {
-    private final ProjectDependenciesBuildInstruction instruction = new ProjectDependenciesBuildInstruction(true);
-    private final ProjectInternal dependencyProjectStub = context.mock(ProjectInternal.class);
-    private final ConfigurationContainerInternal projectConfigurationsStub = context.mock(ConfigurationContainerInternal.class);
-    private final ConfigurationInternal projectConfigurationStub = context.mock(ConfigurationInternal.class);
-    private final TaskContainer dependencyProjectTaskContainerStub = context.mock(TaskContainer.class);
-    private final DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, instruction);
-
-    protected AbstractModuleDependency getDependency() {
-        return projectDependency;
-    }
-
-    protected AbstractModuleDependency createDependency(String group, String name, String version) {
-        return createDependency(group, name, version, null);    
-    }
-
-    protected AbstractModuleDependency createDependency(String group, String name, String version, String configuration) {
-        ProjectInternal dependencyProject = context.mock(ProjectInternal.class);
-        DefaultProjectDependency projectDependency;
-        if (configuration != null) {
-            projectDependency = new DefaultProjectDependency(dependencyProject, configuration, instruction);
-        } else {
-            projectDependency = new DefaultProjectDependency(dependencyProject, instruction);
-        }
-        return projectDependency;
-    }
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(dependencyProjectStub).getConfigurations();
-            will(returnValue(projectConfigurationsStub));
-            allowing(projectConfigurationsStub).getByName("default");
-            will(returnValue(projectConfigurationStub));
-            allowing(dependencyProjectStub).getTasks();
-            will(returnValue(dependencyProjectTaskContainerStub));
-            allowing(dependencyProjectStub).getName();
-            will(returnValue("target-name"));
-            allowing(dependencyProjectStub).getGroup();
-            will(returnValue("target-group"));
-            allowing(dependencyProjectStub).getVersion();
-            will(returnValue("target-version"));
-        }});
-    }
-
-    @Test
-    public void init() {
-        assertTrue(projectDependency.isTransitive());
-        assertEquals("target-name", projectDependency.getName());
-        assertEquals("target-group", projectDependency.getGroup());
-        assertEquals("target-version", projectDependency.getVersion());
-    }
-
-    @Test
-    public void getConfiguration() {
-        context.checking(new Expectations() {{
-            allowing(projectConfigurationsStub).getByName("conf1");
-            will(returnValue(projectConfigurationStub));
-        }});
-
-        DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction);
-        assertThat(projectDependency.getProjectConfiguration(), sameInstance((Configuration) projectConfigurationStub));
-    }
-
-    @Test
-    public void resolveDelegatesToAllSelfResolvingDependenciesInTargetConfiguration() {
-        final DependencyResolveContext resolveContext = context.mock(DependencyResolveContext.class);
-        final Dependency projectSelfResolvingDependency = context.mock(Dependency.class);
-        final ProjectDependency transitiveProjectDependencyStub = context.mock(ProjectDependency.class);
-        final DependencySet dependencies = context.mock(DependencySet.class);
-        context.checking(new Expectations() {{
-            allowing(projectConfigurationsStub).getByName("conf1");
-            will(returnValue(projectConfigurationStub));
-
-            allowing(projectConfigurationStub).getAllDependencies();
-            will(returnValue(dependencies));
-
-            allowing(dependencies).iterator();
-            will(returnIterator(toList(projectSelfResolvingDependency, transitiveProjectDependencyStub)));
-
-            allowing(resolveContext).isTransitive();
-            will(returnValue(true));
-            
-            one(resolveContext).add(projectSelfResolvingDependency);
-            
-            one(resolveContext).add(transitiveProjectDependencyStub);
-        }});
-
-        DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1",
-                instruction);
-        projectDependency.resolve(resolveContext);
-    }
-
-    @Test
-    public void resolveNotDelegatesToProjectDependenciesInTargetConfigurationIfConfigurationIsNonTransitive() {
-        final DependencyResolveContext resolveContext = context.mock(DependencyResolveContext.class);
-        context.checking(new Expectations() {{
-            allowing(resolveContext).isTransitive();
-            will(returnValue(false));
-        }});
-        DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1",
-                instruction);
-        projectDependency.resolve(resolveContext);
-    }
-    
-    @Test
-    public void resolveNotDelegatesToTransitiveProjectDependenciesIfProjectDependencyIsNonTransitive() {
-        DependencyResolveContext resolveContext = context.mock(DependencyResolveContext.class);
-        DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction);
-        projectDependency.setTransitive(false);
-        projectDependency.resolve(resolveContext);
-    }
-
-    private Task taskInTargetProject(final String name) {
-        final Task task = context.mock(Task.class, name);
-        context.checking(new Expectations(){{
-            allowing(dependencyProjectStub).ensureEvaluated();
-            allowing(dependencyProjectTaskContainerStub).getByName(name);
-            will(returnValue(task));
-        }});
-        return task;
-    }
-
-    @Test
-    public void dependsOnTargetConfigurationAndArtifactsOfTargetConfiguration() {
-        Task a = taskInTargetProject("a");
-        Task b = taskInTargetProject("b");
-        Task c = taskInTargetProject("c");
-        expectTargetConfigurationHasDependencies(a, b);
-        expectTargetConfigurationHasArtifacts(c);
-
-        assertThat(projectDependency.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(a, b, c)));
-    }
-
-    private void expectTargetConfigurationHasDependencies(final Task... tasks) {
-        context.checking(new Expectations(){{
-            TaskDependency dependencyStub = context.mock(TaskDependency.class, "dependencies");
-
-            allowing(projectConfigurationStub).getBuildDependencies();
-            will(returnValue(dependencyStub));
-
-            allowing(dependencyStub).getDependencies(null);
-            will(returnValue(toSet(tasks)));
-        }});
-    }
-
-    private void expectTargetConfigurationHasArtifacts(final Task... tasks) {
-        context.checking(new Expectations(){{
-            PublishArtifactSet artifacts = context.mock(PublishArtifactSet.class);
-            TaskDependency dependencyStub = context.mock(TaskDependency.class, "artifacts");
-
-            allowing(projectConfigurationStub).getAllArtifacts();
-            will(returnValue(artifacts));
-
-            allowing(artifacts).getBuildDependencies();
-            will(returnValue(dependencyStub));
-
-            allowing(dependencyStub).getDependencies(null);
-            will(returnValue(toSet(tasks)));
-        }});
-    }
-
-    @Test
-    public void doesNotDependOnAnythingWhenProjectRebuildIsDisabled() {
-        DefaultProjectDependency dependency = new DefaultProjectDependency(dependencyProjectStub,
-                new ProjectDependenciesBuildInstruction(false));
-        assertThat(dependency.getBuildDependencies().getDependencies(null), isEmpty());
-    }
-
-    @Test
-    public void contentEqualsWithEqualDependencies() {
-        ProjectDependency dependency1 = createProjectDependency();
-        ProjectDependency dependency2 = createProjectDependency();
-        assertThat(dependency1.contentEquals(dependency2), equalTo(true));
-    }
-
-    @Test
-    public void contentEqualsWithNonEqualDependencies() {
-        ProjectDependency dependency1 = createProjectDependency();
-        ProjectDependency dependency2 = createProjectDependency();
-        dependency2.setTransitive(!dependency1.isTransitive());
-        assertThat(dependency1.contentEquals(dependency2), equalTo(false));
-    }
-
-    @Test
-    public void copy() {
-        ProjectDependency dependency = createProjectDependency();
-        ProjectDependency copiedDependency = dependency.copy();
-        assertDeepCopy(dependency, copiedDependency);
-        assertThat(copiedDependency.getDependencyProject(), sameInstance(dependency.getDependencyProject()));
-    }
-
-    private ProjectDependency createProjectDependency() {
-        ProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf", instruction);
-        projectDependency.addArtifact(new DefaultDependencyArtifact("name", "type", "ext", "classifier", "url"));
-        return projectDependency;
-    }
-
-    @Test
-    @Override
-    public void equality() {
-        assertThat(new DefaultProjectDependency(dependencyProjectStub, instruction), strictlyEqual(new DefaultProjectDependency(
-                dependencyProjectStub, instruction)));
-        assertThat(new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction), strictlyEqual(new DefaultProjectDependency(
-                dependencyProjectStub, "conf1", instruction)));
-        assertThat(new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction), not(equalTo(new DefaultProjectDependency(
-                dependencyProjectStub, "conf2", instruction))));
-        ProjectInternal otherProject = context.mock(ProjectInternal.class, "otherProject");
-        assertThat(new DefaultProjectDependency(dependencyProjectStub, instruction), not(equalTo(new DefaultProjectDependency(
-                otherProject, instruction))));
-        assertThat(new DefaultProjectDependency(dependencyProjectStub, instruction), not(equalTo(new DefaultProjectDependency(
-                dependencyProjectStub, new ProjectDependenciesBuildInstruction(false)))));
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
index 5be9bda..bc7afa0 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
@@ -17,14 +17,13 @@
 package org.gradle.api.internal.artifacts.dsl
 
 import org.apache.ivy.plugins.resolver.DependencyResolver
-import org.apache.ivy.plugins.resolver.FileSystemResolver
 import org.gradle.api.Action
 import org.gradle.api.artifacts.ArtifactRepositoryContainer
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository
 import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.api.internal.artifacts.BaseRepositoryFactory
 import org.gradle.api.internal.artifacts.DefaultArtifactRepositoryContainerTest
-import org.gradle.api.internal.artifacts.repositories.FixedResolverArtifactRepository
+import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal
 import org.gradle.internal.reflect.Instantiator
 import org.junit.Test
 
@@ -102,17 +101,18 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
         TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
         repositoryFactory.createMavenRepository() >> repository
         1 * repository.setName(repoName)
-        repository.getName() >> repoName
         1 * repository.setUrl(repoRoot)
         1 * repository.setArtifactUrls([testUrl1, testUrl2])
-        DependencyResolver resolver = new FileSystemResolver(name: "resolver")
+        DependencyResolver resolver = Mock()
         1 * repositoryFactory.toResolver(repository) >> resolver
+        ArtifactRepositoryInternal fixedRepo = Mock(ArtifactRepositoryInternal)
+        1 * repositoryFactory.createResolverBackedRepository(resolver) >> fixedRepo
+        fixedRepo.getName() >> repoName
 
         then:
         handler.mavenRepo([name: repoName, url: repoRoot, artifactUrls: [testUrl1, testUrl2]]).is(resolver)
         handler.size() == 1
-        handler.first() instanceof FixedResolverArtifactRepository
-        handler.first().createResolver() == resolver
+        handler.first() == fixedRepo
     }
 
     @Test
@@ -124,15 +124,17 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
         TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
         repositoryFactory.createMavenRepository() >> repository
         1 * repository.setName(repoName)
-        repository.getName() >> repoName
         1 * repository.setUrl(repoRoot)
-        DependencyResolver resolver = new FileSystemResolver(name: "resolver")
+        DependencyResolver resolver = Mock()
         1 * repositoryFactory.toResolver(repository) >> resolver
+        ArtifactRepositoryInternal fixedRepo = Mock(ArtifactRepositoryInternal)
+        1 * repositoryFactory.createResolverBackedRepository(resolver) >> fixedRepo
+        fixedRepo.getName() >> repoName
 
         then:
         handler.mavenRepo([name: repoName, url: repoRoot]).is(resolver)
         handler.size() == 1
-        handler.first().createResolver() == resolver
+        handler.first() == fixedRepo
     }
 
     @Test
@@ -142,15 +144,17 @@ class DefaultRepositoryHandlerTest extends DefaultArtifactRepositoryContainerTes
 
         TestMavenArtifactRepository repository = Mock(TestMavenArtifactRepository)
         repositoryFactory.createMavenRepository() >> repository
-        repository.getName() >> null
         1 * repository.setUrl(repoRoot)
-        DependencyResolver resolver = new FileSystemResolver(name: "resolver")
+        DependencyResolver resolver = Mock()
         1 * repositoryFactory.toResolver(repository) >> resolver
+        ArtifactRepositoryInternal fixedRepo = Mock(ArtifactRepositoryInternal)
+        1 * repositoryFactory.createResolverBackedRepository(resolver) >> fixedRepo
+        1 * fixedRepo.setName("mavenRepo")
 
         then:
         handler.mavenRepo([url: repoRoot]).is(resolver)
         handler.size() == 1
-        handler.first().createResolver() == resolver
+        handler.first() == fixedRepo
     }
 
     public void createIvyRepositoryUsingClosure() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
index 89628b1..6f4d75f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
@@ -20,10 +20,11 @@ import org.gradle.api.GradleException;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
 import org.gradle.api.internal.file.FileResource;
+import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
 import org.gradle.api.internal.file.archive.compression.Bzip2Archiver;
-import org.gradle.api.internal.file.archive.compression.Compressor;
 import org.gradle.api.internal.file.archive.compression.GzipArchiver;
 import org.gradle.api.internal.file.archive.compression.SimpleCompressor;
+import org.gradle.api.internal.file.copy.ArchiveCopyAction;
 import org.gradle.api.internal.file.copy.ReadableCopySpec;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
@@ -51,7 +52,7 @@ public class TarCopySpecVisitorTest {
     @Rule
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4Mockery();
-    private final TarCopyAction copyAction = context.mock(TarCopyAction.class);
+    private final ArchiveCopyAction copyAction = context.mock(ArchiveCopyAction.class);
     private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
     private final TarCopySpecVisitor visitor = new TarCopySpecVisitor();
 
@@ -131,7 +132,7 @@ public class TarCopySpecVisitorTest {
         }
     }
 
-    private TestFile initializeTarFile(final TestFile tarFile, final Compressor compressor) {
+    private TestFile initializeTarFile(final TestFile tarFile, final ArchiveOutputStreamFactory compressor) {
         context.checking(new Expectations() {{
             allowing(copyAction).getArchivePath();
             will(returnValue(tarFile));
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
index 9684b52..bbc30b5 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
@@ -19,8 +19,11 @@ import org.apache.commons.io.IOUtils;
 import org.gradle.api.GradleException;
 import org.gradle.api.file.FileVisitDetails;
 import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory;
 import org.gradle.api.internal.file.copy.ArchiveCopyAction;
+import org.gradle.api.internal.file.copy.ZipDeflatedCompressor;
 import org.gradle.api.internal.file.copy.ReadableCopySpec;
+import org.gradle.api.internal.file.copy.ZipStoredCompressor;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider;
 import org.hamcrest.Description;
@@ -48,7 +51,7 @@ public class ZipCopySpecVisitorTest {
     @Rule
     public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ArchiveCopyAction copyAction = context.mock(ArchiveCopyAction.class);
+    private final ArchiveCopyAction copyAction = context.mock(ZipCopyAction.class);
     private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
     private final ZipCopySpecVisitor visitor = new ZipCopySpecVisitor();
     private TestFile zipFile;
@@ -56,14 +59,40 @@ public class ZipCopySpecVisitorTest {
     @Before
     public void setup() {
         zipFile = tmpDir.getTestDirectory().file("test.zip");
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(zipFile));
+        }});
+        context.checking(new Expectations() {{
+            allowing(copyAction).getCompressor();
+            will(returnValue(ZipStoredCompressor.INSTANCE));
+        }});
+    }
+
+    private TestFile initializeZipFile(final TestFile testFile, final ArchiveOutputStreamFactory compressor) {
+        context.checking(new Expectations() {{
             allowing(copyAction).getArchivePath();
             will(returnValue(zipFile));
+            allowing(copyAction).getCompressor();
+            will(returnValue(compressor));
         }});
+        return testFile;
     }
 
     @Test
     public void createsZipFile() {
+        initializeZipFile(zipFile, ZipStoredCompressor.INSTANCE);
+        zip(dir("dir"), file("dir/file1"), file("file2"));
+
+        TestFile expandDir = tmpDir.getTestDirectory().file("expanded");
+        zipFile.unzipTo(expandDir);
+        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
+        expandDir.file("file2").assertContents(equalTo("contents of file2"));
+    }
+
+    @Test
+    public void createsDeflatedZipFile() {
+        initializeZipFile(zipFile, ZipDeflatedCompressor.INSTANCE);
         zip(dir("dir"), file("dir/file1"), file("file2"));
 
         TestFile expandDir = tmpDir.getTestDirectory().file("expanded");
@@ -87,7 +116,7 @@ public class ZipCopySpecVisitorTest {
     public void wrapsFailureToOpenOutputFile() {
         final TestFile invalidZipFile = tmpDir.createDir("test.zip");
 
-        context.checking(new Expectations(){{
+        context.checking(new Expectations() {{
             allowing(copyAction).getArchivePath();
             will(returnValue(invalidZipFile));
         }});
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/html/SimpleHtmlWriterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/html/SimpleHtmlWriterTest.groovy
new file mode 100644
index 0000000..9d5218b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/html/SimpleHtmlWriterTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.html
+
+import org.jsoup.Jsoup
+import spock.lang.Specification
+
+class SimpleHtmlWriterTest extends Specification {
+
+    def "creates html output"() {
+        given:
+        StringWriter sw = new StringWriter();
+
+        when:
+        SimpleHtmlWriter htmlWriter = new SimpleHtmlWriter(sw)
+        htmlWriter.startElement("html")
+                .startElement("head").endElement()
+                .startElement("body")
+                .startElement("h1").characters("Test Header").endElement()
+                .endElement()
+                .endElement()
+        sw.close()
+
+        then:
+        Jsoup.parse(sw.toString()).select("h1").text() == "Test Header"
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy
index b39e7c0..6185901 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/ErrorHandlingNotationParserTest.groovy
@@ -19,6 +19,7 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.internal.notations.api.NotationParser
 import org.gradle.api.internal.notations.api.UnsupportedNotationException
 import spock.lang.Specification
+
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class ErrorHandlingNotationParserTest extends Specification {
@@ -26,10 +27,6 @@ class ErrorHandlingNotationParserTest extends Specification {
     def parser = new ErrorHandlingNotationParser<String>("String", "<broken>", target)
 
     def "reports unable to parse null"() {
-        given:
-        target.parseNotation(null) >> { throw new UnsupportedNotationException(null) }
-        target.describe(!null) >> { args -> args[0].add("format 1"); args[0].add("format 2") }
-
         when:
         parser.parseNotation(null)
 
@@ -40,6 +37,9 @@ The following types/formats are supported:
   - format 1
   - format 2
 <broken>''')
+
+        1 * target.describe(!null) >> { args -> args[0].add("format 1"); args[0].add("format 2") }
+        0 * target._  //no parsing
     }
 
     def "reports unable to parse non-null"() {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParserTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParserTest.groovy
new file mode 100644
index 0000000..8f70474
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/notations/parsers/TimeUnitsParserTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.notations.parsers
+
+import org.gradle.api.InvalidUserDataException
+import spock.lang.Specification
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS
+import static java.util.concurrent.TimeUnit.NANOSECONDS
+
+/**
+ * by Szczepan Faber, created at: 2/12/13
+ */
+class TimeUnitsParserTest extends Specification {
+
+    def parser = new TimeUnitsParser()
+
+    def "parses time units"() {
+        expect:
+        def unit = parser.parseNotation(input, value)
+        unit.value == normalizedValue
+        unit.timeUnit == parsed
+
+        where:
+        value           |input          |parsed         | normalizedValue
+        10              |'nanoseconds'  |NANOSECONDS    |10
+        20              |'mILLISECONds' |MILLISECONDS   |20
+        1               |'days'         |MILLISECONDS   |1 * 24 * 60 * 60 * 1000
+        2               |'hours'        |MILLISECONDS   |2 * 60 * 60 * 1000
+        5               |'MINUTES'      |MILLISECONDS   |5 * 60 * 1000
+    }
+
+    def "fails gracefully for invalid input"() {
+        when:
+        parser.parseNotation("foobar", 133)
+        then:
+        def ex = thrown(InvalidUserDataException)
+        ex.message.contains("foobar")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionsStorageTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionsStorageTest.groovy
new file mode 100644
index 0000000..9e7049f
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/plugins/ExtensionsStorageTest.groovy
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.plugins
+
+import org.gradle.api.Action
+import org.gradle.api.UnknownDomainObjectException
+import org.gradle.api.plugins.DeferredConfigurable
+import spock.lang.Specification
+
+class ExtensionsStorageTest extends Specification {
+    def storage = new ExtensionsStorage()
+    def listExtension = Mock(List)
+    def setExtension = Mock(Set)
+
+    def "setup"() {
+        storage.add("list", listExtension)
+        storage.add("set", setExtension)
+    }
+
+    def "has extension"() {
+        expect:
+        storage.hasExtension("list")
+        !storage.hasExtension("not")
+    }
+
+    def "get extension"() {
+        when:
+        def list = storage.getByName("list")
+        def set = storage.getByType(Set)
+
+        then:
+        list == listExtension
+        set == setExtension
+
+        when:
+        storage.getByName("foo")
+
+        then:
+        thrown UnknownDomainObjectException
+
+        when:
+        storage.getByType(String)
+
+        then:
+        thrown UnknownDomainObjectException
+    }
+
+    def "find extension"() {
+        when:
+        def list = storage.findByName("list")
+        def set = storage.findByType(Set)
+        def foo = storage.findByName("foo")
+        def string = storage.findByType(String)
+
+        then:
+        list == listExtension
+        set == setExtension
+        foo == null
+        string == null
+    }
+
+    def "get as map"() {
+        expect:
+        storage.getAsMap() == ["list": listExtension, "set": setExtension]
+    }
+
+    def "configures regular extension"() {
+        when:
+        def extension = Mock(TestExtension)
+        storage.add("ext", extension)
+
+        and:
+        storage.configureExtension("ext", {
+            it.call(1)
+        })
+        storage.configureExtension(TestExtension, new Action<TestExtension>() {
+            void execute(TestExtension t) {
+                t.call(2)
+            }
+        })
+
+        then:
+        extension.call(1)
+        extension.call(2)
+
+        when:
+        def val = storage.getByName("ext")
+
+        then:
+        val == extension
+    }
+
+    def "configures deferred configurable extension"() {
+
+        TestDeferredExtension extension = new TestDeferredExtension()
+        def delegate = Mock(TestExtension)
+        extension.delegate = delegate
+
+        when:
+        storage.add("ext", extension)
+        storage.configureExtension("ext", {
+            it.call(1)
+        })
+        storage.configureExtension(TestDeferredExtension, new Action<TestDeferredExtension>() {
+            void execute(TestDeferredExtension t) {
+                t.call(2)
+            }
+        })
+
+        then:
+        0 * _
+
+        when:
+        storage.getByName("ext")
+
+        then:
+        1 * delegate.call(1)
+        1 * delegate.call(2)
+    }
+
+    public static interface TestExtension {
+        void call(def value);
+    }
+
+    @DeferredConfigurable
+    public static class TestDeferredExtension {
+        TestExtension delegate
+
+        void call(def value) {
+            delegate.call(value)
+        }
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
index 2ddf1e4..32c4cee 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
@@ -16,18 +16,14 @@
 
 package org.gradle.api.internal.project
 
-import org.gradle.api.component.SoftwareComponentContainer
-
-import java.awt.Point
-import java.text.FieldPosition
 import org.apache.tools.ant.types.FileSet
 import org.gradle.api.artifacts.Module
 import org.gradle.api.artifacts.dsl.ArtifactHandler
 import org.gradle.api.artifacts.dsl.DependencyHandler
 import org.gradle.api.artifacts.dsl.RepositoryHandler
+import org.gradle.api.component.SoftwareComponentContainer
 import org.gradle.api.initialization.dsl.ScriptHandler
 import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
-import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
 import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
 import org.gradle.api.internal.file.FileOperations
@@ -42,6 +38,7 @@ import org.gradle.configuration.ScriptPluginFactory
 import org.gradle.groovy.scripts.EmptyScript
 import org.gradle.groovy.scripts.ScriptSource
 import org.gradle.internal.Factory
+import org.gradle.internal.reflect.Instantiator
 import org.gradle.internal.service.ServiceRegistry
 import org.gradle.logging.LoggingManagerInternal
 import org.gradle.logging.StandardOutputCapture
@@ -50,14 +47,17 @@ import org.gradle.util.JUnit4GroovyMockery
 import org.gradle.util.TestClosure
 import org.jmock.integration.junit4.JMock
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
+
+import java.awt.Point
+import java.text.FieldPosition
+
 import org.gradle.api.*
 import org.gradle.api.internal.*
+
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.*
-import org.gradle.internal.reflect.Instantiator
 
 /**
  * @author Hans Dockter
@@ -92,7 +92,7 @@ class DefaultProjectTest {
     Factory<AntBuilder> antBuilderFactoryMock = context.mock(Factory.class)
     AntBuilder testAntBuilder
 
-    DefaultConfigurationContainer configurationContainerMock = context.mock(DefaultConfigurationContainer.class)
+    ConfigurationContainerInternal configurationContainerMock = context.mock(ConfigurationContainerInternal.class)
     RepositoryHandler repositoryHandlerMock = context.mock(RepositoryHandler.class)
     DependencyFactory dependencyFactoryMock = context.mock(DependencyFactory.class)
     DependencyHandler dependencyHandlerMock = context.mock(DependencyHandler)
@@ -170,33 +170,7 @@ class DefaultProjectTest {
         }
     }
 
-  @Ignore void testArtifacts() {
-        boolean called = false;
-        ArtifactHandler artifactHandlerMock = [testMethod: { called = true }] as ArtifactHandler
-        project.artifactHandler = artifactHandlerMock
-        project.artifacts {
-            testMethod()
-        }
-        assertTrue(called)
-  }
-
-  @Test void testDependencies() {
-      context.checking {
-          one(dependencyHandlerMock).add('conf', 'dep')
-      }
-      project.dependencies {
-          add('conf', 'dep')
-      }
-  }
-
-  @Test void testConfigurations() {
-        Closure cl = { }
-        context.checking {
-          one(configurationContainerMock).configure(cl)
-        }
-        project.configurationContainer = configurationContainerMock
-        project.configurations cl
-    }
+    //TODO please move more coverage to NewDefaultProjectTest
 
   @Test void testScriptClasspath() {
         context.checking {
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.groovy
index b7026e4..9902974 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.groovy
@@ -42,8 +42,9 @@ public class GradleInternalServiceRegistryTest extends Specification {
     private StartParameter startParameter = new StartParameter()
 
     public void setup() {
-        parent.get(ListenerManager.class) >> listenerManager
-        parent.get(CacheRepository.class) >> cacheRepository
+        parent.get(StartParameter) >> Mock(StartParameter)
+        parent.get(ListenerManager) >> listenerManager
+        parent.get(CacheRepository) >> cacheRepository
         parent.get(DocumentationRegistry) >> Mock(DocumentationRegistry)
         gradle.getStartParameter() >> startParameter
         gradle.getScriptClassLoader() >> new MultiParentClassLoader()
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/NewDefaultProjectTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/NewDefaultProjectTest.groovy
new file mode 100644
index 0000000..7584cac
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/NewDefaultProjectTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.project
+
+import org.gradle.api.artifacts.dsl.ArtifactHandler
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.api.internal.artifacts.configurations.ConfigurationContainerInternal
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * @author Szczepan Faber
+ */
+class NewDefaultProjectTest extends Specification {
+
+    def project = HelperUtil.createRootProject()
+
+    void "delegates to artifacts handler"() {
+        def handler = Mock(ArtifactHandler)
+        project.artifactHandler = handler
+
+        when:
+        project.artifacts {
+            add('conf', 'art')
+        }
+
+        then:
+        1 * handler.add('conf', 'art')
+    }
+
+    void "delegates to dependency handler"() {
+        def handler = Mock(DependencyHandler)
+        project.dependencyHandler = handler
+
+        when:
+        project.dependencies {
+            add('conf', 'dep')
+        }
+
+        then:
+        1 * handler.add('conf', 'dep')
+    }
+
+    void "delegates to configuration container"() {
+        Closure cl = {}
+        def container = Mock(ConfigurationContainerInternal)
+        project.configurationContainer = container
+
+        when:
+        project.configurations cl
+
+        then:
+        1 * container.configure(cl)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
index 6b721e5..2894370 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
@@ -39,6 +39,7 @@ import org.gradle.api.internal.tasks.DefaultTaskContainerFactory;
 import org.gradle.api.internal.tasks.TaskContainerInternal;
 import org.gradle.api.logging.LoggingManager;
 import org.gradle.api.plugins.PluginContainer;
+import org.gradle.initialization.ProjectAccessListener;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 import org.gradle.internal.reflect.DirectInstantiator;
@@ -101,6 +102,8 @@ public class ProjectInternalServiceRegistryTest {
             will(returnValue(context.mock(FileSystem.class)));
             allowing(parent).get(ClassGenerator.class);
             will(returnValue(context.mock(ClassGenerator.class)));
+            allowing(parent).get(ProjectAccessListener.class);
+            will(returnValue(context.mock(ProjectAccessListener.class)));
         }});
     }
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.groovy
new file mode 100644
index 0000000..d1ca77b
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.groovy
@@ -0,0 +1,309 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks;
+
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Rule
+import org.gradle.api.Task
+import org.gradle.api.UnknownTaskException
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.taskfactory.ITaskFactory
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.initialization.ProjectAccessListener
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.util.GUtil
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+import static java.util.Collections.singletonMap
+
+public class DefaultTaskContainerTest extends Specification {
+
+    private taskFactory = Mock(ITaskFactory)
+    private project = Mock(ProjectInternal, name: "<project>")
+    private taskCount = 1;
+    private accessListener = Mock(ProjectAccessListener)
+    private container = new DefaultTaskContainer(project, Mock(Instantiator), taskFactory, accessListener)
+
+    void "adds by Map"() {
+        def options = singletonMap("option", "value")
+        def task = task("task")
+        taskFactory.createTask(options) >> task
+
+        when:
+        def added = container.add(options)
+
+        then:
+        added == task
+        container.getByName("task") == task
+    }
+
+    void "adds by name"() {
+        given:
+        def options = singletonMap(Task.TASK_NAME, "task")
+        def task = task("task")
+        taskFactory.createTask(options) >> task
+
+        expect:
+        container.add("task") == task
+    }
+
+    void "adds by name and type"() {
+        given:
+        def options = GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, Task.class)
+        def task = task("task")
+        taskFactory.createTask(options) >> task
+
+        expect:
+        container.add("task", Task.class) == task
+    }
+
+    void "adds by name and closure"() {
+        given:
+        final Closure action = HelperUtil.toClosure("{ description = 'description' }")
+        def options = singletonMap(Task.TASK_NAME, "task")
+        def task = task("task")
+
+        taskFactory.createTask(options) >> task
+
+        when:
+        def added = container.add("task", action)
+
+        then:
+        added == task
+        1 * task.configure(action) >> task
+    }
+
+    void "replaces task by name"() {
+        given:
+        def options = singletonMap(Task.TASK_NAME, "task")
+        def task = task("task")
+        taskFactory.createTask(options) >> task
+
+        when:
+        def replaced = container.replace("task")
+
+        then:
+        replaced == task
+        container.getByName("task") == task
+    }
+
+    void "replaces by name and type"() {
+        given:
+        def options = GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, Task.class)
+        def task = task("task")
+        taskFactory.createTask(options) >> task
+
+        expect:
+        container.replace("task", Task.class) == task
+    }
+
+    void "does not fire rule when adding task"() {
+        def rule = Mock(Rule)
+        def options = singletonMap(Task.TASK_NAME, "task")
+        def task = task("task")
+
+        container.addRule(rule)
+        taskFactory.createTask(options) >> task
+
+        when:
+        container.add("task")
+
+        then:
+        0 * rule._
+    }
+
+    void "prevents duplicate tasks"() {
+        given:
+        def task = addTask("task")
+        taskFactory.createTask(singletonMap(Task.TASK_NAME, "task")) >> { this.task("task") }
+
+        when:
+        container.add("task")
+
+        then:
+        def ex = thrown(InvalidUserDataException)
+        ex.message == "Cannot add Mock for type 'TaskInternal' named '[task2]' as a task with that name already exists."
+        container.getByName("task") == task
+    }
+
+    void "replaces duplicate task"() {
+        given:
+        addTask("task")
+        def newTask = task("task")
+        taskFactory.createTask(singletonMap(Task.TASK_NAME, "task")) >> newTask
+
+        when:
+        container.replace("task")
+
+        then:
+        container.getByName("task") == newTask
+    }
+
+    void "fails if unknown task is requested"() {
+        when:
+        container.getByName("unknown")
+
+        then:
+        def ex = thrown(UnknownTaskException)
+        ex.message == "Task with name 'unknown' not found in Mock for type 'ProjectInternal' named '<project>'."
+    }
+
+    void "finds tasks"() {
+        when:
+        def task = addTask("task")
+
+        then:
+        container.findByPath("unknown") == null
+        container.findByPath("task") == task
+        container.findByName("task") == task
+    }
+
+    void "finds task by relative path"() {
+        when:
+        Task task = task("task")
+        expectTaskLookupInOtherProject("sub", "task", task)
+
+        then:
+        container.findByPath("sub:task") == task
+    }
+
+    void "finds tasks by absolute path"() {
+        when:
+        Task task = task("task")
+        expectTaskLookupInOtherProject(":", "task", task)
+
+        then:
+        container.findByPath(":task") == task
+    }
+
+    void "does not find tasks from unknown projects"() {
+        when:
+        project.findProject(":unknown") >> null
+
+        then:
+        container.findByPath(":unknown:task") == null
+    }
+
+    void "searching by path ensures the other project is evaluated"() {
+        given:
+        def otherProject = expectTaskLookupInOtherProject(":other", "task", null)
+
+        when:
+        container.findByPath(":other:task")
+
+        then:
+        1 * accessListener.beforeRequestingTaskByPath(otherProject)
+    }
+
+    void "does not find unknown tasks by path"() {
+        when:
+        expectTaskLookupInOtherProject(":other", "task", null)
+
+        then:
+        container.findByPath(":other:task") >> null
+    }
+
+    void "gets task by path"() {
+        when:
+        Task task = addTask("task")
+        expectTaskLookupInOtherProject(":a:b:c", "task", task)
+
+        then:
+        container.getByPath(":a:b:c:task") == task
+    }
+
+    void "get by path fails for unknown task"() {
+        when:
+        container.getByPath("unknown")
+
+        then:
+        def ex = thrown(UnknownTaskException)
+        ex.message == "Task with path 'unknown' not found in Mock for type 'ProjectInternal' named '<project>'."
+    }
+
+    void "resolve locates by name"() {
+        when:
+        Task task = addTask("1")
+
+        then:
+        container.resolveTask("1") == task
+    }
+
+    void "resolve locates by path"() {
+        when:
+        Task task = addTask("task")
+        expectTaskLookupInOtherProject(":", "task", task)
+
+        then:
+        container.resolveTask(new StringBuilder(":task")) == task
+    }
+
+    void "actualizes task graph"() {
+        given:
+        def task = addTask("a")
+        def aTaskDependency = Mock(TaskDependency)
+        task.getTaskDependencies() >> aTaskDependency
+
+        def Task b = this.task("b")
+        taskFactory.createTask(singletonMap(Task.TASK_NAME, "b")) >> b
+
+        def bTaskDependency = Mock(TaskDependency, name: "bTaskDependency")
+        b.getTaskDependencies() >> bTaskDependency
+
+        bTaskDependency.getDependencies(b) >> Collections.emptySet()
+
+        aTaskDependency.getDependencies(task) >> { container.add("b"); Collections.singleton(b) }
+        task.dependsOn("b")
+
+        assert container.size() == 1
+
+        when:
+        container.actualize()
+
+        then:
+        container.size() == 2
+    }
+
+    private ProjectInternal expectTaskLookupInOtherProject(final String projectPath, final String taskName, def task) {
+        def otherProject = Mock(ProjectInternal)
+        def otherTaskContainer = Mock(TaskContainerInternal)
+
+        project.findProject(projectPath) >> otherProject
+        otherProject.getTasks() >> otherTaskContainer
+
+        otherTaskContainer.findByName(taskName) >> task
+
+        otherProject
+    }
+
+    private TaskInternal task(final String name) {
+        Mock(TaskInternal, name: "[task" + ++taskCount + "]") {
+            getName() >> name
+        }
+    }
+
+    private Task addTask(String name) {
+        def task = task(name)
+        def options = singletonMap(Task.TASK_NAME, name)
+        taskFactory.createTask(options) >> task
+        container.add(name)
+        return task;
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
deleted file mode 100644
index 872c390..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.internal.tasks;
-
-import groovy.lang.Closure;
-import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.Rule;
-import org.gradle.api.Task;
-import org.gradle.api.UnknownTaskException;
-import org.gradle.api.internal.TaskInternal;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.taskfactory.ITaskFactory;
-import org.gradle.api.tasks.TaskDependency;
-import org.gradle.util.GUtil;
-import org.gradle.util.HelperUtil;
-import org.jmock.Expectations;
-import org.jmock.api.Invocation;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.jmock.lib.action.CustomAction;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Collections;
-import java.util.Map;
-
-import static java.util.Collections.singletonMap;
-import static org.hamcrest.Matchers.*;
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class DefaultTaskContainerTest {
-    private final JUnit4Mockery context = new JUnit4Mockery();
-    private final ITaskFactory taskFactory = context.mock(ITaskFactory.class);
-    private final ProjectInternal project = context.mock(ProjectInternal.class, "<project>");
-    private int taskCount;
-    private final DefaultTaskContainer container = new DefaultTaskContainer(project, context.mock(org.gradle.internal.reflect.Instantiator.class), taskFactory);
-
-    @Test
-    public void addsTaskWithMap() {
-        final Map<String, ?> options = singletonMap("option", "value");
-        final Task task = task("task");
-
-        context.checking(new Expectations(){{
-            one(taskFactory).createTask(options);
-            will(returnValue(task));
-        }});
-        assertThat(container.add(options), sameInstance(task));
-        assertThat(container.getByName("task"), sameInstance(task));
-    }
-
-    @Test
-    public void addsTaskWithName() {
-        final Map<String, ?> options = singletonMap(Task.TASK_NAME, "task");
-        final Task task = task("task");
-
-        context.checking(new Expectations(){{
-            one(taskFactory).createTask(options);
-            will(returnValue(task));
-        }});
-        assertThat(container.add("task"), sameInstance(task));
-    }
-
-    @Test
-    public void addsTaskWithNameAndType() {
-        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, Task.class);
-        final Task task = task("task");
-
-        context.checking(new Expectations(){{
-            one(taskFactory).createTask(options);
-            will(returnValue(task));
-        }});
-        assertThat(container.add("task", Task.class), sameInstance(task));
-    }
-
-    @Test
-    public void addsTaskWithNameAndConfigureClosure() {
-        final Closure action = HelperUtil.toClosure("{ description = 'description' }");
-        final Map<String, ?> options = singletonMap(Task.TASK_NAME, "task");
-        final Task task = task("task");
-
-        context.checking(new Expectations(){{
-            one(taskFactory).createTask(options);
-            will(returnValue(task));
-            one(task).configure(action);
-            will(returnValue(task));
-        }});
-        assertThat(container.add("task", action), sameInstance(task));
-    }
-
-    @Test
-    public void replacesTaskWithName() {
-        final Map<String, ?> options = singletonMap(Task.TASK_NAME, "task");
-        final Task task = task("task");
-
-        context.checking(new Expectations(){{
-            one(taskFactory).createTask(options);
-            will(returnValue(task));
-        }});
-        assertThat(container.replace("task"), sameInstance(task));
-        assertThat(container.getByName("task"), sameInstance(task));
-    }
-
-    @Test
-    public void replacesTaskWithNameAndType() {
-        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, Task.class);
-        final Task task = task("task");
-
-        context.checking(new Expectations(){{
-            one(taskFactory).createTask(options);
-            will(returnValue(task));
-        }});
-        assertThat(container.replace("task", Task.class), sameInstance(task));
-    }
-
-    @Test
-    public void doesNotFireRuleWhenAddingTask() {
-        Rule rule = context.mock(Rule.class);
-        final Map<String, ?> options = singletonMap(Task.TASK_NAME, "task");
-        final Task task = task("task");
-
-        container.addRule(rule);
-
-        context.checking(new Expectations(){{
-            one(taskFactory).createTask(options);
-            will(returnValue(task));
-        }});
-
-        container.add("task");
-    }
-    
-    @Test
-    public void cannotAddDuplicateTask() {
-        final Task task = addTask("task");
-
-        context.checking(new Expectations() {{
-            one(taskFactory).createTask(singletonMap(Task.TASK_NAME, "task"));
-            will(returnValue(task("task")));
-        }});
-
-        try {
-            container.add("task");
-            fail();
-        } catch (InvalidUserDataException e) {
-            assertThat(e.getMessage(), equalTo("Cannot add [task2] as a task with that name already exists."));
-        }
-
-        assertThat(container.getByName("task"), sameInstance(task));
-    }
-
-    @Test
-    public void canReplaceDuplicateTask() {
-        addTask("task");
-
-        final Task newTask = task("task");
-        context.checking(new Expectations() {{
-            one(taskFactory).createTask(singletonMap(Task.TASK_NAME, "task"));
-            will(returnValue(newTask));
-        }});
-        
-        container.replace("task");
-        assertThat(container.getByName("task"), sameInstance(newTask));
-    }
-
-    @Test
-    public void getByNameFailsForUnknownTask() {
-        try {
-            container.getByName("unknown");
-            fail();
-        } catch (UnknownTaskException e) {
-            assertThat(e.getMessage(), equalTo("Task with name 'unknown' not found in <project>."));
-        }
-    }
-
-    @Test
-    public void canFindTaskByName() {
-        Task task = addTask("task");
-
-        assertThat(container.findByPath("task"), sameInstance(task));
-    }
-
-    @Test
-    public void canFindTaskByRelativePath() {
-        Task task = task("task");
-        expectTaskLookupInOtherProject("sub", "task", task);
-
-        assertThat(container.findByPath("sub:task"), sameInstance(task));
-    }
-
-    @Test
-    public void canFindTaskByAbsolutePath() {
-        Task task = task("task");
-        expectTaskLookupInOtherProject(":", "task", task);
-
-        assertThat(container.findByPath(":task"), sameInstance(task));
-    }
-
-    @Test
-    public void findByPathReturnsNullForUnknownProject() {
-        context.checking(new Expectations(){{
-            allowing(project).findProject(":unknown");
-            will(returnValue(null));
-        }});
-
-        assertThat(container.findByPath(":unknown:task"), nullValue());
-    }
-
-    @Test
-    public void findByPathReturnsNullForUnknownTask() {
-        expectTaskLookupInOtherProject(":other", "task", null);
-
-        assertThat(container.findByPath(":other:task"), nullValue());
-    }
-
-    @Test
-    public void canGetTaskByName() {
-        Task task = addTask("task");
-
-        assertThat(container.getByPath("task"), sameInstance(task));
-    }
-
-    @Test
-    public void canGetTaskByPath() {
-        Task task = addTask("task");
-        expectTaskLookupInOtherProject(":a:b:c", "task", task);
-
-        assertThat(container.getByPath(":a:b:c:task"), sameInstance(task));
-    }
-
-    @Test
-    public void getByPathFailsForUnknownTask() {
-        try {
-            container.getByPath("unknown");
-            fail();
-        } catch (UnknownTaskException e) {
-            assertThat(e.getMessage(), equalTo("Task with path 'unknown' not found in <project>."));
-        }
-    }
-
-    @Test
-    public void resolveLocatesTaskByName() {
-        Task task = addTask("1");
-
-        assertThat(container.resolveTask(1), sameInstance(task));
-    }
-
-    @Test
-    public void resolveLocatesTaskByPath() {
-        Task task = addTask("task");
-        expectTaskLookupInOtherProject(":", "task", task);
-        assertThat(container.resolveTask(new StringBuilder(":task")), sameInstance(task));
-    }
-
-    @Test public void actualizing() {
-        final Task task = addTask("a");
-        context.checking(new Expectations(){{
-            allowing(task).dependsOn("b");
-            allowing(task).getTaskDependencies();
-            TaskDependency aTaskDependency = context.mock(TaskDependency.class);
-            will(returnValue(aTaskDependency));
-
-            final Task b = task("b");
-            one(taskFactory).createTask(singletonMap(Task.TASK_NAME, "b"));
-            will(returnValue(b));
-
-            TaskDependency bTaskDependency = context.mock(TaskDependency.class, "bTaskDependency");
-            allowing(b).getTaskDependencies();
-            will(returnValue(bTaskDependency));
-            allowing(bTaskDependency).getDependencies(b);
-            will(returnValue(Collections.emptySet()));
-
-            allowing(aTaskDependency).getDependencies(task);
-            will(new CustomAction("create task b") {
-                public Object invoke(Invocation invocation) throws Throwable {
-                    container.add("b");
-                    return Collections.singleton(b);
-                }
-            });
-
-        }});
-        task.dependsOn("b");
-
-        assertEquals(1, container.size());
-        container.actualize();
-        assertEquals(2, container.size());
-    }
-    
-    private void expectTaskLookupInOtherProject(final String projectPath, final String taskName, final Task task) {
-        context.checking(new Expectations() {{
-            ProjectInternal otherProject = context.mock(ProjectInternal.class);
-            TaskContainerInternal otherTaskContainer = context.mock(TaskContainerInternal.class);
-
-            allowing(project).findProject(projectPath);
-            will(returnValue(otherProject));
-
-            allowing(otherProject).getTasks();
-            will(returnValue(otherTaskContainer));
-
-            allowing(otherTaskContainer).findByName(taskName);
-            will(returnValue(task));
-        }});
-    }
-
-    private TaskInternal task(final String name) {
-        final TaskInternal task = context.mock(TaskInternal.class, "[task" + ++taskCount + "]");
-        context.checking(new Expectations(){{
-            allowing(task).getName();
-            will(returnValue(name));
-        }});
-        return task;
-    }
-
-    private Task addTask(String name) {
-        final Task task = task(name);
-        final Map<String, ?> options = singletonMap(Task.TASK_NAME, name);
-        context.checking(new Expectations() {{
-            one(taskFactory).createTask(options);
-            will(returnValue(task));
-        }});
-        container.add(name);
-        return task;
-    }
-
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/XmlTransformerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/XmlTransformerTest.groovy
index ff725bb..a6be29b 100644
--- a/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/XmlTransformerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/xml/XmlTransformerTest.groovy
@@ -20,9 +20,13 @@ import org.gradle.api.XmlProvider
 import org.gradle.api.internal.DomNode
 import org.gradle.util.TextUtil
 import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import javax.xml.parsers.DocumentBuilderFactory
 
 class XmlTransformerTest extends Specification {
     final XmlTransformer transformer = new XmlTransformer()
+    @Rule TestNameTestDirectoryProvider tmpDir
 
     def "returns original string when no actions are provided"() {
         expect:
@@ -88,6 +92,21 @@ class XmlTransformerTest extends Specification {
         looksLike '<root>\n  <child1/>\n</root>\n', writer.toString()
     }
 
+    def "can transform String to an OutputStream"() {
+        Action<XmlProvider> action = Mock()
+        transformer.addAction(action)
+        def outputStream = new ByteArrayOutputStream()
+
+        when:
+        transformer.transform('<root/>', outputStream)
+
+        then:
+        action.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child\u03b1')
+        }
+        looksLike '<root>\n  <child\u03b1/>\n</root>\n', outputStream.toByteArray()
+    }
+
     def "can transform Node to a Writer"() {
         Action<XmlProvider> action = Mock()
         transformer.addAction(action)
@@ -104,6 +123,38 @@ class XmlTransformerTest extends Specification {
         looksLike '<root>\n  <child1/>\n</root>\n', writer.toString()
     }
 
+    def "can transform Node to an OutputStream"() {
+        Action<XmlProvider> action = Mock()
+        transformer.addAction(action)
+        def outputStream = new ByteArrayOutputStream()
+        Node node = new XmlParser().parseText('<root/>')
+
+        when:
+        transformer.transform(node, outputStream)
+
+        then:
+        action.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child\u03b1')
+        }
+        looksLike '<root>\n  <child\u03b1/>\n</root>\n', outputStream.toByteArray()
+    }
+
+    def "can transform Node to a File"() {
+        Action<XmlProvider> action = Mock()
+        transformer.addAction(action)
+        File file = tmpDir.file("out.xml")
+        Node node = new XmlParser().parseText('<root/>')
+
+        when:
+        transformer.transform(node, file)
+
+        then:
+        action.execute(_) >> { XmlProvider provider ->
+            provider.asNode().appendNode('child\u03b1')
+        }
+        looksLike '<root>\n  <child\u03b1/>\n</root>\n', file.bytes
+    }
+
     def "can use a closure as an action"() {
         transformer.addAction { provider ->
             provider.asNode().appendNode('child1')
@@ -293,14 +344,15 @@ class XmlTransformerTest extends Specification {
     }
 
     private void looksLike(String expected, String actual) {
-        assert removeTrailingWhitespace(actual) == removeTrailingWhitespace(TextUtil.toPlatformLineSeparators(addXmlDeclaration(expected)))
+        assert removeTrailingWhitespace(actual) == removeTrailingWhitespace(TextUtil.toPlatformLineSeparators("<?xml version=\"1.0\"?>\n" + expected))
     }
 
-    private String removeTrailingWhitespace(String value) {
-        return value.replaceFirst('(?s)\\s+$', "")
+    private void looksLike(String expected, byte[] actual) {
+        DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(actual))
+        assert removeTrailingWhitespace(new String(actual, "utf-8")) == removeTrailingWhitespace(TextUtil.toPlatformLineSeparators("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + expected))
     }
 
-    private String addXmlDeclaration(String value) {
-        "<?xml version=\"1.0\"?>\n" + value
+    private String removeTrailingWhitespace(String value) {
+        return value.replaceFirst('(?s)\\s+$', "")
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
index a030173..347c903 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/DefaultBuildConfigurerTest.groovy
@@ -16,7 +16,6 @@
 package org.gradle.configuration
 
 import org.gradle.StartParameter
-import org.gradle.api.Action
 import org.gradle.api.internal.GradleInternal
 import org.gradle.api.internal.project.ProjectInternal
 import spock.lang.Specification
@@ -25,32 +24,37 @@ class DefaultBuildConfigurerTest extends Specification {
     private startParameter = Mock(StartParameter)
     private gradle = Mock(GradleInternal)
     private rootProject = Mock(ProjectInternal)
-    private action = Mock(Action)
-    private configurer = new DefaultBuildConfigurer(action)
+    private configurer = new DefaultBuildConfigurer()
 
-    def executesActionsForEachProject() {
+    def setup() {
+        gradle.startParameter >> startParameter
+        gradle.rootProject >> rootProject
+    }
+
+    def "configures build for standard mode"() {
         when:
         configurer.configure(gradle)
 
         then:
-        1 * gradle.startParameter >> startParameter
-        _ * gradle.rootProject >> rootProject
+        1 * gradle.addProjectEvaluationListener(_ as ImplicitTasksConfigurer);
+        1 * gradle.addProjectEvaluationListener(_ as ProjectDependencies2TaskResolver);
         1 * rootProject.allprojects(!null) >> { args ->
-            args[0].execute(rootProject)
+            assert args[0] instanceof DefaultBuildConfigurer.ConfigureProject
         }
-        1 * action.execute(rootProject)
+        0 * rootProject._
     }
 
-    def "works in configure on demand mode"() {
+    def "configures build for on demand mode"() {
         when:
         configurer.configure(gradle)
 
         then:
         1 * startParameter.isConfigureOnDemand() >> true
-        1 * gradle.startParameter >> startParameter
-        1 * gradle.addProjectEvaluationListener(_ as ImplicitTasksConfigurer)
-        1 * gradle.rootProject >> rootProject
         1 * rootProject.evaluate()
-        0 * _._
+        0 * rootProject._
+
+        and:
+        1 * gradle.addProjectEvaluationListener(_ as ImplicitTasksConfigurer);
+        1 * gradle.addProjectEvaluationListener(_ as ProjectDependencies2TaskResolver);
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
index 47f0b14..ecbeebe 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/ImplicitTasksConfigurerTest.groovy
@@ -23,14 +23,6 @@ class ImplicitTasksConfigurerTest extends Specification {
     private final ImplicitTasksConfigurer configurer = new ImplicitTasksConfigurer()
     private final ProjectInternal project = HelperUtil.createRootProject()
 
-    def "applies help tasks"() {
-        when:
-        configurer.execute(project)
-
-        then:
-        project.plugins.hasPlugin('help-tasks')
-    }
-
     def "applies help tasks after evaluate"() {
         when:
         configurer.afterEvaluate(project, null)
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.groovy
new file mode 100644
index 0000000..fbc6c84
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.configuration
+
+import org.gradle.api.ProjectEvaluationListener
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.ProjectStateInternal
+import spock.lang.Specification
+
+public class LifecycleProjectEvaluatorTest extends Specification {
+    private project = Mock(ProjectInternal)
+    private listener = Mock(ProjectEvaluationListener)
+    private delegate = Mock(ProjectEvaluator)
+    private state = Mock(ProjectStateInternal)
+    private evaluator = new LifecycleProjectEvaluator(delegate)
+
+    void setup() {
+        project.getProjectEvaluationBroadcaster() >> listener
+    }
+
+    void "nothing happens if project was already configured"() {
+        state.executed >> true
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        0 * delegate._
+    }
+
+    void "nothing happens if project is being configured now"() {
+        state.executing >> true
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        0 * delegate._
+    }
+    
+    void "evaluates the project firing all necessary listeners and updating the state"() {
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        1 * listener.beforeEvaluate(project)
+        1 * state.setExecuting(true)
+
+        then:
+        1 * delegate.evaluate(project, state)
+
+        then:
+        1 * state.setExecuting(false)
+        1 * state.executed()
+        1 * listener.afterEvaluate(project, state)
+    }
+
+    void "notifies listeners and updates states even if there was evaluation failure"() {
+        def failure = new RuntimeException()
+
+        when:
+        evaluator.evaluate(project, state)
+
+        then:
+        def ex = thrown(RuntimeException)
+        ex == failure
+
+        and:
+        delegate.evaluate(project, state) >> { throw failure }
+
+        and:
+        1 * state.setExecuting(false)
+        1 * state.executed()
+        1 * listener.afterEvaluate(project, state)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.java b/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.java
deleted file mode 100644
index 9950f31..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/LifecycleProjectEvaluatorTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.configuration;
-
-import org.gradle.api.ProjectEvaluationListener;
-import org.gradle.api.internal.project.ProjectInternal;
-import org.gradle.api.internal.project.ProjectStateInternal;
-import org.gradle.util.JUnit4GroovyMockery;
-import org.hamcrest.Matchers;
-import org.jmock.Expectations;
-import org.jmock.Sequence;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
- at RunWith(JMock.class)
-public class LifecycleProjectEvaluatorTest {
-    private final JUnit4Mockery context = new JUnit4GroovyMockery();
-    private final ProjectInternal project = context.mock(ProjectInternal.class);
-    private final ProjectEvaluationListener listener = context.mock(ProjectEvaluationListener.class);
-    private final ProjectEvaluator delegate = context.mock(ProjectEvaluator.class, "delegate");
-    private final ProjectStateInternal state = context.mock(ProjectStateInternal.class);
-    private final LifecycleProjectEvaluator evaluator = new LifecycleProjectEvaluator(delegate);
-
-    @Before
-    public void setUp() {
-        context.checking(new Expectations() {{
-            allowing(project).getProjectEvaluationBroadcaster();
-            will(returnValue(listener));
-        }});
-    }
-
-    @Test
-    public void doesNothingWhenProjectHasAlreadyBeenExecuted() {
-        context.checking(new Expectations() {{
-            allowing(state).getExecuted();
-            will(returnValue(true));
-        }});
-
-        evaluator.evaluate(project, state);
-    }
-    
-    @Test
-    public void createsAndExecutesScriptAndNotifiesListener() {
-        context.checking(new Expectations() {{
-            allowing(state).getExecuted();
-            will(returnValue(false));
-
-            Sequence sequence = context.sequence("seq");
-
-            one(listener).beforeEvaluate(project);
-            inSequence(sequence);
-
-            one(state).setExecuting(true);
-            inSequence(sequence);
-
-            one(delegate).evaluate(project, state);
-            inSequence(sequence);
-
-            one(state).setExecuting(false);
-            inSequence(sequence);
-
-            one(state).executed();
-            inSequence(sequence);
-
-            one(listener).afterEvaluate(project, state);
-            inSequence(sequence);
-        }});
-
-        evaluator.evaluate(project, state);
-    }
-
-    @Test
-    public void notifiesListenerOnFailure() {
-        final RuntimeException failure = new RuntimeException();
-
-        context.checking(new Expectations() {{
-            allowing(state).getExecuted();
-            will(returnValue(false));
-
-            Sequence sequence = context.sequence("seq");
-
-            one(listener).beforeEvaluate(project);
-            inSequence(sequence);
-
-            one(state).setExecuting(true);
-            inSequence(sequence);
-
-            one(delegate).evaluate(project, state);
-            will(throwException(failure));
-            inSequence(sequence);
-
-            one(state).setExecuting(false);
-            inSequence(sequence);
-
-            one(state).executed();
-            inSequence(sequence);
-            
-            one(listener).afterEvaluate(project, state);
-            inSequence(sequence);
-        }});
-
-        try {
-            evaluator.evaluate(project, state);
-            fail();
-        } catch (RuntimeException e) {
-            assertThat(e, Matchers.sameInstance(failure));
-        }
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy
index 5a65ba8..b11e6a8 100644
--- a/subprojects/core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy
@@ -17,34 +17,24 @@
 package org.gradle.configuration
 
 import org.gradle.util.HelperUtil
-import static org.junit.Assert.*
-import static org.hamcrest.Matchers.*
-import org.junit.Before
-import org.junit.Test
-import org.gradle.api.Task
-import org.gradle.api.Project
+import spock.lang.Specification
 
 /**
  * @author Hans Dockter
  */
-class ProjectDependencies2TaskResolverTest {
-    Project root
-    Project child
-    Task rootTask
-    Task childTask
-    ProjectDependencies2TaskResolver resolver
+class ProjectDependencies2TaskResolverTest extends Specification {
+    private root = HelperUtil.createRootProject()
+    private child = HelperUtil.createChildProject(root, "child")
+    private rootTask = root.tasks.add('compile')
+    private childTask = child.tasks.add('compile')
 
-    @Before public void setUp()  {
-        resolver = new ProjectDependencies2TaskResolver()
-        root = HelperUtil.createRootProject()
-        child = HelperUtil.createChildProject(root, "child")
-        rootTask = root.tasks.add('compile')
-        childTask = child.tasks.add('compile')
-    }
+    private resolver = new ProjectDependencies2TaskResolver()
 
-    @Test public void testResolve() {
+    void "resolves task dependencies"() {
         child.dependsOn(root.path, false)
-        resolver.execute(child)
-        assertThat(childTask.taskDependencies.getDependencies(childTask), equalTo([rootTask] as Set))
+        when:
+        resolver.afterEvaluate(child, null)
+        then:
+        childTask.taskDependencies.getDependencies(childTask) == [rootTask] as Set
     }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/OnlyWhenConfigureOnDemandTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/OnlyWhenConfigureOnDemandTest.groovy
deleted file mode 100644
index 39fb871..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/execution/OnlyWhenConfigureOnDemandTest.groovy
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.execution
-
-import org.gradle.StartParameter
-import org.gradle.api.internal.GradleInternal
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 1/8/13
- */
-class OnlyWhenConfigureOnDemandTest extends Specification {
-
-    private delegate = Mock(BuildConfigurationAction)
-    private onlyWhen = new OnlyWhenConfigureOnDemand(delegate)
-    private context = Mock(BuildExecutionContext)
-    private startParameter = Mock(StartParameter)
-    private gradle = Mock(GradleInternal)
-
-    def "does not do anything when configure on demand is off"() {
-        given:
-        startParameter.configureOnDemand >> false
-
-        when:
-        onlyWhen.configure(context)
-
-        then:
-        1 * context.gradle >> gradle
-        1 * gradle.startParameter >> startParameter
-        1 * startParameter.configureOnDemand >> false
-        1 * context.proceed()
-        0 * _._
-    }
-
-    def "delegates when configure on demand is off"() {
-        given:
-        startParameter.configureOnDemand >> false
-
-        when:
-        onlyWhen.configure(context)
-
-        then:
-        1 * context.gradle >> gradle
-        1 * gradle.startParameter >> startParameter
-        1 * startParameter.configureOnDemand >> true
-        1 * delegate.configure(context)
-        0 * _._
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy
index a68a457..7ffe1f2 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectEvaluatingActionTest.groovy
@@ -50,10 +50,11 @@ class ProjectEvaluatingActionTest extends Specification {
         1 * context.proceed()
         1 * evaluator.evaluateByPath(project, 'foo')
         1 * evaluator.evaluateByPath(project, 'bar:baz')
+        0 * project.evaluate()
         0 * evaluator._
     }
 
-    def "evaluates the default project even if the task names are empty"() {
+    def "evaluates the default project when the task names are empty"() {
         when:
         action.configure(context)
 
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectFinderByTaskPathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/ProjectFinderByTaskPathTest.groovy
deleted file mode 100644
index 493fbac..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/execution/ProjectFinderByTaskPathTest.groovy
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.execution
-
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.testfixtures.ProjectBuilder
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 1/8/13
- */
-class ProjectFinderByTaskPathTest extends Specification {
-
-    def finder = new ProjectFinderByTaskPath()
-
-    //root->foo->bar->baz
-    ProjectInternal root = new ProjectBuilder().withName("root").build()
-    ProjectInternal foo = new ProjectBuilder().withName("foo").withParent(root).build()
-    ProjectInternal bar = new ProjectBuilder().withName("bar").withParent(foo).build()
-
-    def "finds root"() {
-        expect:
-        finder.findProject(":", root) == root
-        finder.findProject(":", foo) == root
-        finder.findProject(":", bar) == root
-    }
-
-    def "finds project from root"() {
-        expect:
-        finder.findProject(":foo:bar:someTask", foo) == bar
-        finder.findProject(":foo:bar:someTask", bar) == bar
-        finder.findProject(":foo:bar:someTask", root) == bar
-        finder.findProject(":foo:someTask", bar) == foo
-        finder.findProject(":someTask", bar) == root
-    }
-
-    def "finds project relatively"() {
-        expect:
-        finder.findProject("foo:bar:someTask", root) == bar
-        finder.findProject("foo:someTask", root) == foo
-        finder.findProject("bar:someTask", foo) == bar
-    }
-
-    def "provides decent information when project cannot be found"() {
-        when:
-        finder.findProject("foo:xxx:someTask", root)
-        then:
-        def ex = thrown(ProjectFinderByTaskPath.ProjectLookupException)
-        ex.message.contains("xxx")
-    }
-
-    def "only allows valid task path as input"() {
-        when:
-        finder.findProject("someTaskName", foo)
-        then:
-        def ex = thrown(IllegalArgumentException)
-        ex.message.contains('someTaskName')
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy
index c56291d..4a8306c 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/TaskPathProjectEvaluatorTest.groovy
@@ -17,6 +17,8 @@
 package org.gradle.execution
 
 import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.execution.taskpath.ResolvedTaskPath
+import org.gradle.execution.taskpath.TaskPathResolver
 import spock.lang.Specification
 
 /**
@@ -24,30 +26,38 @@ import spock.lang.Specification
  */
 class TaskPathProjectEvaluatorTest extends Specification {
 
-    private finder = Mock(ProjectFinderByTaskPath)
+    private resolver = Mock(TaskPathResolver)
     private project = Mock(ProjectInternal)
 
-    private evaluator = new TaskPathProjectEvaluator(finder)
+    private evaluator = new TaskPathProjectEvaluator(resolver)
 
-    def "evaluates task path"() {
-        def foundProject = Mock(ProjectInternal)
+    def "evaluates project by task path"() {
+        def path = Mock(ResolvedTaskPath)
+        def fooProject = Mock(ProjectInternal)
 
         when:
         evaluator.evaluateByPath(project, ":foo:bar")
 
         then:
-        1 * finder.findProject(":foo:bar", project) >> foundProject
-        1 * foundProject.evaluate()
+        1 * resolver.resolvePath(":foo:bar", project) >> path
+        1 * path.isQualified() >> true
+        1 * path.getProject() >> fooProject
+        1 * fooProject.evaluate()
         0 * _._
     }
 
-    def "evaluates task name"() {
+    def "evaluates all projects"() {
+        def path = Mock(ResolvedTaskPath)
         def subprojects = [Mock(ProjectInternal), Mock(ProjectInternal)]
 
         when:
         evaluator.evaluateByPath(project, "someTask")
 
         then:
+        1 * resolver.resolvePath("someTask", project) >> path
+        1 * path.isQualified() >> false
+
+        and:
         1 * project.evaluate()
         1 * project.subprojects >> subprojects
         1 * subprojects[0].evaluate()
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy
index f35b19c..fc8f67e 100644
--- a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/DefaultTaskExecutionPlanTest.groovy
@@ -168,7 +168,7 @@ public class DefaultTaskExecutionPlanTest extends Specification {
         executionPlan.addToTaskGraph([a, b])
 
         when:
-        def taskInfoA = executionPlan.getTaskToExecute(anyTask)
+        def taskInfoA = taskToExecute
         taskInfoA.executionFailure = failure
         executionPlan.taskComplete(taskInfoA)
 
@@ -183,6 +183,10 @@ public class DefaultTaskExecutionPlanTest extends Specification {
         e == failure
     }
 
+    protected TaskInfo getTaskToExecute() {
+        executionPlan.getTaskToExecute(anyTask)
+    }
+
     def "stops returning tasks on first task failure when no failure handler provided"() {
         RuntimeException failure = new RuntimeException("failure");
         Task a = brokenTask("a", failure);
@@ -295,7 +299,7 @@ public class DefaultTaskExecutionPlanTest extends Specification {
     def getExecutedTasks() {
         def tasks = []
         def taskInfo
-        while ((taskInfo = executionPlan.getTaskToExecute(anyTask)) != null) {
+        while ((taskInfo = taskToExecute) != null) {
             tasks << taskInfo.task
             executionPlan.taskComplete(taskInfo)
         }
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/ParallelTaskExecutionPlanTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/ParallelTaskExecutionPlanTest.groovy
new file mode 100644
index 0000000..afcb0ee
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskgraph/ParallelTaskExecutionPlanTest.groovy
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution.taskgraph;
+
+
+public class ParallelTaskExecutionPlanTest extends DefaultTaskExecutionPlanTest {
+
+    protected TaskInfo getTaskToExecute() {
+        executionPlan.getTaskToExecute()
+    }
+}
+
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 36fa1be..f7a921a 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
@@ -19,15 +19,13 @@ package org.gradle.execution.taskgraph;
 
 import org.gradle.api.internal.changedetection.TaskArtifactStateCacheAccess
 import spock.lang.Specification
-import org.gradle.api.internal.DocumentationRegistry
 
 public class TaskPlanExecutorFactoryTest extends Specification {
     final TaskArtifactStateCacheAccess cache = Mock()
-    final DocumentationRegistry documentationRegistry = Mock()
 
     def "creates a default executor"() {
         when:
-        def factory = new TaskPlanExecutorFactory(cache, 0, documentationRegistry)
+        def factory = new TaskPlanExecutorFactory(cache, 0)
 
         then:
         factory.create().class == DefaultTaskPlanExecutor
@@ -35,7 +33,7 @@ public class TaskPlanExecutorFactoryTest extends Specification {
 
     def "creates a parallel executor"() {
         when:
-        def factory = new TaskPlanExecutorFactory(cache, parallelExecuterCount, documentationRegistry)
+        def factory = new TaskPlanExecutorFactory(cache, parallelExecuterCount)
 
         then:
         factory.create().class == ParallelTaskPlanExecutor
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPathTest.groovy
new file mode 100644
index 0000000..2120e88
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ProjectFinderByTaskPathTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution.taskpath
+
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/8/13
+ */
+class ProjectFinderByTaskPathTest extends Specification {
+
+    def finder = new ProjectFinderByTaskPath()
+
+    //root->foo->bar
+    DefaultProject root = HelperUtil.createRootProject()
+    DefaultProject foo = HelperUtil.createChildProject(root, "foo")
+    DefaultProject bar = HelperUtil.createChildProject(foo, "bar")
+
+    def "finds root"() {
+        expect:
+        finder.findProject(":", root) == root
+        finder.findProject(":", foo) == root
+        finder.findProject(":", bar) == root
+    }
+
+    def "finds project from root"() {
+        expect:
+        finder.findProject(":foo:bar", foo) == bar
+        finder.findProject(":foo:bar", bar) == bar
+        finder.findProject(":foo:bar", root) == bar
+        finder.findProject(":foo", bar) == foo
+    }
+
+    def "finds project relatively"() {
+        expect:
+        finder.findProject("foo:bar", root) == bar
+        finder.findProject("foo", root) == foo
+        finder.findProject("bar", foo) == bar
+    }
+
+    def "provides decent information when project cannot be found"() {
+        when:
+        finder.findProject("foo:xxx", root)
+        then:
+        def ex = thrown(ProjectFinderByTaskPath.ProjectLookupException)
+        ex.message.contains("xxx")
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ResolvedTaskPathTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ResolvedTaskPathTest.groovy
new file mode 100644
index 0000000..6b65fa9
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/ResolvedTaskPathTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution.taskpath
+
+import org.gradle.api.internal.project.ProjectInternal
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/29/13
+ */
+class ResolvedTaskPathTest extends Specification {
+
+    def "knows if path is qualified"() {
+        expect:
+        new ResolvedTaskPath(":foo:", "someTask", Mock(ProjectInternal)).qualified
+        new ResolvedTaskPath(":", "someTask", Mock(ProjectInternal)).qualified
+        !new ResolvedTaskPath("", "someTask", Mock(ProjectInternal)).qualified
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/TaskPathResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/TaskPathResolverTest.groovy
new file mode 100644
index 0000000..c2acb52
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/execution/taskpath/TaskPathResolverTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.execution.taskpath
+
+import org.gradle.api.internal.project.ProjectInternal
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/29/13
+ */
+class TaskPathResolverTest extends Specification {
+
+    private finder = Mock(ProjectFinderByTaskPath)
+    private resolver = new TaskPathResolver(finder)
+    private project = Mock(ProjectInternal)
+
+    def "resolves qualified path"() {
+        def fooProject = Stub(ProjectInternal) {
+            getPath() >> ":x:foo"
+        }
+
+        when:
+        def path = resolver.resolvePath(":x:foo:someTask", project)
+
+        then:
+        1 * finder.findProject(":x:foo", project) >> fooProject
+
+        and:
+        path.qualified
+        path.taskName == 'someTask'
+        path.prefix == ':x:foo:'
+        path.project == fooProject
+    }
+
+    def "resolves qualified relative path"() {
+        def fooProject = Stub(ProjectInternal) {
+            getPath() >> ":x:foo"
+        }
+
+        when:
+        def path = resolver.resolvePath("x:foo:someTask", project)
+
+        then:
+        1 * finder.findProject("x:foo", project) >> fooProject
+
+        and:
+        path.qualified
+        path.taskName == 'someTask'
+        path.prefix == "x:foo:"
+        path.project == fooProject
+    }
+
+    def "resolves qualified root task"() {
+        def root = Stub(ProjectInternal) {
+            getPath() >> ":"
+        }
+
+        when:
+        def path = resolver.resolvePath(":someTask", project)
+
+        then:
+        1 * finder.findProject(":", project) >> root
+
+        and:
+        path.qualified
+        path.taskName == 'someTask'
+        path.prefix == ':'
+        path.project == root
+    }
+
+    def "resolves unqualified path"() {
+        when:
+        def path = resolver.resolvePath("someTask", project)
+
+        then:
+        !path.qualified
+        path.taskName == 'someTask'
+        path.prefix == ''
+        path.project == project
+
+        and:
+        0 * finder.findProject(_, _)
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
index 6efe873..8f86e87 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
@@ -32,11 +32,8 @@ import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.JUnit4GroovyMockery
 import org.jmock.api.Action
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.*
 
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertEquals
@@ -45,6 +42,7 @@ import static org.junit.Assert.assertEquals
  * @author Hans Dockter
  */
 @RunWith(org.jmock.integration.junit4.JMock)
+ at Ignore //TODO SF spockify
 class BuildSourceBuilderTest {
     @Rule public TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     JUnit4GroovyMockery context = new JUnit4GroovyMockery()
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
index 63315d9..553cc21 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultCommandLineConverterTest.java
@@ -58,7 +58,6 @@ public class DefaultCommandLineConverterTest {
     private boolean expectedSearchUpwards = true;
     private boolean expectedDryRun;
     private ShowStacktrace expectedShowStackTrace = ShowStacktrace.INTERNAL_EXCEPTIONS;
-    private String expectedEmbeddedScript = "somescript";
     private LogLevel expectedLogLevel = LogLevel.LIFECYCLE;
     private boolean expectedColorOutput = true;
     private StartParameter actualStartParameter;
@@ -72,6 +71,7 @@ public class DefaultCommandLineConverterTest {
     private RefreshOptions expectedRefreshOptions = RefreshOptions.NONE;
     private boolean expectedRecompileScripts;
     private int expectedParallelExecutorCount;
+    private boolean expectedConfigureOnDemand;
 
     @Test
     public void withoutAnyOptions() {
@@ -111,6 +111,7 @@ public class DefaultCommandLineConverterTest {
         assertEquals(expectedRefreshDependencies, startParameter.isRefreshDependencies());
         assertEquals(expectedProjectCacheDir, startParameter.getProjectCacheDir());
         assertEquals(expectedParallelExecutorCount, startParameter.getParallelThreadCount());
+        assertEquals(expectedConfigureOnDemand, startParameter.isConfigureOnDemand());
     }
 
     @Test
@@ -406,4 +407,10 @@ public class DefaultCommandLineConverterTest {
     public void withInvalidParallelExecutorThreads() {
         checkConversion("--parallel-threads", "foo");
     }
+
+    @Test
+    public void withConfigureOnDemand() {
+        expectedConfigureOnDemand = true;
+        checkConversion("--configure-on-demand");
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy
index 28cec22..43dd49f 100644
--- a/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DependencyResolutionLoggerTest.groovy
@@ -16,17 +16,19 @@
 package org.gradle.initialization
 
 import org.gradle.api.artifacts.ResolvableDependencies
+import org.gradle.logging.ProgressLogger
 import org.gradle.logging.ProgressLoggerFactory
 import spock.lang.Specification
-import org.gradle.logging.ProgressLogger
 
 class DependencyResolutionLoggerTest extends Specification {
-    final ProgressLoggerFactory progressLoggerFactory = Mock()
-    final ResolvableDependencies dependencies = Mock()
-    final ProgressLogger progressLogger = Mock()
-    final DependencyResolutionLogger logger = new DependencyResolutionLogger(progressLoggerFactory)
+    ProgressLoggerFactory loggerFactory = Stub()
+    ResolvableDependencies dependencies = Mock()
+    ProgressLogger progressLogger = Mock()
+    DependencyResolutionLogger logger = new DependencyResolutionLogger(loggerFactory)
 
     def "generates progress logging events as dependency sets are resolved"() {
+        def progressLoggerFactory = Mock(ProgressLoggerFactory)
+        logger = new DependencyResolutionLogger(progressLoggerFactory)
         when:
         logger.beforeResolve(dependencies)
 
@@ -44,4 +46,48 @@ class DependencyResolutionLoggerTest extends Specification {
         1 * progressLogger.completed()
         0 * progressLogger._
     }
+
+    def "stacks loggers in case resolution triggers nested resolution"() {
+        def otherDeps = Mock(ResolvableDependencies, name: "otherDeps")
+        def otherLogger = Mock(ProgressLogger, name: "otherLogger")
+
+        loggerFactory.newOperation(_) >>> [progressLogger, otherLogger]
+
+        when:
+        logger.beforeResolve(dependencies)
+        logger.beforeResolve(otherDeps)
+
+        and:
+        logger.afterResolve(otherDeps)
+        logger.afterResolve(dependencies)
+
+        then:
+        1 * otherLogger.completed()
+        then:
+        1 * progressLogger.completed()
+
+        when:
+        logger.afterResolve(dependencies)
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "cannot complete resolution without starting it first"() {
+        when:
+        logger.afterResolve(dependencies)
+
+        then:
+        thrown(IllegalStateException)
+    }
+
+    def "fails fast if afterResolve called multiple times"() {
+        when:
+        logger.beforeResolve(dependencies)
+
+        logger.afterResolve(dependencies)
+        logger.afterResolve(dependencies) //again
+
+        then:
+        thrown(IllegalStateException)
+    }
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/LayoutCommandLineConverterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/LayoutCommandLineConverterTest.groovy
new file mode 100644
index 0000000..8d307e3
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/initialization/LayoutCommandLineConverterTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.initialization
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 2/19/13
+ */
+class LayoutCommandLineConverterTest extends Specification {
+
+    def converter = new LayoutCommandLineConverter();
+
+    def convert(String... args) {
+        converter.convert(Arrays.asList(args))
+    }
+
+    def "has reasonable defaults"() {
+        expect:
+        convert().projectDir
+        !convert().gradleUserHomeDir
+        convert().searchUpwards == null
+    }
+
+    def "converts"() {
+        expect:
+        convert("-p", "foo").projectDir.name == "foo"
+        convert("-g", "bar").gradleUserHomeDir.name == "bar"
+        !convert("-u").searchUpwards
+    }
+}
diff --git a/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java b/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
index cee26e3..90eb842 100644
--- a/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
+++ b/subprojects/core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
@@ -17,11 +17,9 @@
 package org.gradle.listener;
 
 import org.gradle.api.Action;
-import org.gradle.api.internal.ClosureBackedAction;
 import org.gradle.messaging.dispatch.Dispatch;
 import org.gradle.messaging.dispatch.MethodInvocation;
 import org.gradle.util.JUnit4GroovyMockery;
-import org.gradle.util.TestClosure;
 import org.hamcrest.Description;
 import org.jmock.Expectations;
 import org.jmock.api.Invocation;
@@ -30,7 +28,6 @@ import org.jmock.integration.junit4.JUnit4Mockery;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.gradle.util.HelperUtil.toClosure;
 import static org.gradle.util.Matchers.strictlyEqual;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
@@ -130,33 +127,6 @@ public class ListenerBroadcastTest {
     }
 
     @Test
-    public void canUseClosureToReceiveNotificationsForSingleEventMethod() {
-        final TestClosure testClosure = context.mock(TestClosure.class);
-        context.checking(new Expectations() {{
-            one(testClosure).call("param");
-            will(returnValue("ignore me"));
-        }});
-
-        broadcast.add("event1", new ClosureBackedAction<Object>(toClosure(testClosure)));
-        broadcast.getSource().event1("param");
-    }
-
-    @Test
-    public void doesNotNotifyClosureForOtherEventMethods() {
-        final TestClosure testClosure = context.mock(TestClosure.class);
-
-        broadcast.add("event1", new ClosureBackedAction<Object>(toClosure(testClosure)));
-        broadcast.getSource().event2(9, "param");
-    }
-
-    @Test
-    public void closureCanHaveFewerParametersThanEventMethod() {
-        broadcast.add("event2", new ClosureBackedAction<Object>(toClosure("{ a -> 'result' }")));
-        broadcast.getSource().event2(1, "param");
-        broadcast.getSource().event2(2, null);
-    }
-
-    @Test
     public void canUseActionForSingleEventMethod() {
         final Action<String> action = context.mock(Action.class);
         context.checking(new Expectations() {{
@@ -236,27 +206,6 @@ public class ListenerBroadcastTest {
     }
 
     @Test
-    public void wrapsExceptionThrownByClosure() {
-        final TestClosure testClosure = context.mock(TestClosure.class);
-        final RuntimeException failure = new RuntimeException();
-
-        context.checking(new Expectations() {{
-            one(testClosure).call("param");
-            will(throwException(failure));
-        }});
-
-        broadcast.add("event1", new ClosureBackedAction<Object>(toClosure(testClosure)));
-
-        try {
-            broadcast.getSource().event1("param");
-            fail();
-        } catch (ListenerNotificationException e) {
-            assertThat(e.getMessage(), equalTo("Failed to notify test listener."));
-            assertThat(e.getCause(), sameInstance((Throwable) failure));
-        }
-    }
-
-    @Test
     public void dispatchWrapsExceptionThrownByListener() throws NoSuchMethodException {
         final TestListener listener = context.mock(TestListener.class);
         final RuntimeException failure = new RuntimeException();
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
index 82d09c3..7318bc1 100644
--- a/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/HtmlReportRendererTest.groovy
@@ -15,32 +15,32 @@
  */
 package org.gradle.reporting
 
+import org.gradle.api.internal.html.SimpleHtmlWriter
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.TextUtil
 import org.junit.Rule
-import org.w3c.dom.Element
 import spock.lang.Specification
 
 class HtmlReportRendererTest extends Specification {
-    final DomReportRenderer<String> domRenderer = new DomReportRenderer<String>() {
+    ReportRenderer<String, SimpleHtmlWriter> abstractHtmlReportRenderer = new ReportRenderer<String, SimpleHtmlWriter>() {
         @Override
-        void render(String model, Element parent) {
-            parent.appendChild(parent.ownerDocument.createElement(model))
+        void render(String model, SimpleHtmlWriter htmlWriter) {
+            htmlWriter.startElement("pre").characters(model).endElement()
         }
     }
-    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    @Rule
+    final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final HtmlReportRenderer renderer = new HtmlReportRenderer()
 
     def "renders report to stream"() {
         StringWriter writer = new StringWriter()
-
         when:
-        renderer.renderer(domRenderer).writeTo("test", writer)
+        renderer.renderer(abstractHtmlReportRenderer).writeTo("test", writer)
 
         then:
         writer.toString() == TextUtil.toPlatformLineSeparators('''<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
 <html>
-<test></test>
+<pre>test</pre>
 </html>
 ''')
     }
@@ -52,7 +52,7 @@ class HtmlReportRendererTest extends Specification {
         renderer.requireResource(getClass().getResource("base-style.css"))
 
         when:
-        renderer.renderer(domRenderer).writeTo("test", destFile)
+        renderer.renderer(abstractHtmlReportRenderer).writeTo("test", destFile)
 
         then:
         tmpDir.file("base-style.css").file
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/TabsRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/TabsRendererTest.groovy
index 7cf1791..032f3af 100644
--- a/subprojects/core/src/test/groovy/org/gradle/reporting/TabsRendererTest.groovy
+++ b/subprojects/core/src/test/groovy/org/gradle/reporting/TabsRendererTest.groovy
@@ -15,34 +15,42 @@
  */
 package org.gradle.reporting
 
+import org.gradle.api.internal.html.SimpleHtmlWriter
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
 import spock.lang.Specification
-import org.w3c.dom.Element
-import javax.xml.parsers.DocumentBuilderFactory
 
 class TabsRendererTest extends Specification {
-    final DomReportRenderer<String> contentRenderer = new DomReportRenderer<String>() {
+    final ReportRenderer<String, SimpleHtmlWriter> contentRenderer = new ReportRenderer<String, SimpleHtmlWriter>() {
         @Override
-        void render(String model, Element parent) {
-            parent.appendChild(parent.ownerDocument.createTextNode(model))
+        void render(String model, SimpleHtmlWriter parent) throws IOException {
         }
     }
     final TabsRenderer renderer = new TabsRenderer()
 
     def "renders tabs"() {
+        def writer = new StringWriter()
         given:
-        def doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
-        def parent = doc.createElement("parent")
+        SimpleHtmlWriter htmlBuilder = new SimpleHtmlWriter(writer);
 
         and:
         renderer.add('tab 1', contentRenderer)
         renderer.add('tab 2', contentRenderer)
 
         when:
-        renderer.render("test", parent)
+        renderer.render("test", htmlBuilder)
 
+        def html = html(writer.toString());
         then:
-        parent.childNodes.length == 1
-        parent.childNodes.item(0) instanceof Element
-        parent.childNodes.item(0).nodeName == 'div'
+        html.select("div#tabs > ul > li > a").find { it.text() == "tab 1" }
+        html.select("div#tabs > ul > li > a").find { it.text() == "tab 2" }
+
+        html.select("div#tabs > div#tab0 > h2").find { it.text() == "tab 1" }
+        html.select("div#tabs > div#tab1 > h2").find { it.text() == "tab 2" }
+    }
+
+    Document html(String renderedString) {
+        Jsoup.parse(renderedString)
     }
+
 }
diff --git a/subprojects/core/src/test/groovy/org/gradle/reporting/TextDomReportRendererTest.groovy b/subprojects/core/src/test/groovy/org/gradle/reporting/TextDomReportRendererTest.groovy
deleted file mode 100644
index a781fb2..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/reporting/TextDomReportRendererTest.groovy
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.reporting
-
-import spock.lang.Specification
-import javax.xml.parsers.DocumentBuilderFactory
-import org.w3c.dom.Element
-import org.w3c.dom.Text
-
-class TextDomReportRendererTest extends Specification {
-    final TextReportRenderer<String> textRenderer = new TextReportRenderer<String>() {
-        @Override protected void writeTo(String model, Writer out) {
-            out.write("<html><p>$model</p></html>")
-        }
-    }
-    final TextDomReportRenderer<String> renderer = new TextDomReportRenderer<String>(textRenderer)
-
-    def "converts text to DOM elements"() {
-        given:
-        def doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
-        def parent = doc.createElement("parent")
-
-        when:
-        renderer.render("test", parent)
-
-        then:
-        parent.childNodes.length == 1
-        parent.childNodes.item(0) instanceof Element
-        parent.childNodes.item(0).nodeName == 'p'
-        parent.childNodes.item(0).childNodes.length == 1
-        parent.childNodes.item(0).childNodes.item(0) instanceof Text
-        parent.childNodes.item(0).childNodes.item(0).nodeValue == "test"
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/DeprecationLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/DeprecationLoggerTest.groovy
deleted file mode 100644
index 0d581f0..0000000
--- a/subprojects/core/src/test/groovy/org/gradle/util/DeprecationLoggerTest.groovy
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.util
-
-import org.gradle.internal.Factory
-import org.gradle.logging.ConfigureLogging
-import org.gradle.logging.TestAppender
-import org.junit.Rule
-import spock.lang.Specification
-
-class DeprecationLoggerTest extends Specification {
-    final TestAppender appender = new TestAppender()
-    @Rule final ConfigureLogging logging = new ConfigureLogging(appender)
-
-    public void cleanup() {
-        DeprecationLogger.reset()
-    }
-
-    def "logs deprecation warning once"() {
-        when:
-        DeprecationLogger.nagUserWith("nag")
-        DeprecationLogger.nagUserWith("nag")
-
-        then:
-        appender.toString() == '[WARN nag]'
-    }
-
-    def "does not log warning while disabled with factory"() {
-        Factory<String> factory = Mock()
-
-        when:
-        def result = DeprecationLogger.whileDisabled(factory)
-
-        then:
-        result == 'result'
-
-        and:
-        1 * factory.create() >> {
-            DeprecationLogger.nagUserWith("nag")
-            return "result"
-        }
-        0 * _._
-    }
-
-    def "does not log warning while disabled with action"() {
-        Runnable action = Mock()
-
-        when:
-        DeprecationLogger.whileDisabled(action)
-
-        then:
-        1 * action.run()
-        0 * _._
-    }
-
-    def "deprecation message has next major version"() {
-        given:
-        def major = GradleVersion.current().major
-
-        expect:
-        major != -1
-
-        when:
-        DeprecationLogger.nagUserOfDeprecated("foo", "bar")
-
-        then:
-        appender.toString() == "[WARN foo has been deprecated and is scheduled to be removed in Gradle ${major + 1}.0. bar.]"
-    }
-}
diff --git a/subprojects/core/src/test/groovy/org/gradle/util/SingleMessageLoggerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/util/SingleMessageLoggerTest.groovy
new file mode 100644
index 0000000..c8b198e
--- /dev/null
+++ b/subprojects/core/src/test/groovy/org/gradle/util/SingleMessageLoggerTest.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.util
+
+import org.gradle.internal.Factory
+import org.gradle.logging.ConfigureLogging
+import org.gradle.logging.TestAppender
+import org.junit.Rule
+import spock.lang.Specification
+
+class SingleMessageLoggerTest extends Specification {
+    final TestAppender appender = new TestAppender()
+    @Rule final ConfigureLogging logging = new ConfigureLogging(appender)
+
+    public void cleanup() {
+        SingleMessageLogger.reset()
+    }
+
+    def "logs deprecation warning once"() {
+        when:
+        SingleMessageLogger.nagUserWith("nag")
+        SingleMessageLogger.nagUserWith("nag")
+
+        then:
+        appender.toString() == '[WARN nag]'
+    }
+
+    def "does not log warning while disabled with factory"() {
+        Factory<String> factory = Mock()
+
+        when:
+        def result = SingleMessageLogger.whileDisabled(factory)
+
+        then:
+        result == 'result'
+
+        and:
+        1 * factory.create() >> {
+            SingleMessageLogger.nagUserWith("nag")
+            return "result"
+        }
+        0 * _._
+    }
+
+    def "does not log warning while disabled with action"() {
+        Runnable action = Mock()
+
+        when:
+        SingleMessageLogger.whileDisabled(action)
+
+        then:
+        1 * action.run()
+        0 * _._
+    }
+
+    def "deprecation message has next major version"() {
+        given:
+        def major = GradleVersion.current().major
+
+        expect:
+        major != -1
+
+        when:
+        SingleMessageLogger.nagUserOfDeprecated("foo", "bar")
+
+        then:
+        appender.toString() == "[WARN foo has been deprecated and is scheduled to be removed in Gradle ${major + 1}.0. bar.]"
+    }
+}
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
index 91610da..8b24c26 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/HelperUtil.groovy
@@ -16,20 +16,10 @@
 package org.gradle.util
 
 import org.apache.ivy.core.module.descriptor.Configuration
-import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
-import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
 import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
-import org.apache.ivy.core.module.id.ArtifactId
-import org.apache.ivy.core.module.id.ModuleId
 import org.apache.ivy.core.module.id.ModuleRevisionId
-import org.apache.ivy.plugins.matcher.ExactPatternMatcher
-import org.apache.ivy.plugins.matcher.PatternMatcher
 import org.codehaus.groovy.control.CompilerConfiguration
-import org.gradle.BuildResult
 import org.gradle.api.Task
-import org.gradle.api.artifacts.ModuleDependency
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
-import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.internal.project.ProjectInternal
 import org.gradle.api.internal.project.taskfactory.ITaskFactory
@@ -73,6 +63,10 @@ class HelperUtil {
          return project.services.get(ITaskFactory).createTask([name: name, type: type])
      }
 
+    static ProjectBuilder builder() {
+        return ProjectBuilder.builder().withProjectDir(TestNameTestDirectoryProvider.newInstance().testDirectory)
+    }
+
      static DefaultProject createRootProject() {
          createRootProject(TestNameTestDirectoryProvider.newInstance().testDirectory)
      }
@@ -93,36 +87,12 @@ class HelperUtil {
                  .build();
      }
 
-     static DefaultExcludeRule getTestExcludeRule(def module = 'module') {
-         new DefaultExcludeRule(new ArtifactId(
-                 new ModuleId('org', module), PatternMatcher.ANY_EXPRESSION,
-                 PatternMatcher.ANY_EXPRESSION,
-                 PatternMatcher.ANY_EXPRESSION),
-                 ExactPatternMatcher.INSTANCE, null)
-     }
-
-     static DefaultDependencyDescriptor getTestDescriptor() {
-         new DefaultDependencyDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), false)
-     }
-
      static DefaultModuleDescriptor createModuleDescriptor(Set confs) {
          DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), "status", null)
          confs.each { moduleDescriptor.addConfiguration(new Configuration(it)) }
          return moduleDescriptor;
      }
 
-     static BuildResult createBuildResult(Throwable t) {
-         return new BuildResult(null, t);
-     }
-
-     static ModuleDependency createDependency(String group, String name, String version) {
-       new DefaultExternalModuleDependency(group, name, version)
-     }
-
-     static DefaultPublishArtifact createPublishArtifact(String name, String extension, String type, String classifier) {
-       new DefaultPublishArtifact(name, extension, type, classifier, new Date(), new File(""))
-     }
-
      static groovy.lang.Script createScript(String code) {
          new GroovyShell().parse(code)
      }
diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
index 13e0cd3..5ceada1 100644
--- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
+++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/Matchers.java
@@ -273,14 +273,23 @@ public class Matchers {
     }
 
     @Factory
-    public static Matcher<Task> dependsOn(final Matcher<? extends Iterable<String>> matcher) {
+    public static Matcher<Task> dependsOn(Matcher<? extends Iterable<String>> matcher) {
+        return dependsOn(matcher, false);
+    }
+
+    @Factory
+    public static Matcher<Task> dependsOnPaths(Matcher<? extends Iterable<String>> matcher) {
+        return dependsOn(matcher, true);
+    }
+
+    private static Matcher<Task> dependsOn(final Matcher<? extends Iterable<String>> matcher, final boolean matchOnPaths) {
         return new BaseMatcher<Task>() {
             public boolean matches(Object o) {
                 Task task = (Task) o;
                 Set<String> names = new HashSet<String>();
                 Set<? extends Task> depTasks = task.getTaskDependencies().getDependencies(task);
                 for (Task depTask : depTasks) {
-                    names.add(depTask.getName());
+                    names.add(matchOnPaths ? depTask.getPath() : depTask.getName());
                 }
                 boolean matches = matcher.matches(names);
                 if (!matches) {
diff --git a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
index 2de4f0a..5531fe7 100755
--- a/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
+++ b/subprojects/cpp/src/integTest/groovy/org/gradle/plugins/cpp/CppSamplesIntegrationTest.groovy
@@ -23,9 +23,9 @@ import org.junit.Rule
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 class CppSamplesIntegrationTest extends AbstractBinariesIntegrationSpec {
-    @Rule public final Sample exewithlib = new Sample('cpp/exewithlib')
-    @Rule public final Sample dependencies = new Sample('cpp/dependencies')
-    @Rule public final Sample exe = new Sample('cpp/exe')
+    @Rule public final Sample exewithlib = new Sample(temporaryFolder, 'cpp/exewithlib')
+    @Rule public final Sample dependencies = new Sample(temporaryFolder, 'cpp/dependencies')
+    @Rule public final Sample exe = new Sample(temporaryFolder, 'cpp/exe')
 
     def "exe with lib"() {
         given:
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 2a0e043..dee91ad 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
@@ -589,7 +589,7 @@ org:middle:1.0 -> 2.0+ FAILED
 
     def "shows multiple failed outgoing dependencies"() {
         given:
-        mavenRepo.module("org", "leaf", "1.0")
+        mavenRepo.module("org", "leaf", "1.0").publish()
         mavenRepo.module("org", "top", "1.0")
                 .dependsOn("org", "leaf", "1.0")
                 .dependsOn("org", "leaf", "[1.5,2.0]")
@@ -618,7 +618,7 @@ org:middle:1.0 -> 2.0+ FAILED
         then:
         // TODO - need to use a fixed ordering for dynamic requested versions
         output.contains(toPlatformLineSeparators("""
-org:leaf:1.0 FAILED
+org:leaf:1.0
 \\--- org:top:1.0
      \\--- conf
 """))
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 ae95f5f..94ecc8e 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
@@ -75,7 +75,7 @@ class DependencyReportTaskIntegrationTest extends AbstractIntegrationSpec {
         """
 
         when:
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
         run "dependencies"
 
         then:
@@ -107,7 +107,7 @@ foo
         """
 
         when:
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
         run "dependencies"
 
         then:
@@ -140,7 +140,7 @@ foo
         """
 
         when:
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
         run "dependencies"
 
         then:
@@ -177,7 +177,7 @@ config
         """
 
         when:
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
         run "dependencies"
 
         then:
diff --git a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java
index b1d4122..499fca1 100644
--- a/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java
+++ b/subprojects/diagnostics/src/main/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResult.java
@@ -50,12 +50,9 @@ public abstract class AbstractRenderableDependencyResult implements RenderableDe
     private String getVerboseName() {
         ModuleVersionSelector requested = getRequested();
         ModuleVersionIdentifier selected = getActual();
-        if(!selected.getGroup().equals(requested.getGroup())) {
+        if(!selected.getGroup().equals(requested.getGroup()) || !selected.getName().equals(requested.getName())) {
             return getSimpleName() + " -> " + selected.getGroup() + ":" + selected.getName() + ":" + selected.getVersion();
         }
-        if (!selected.getName().equals(requested.getName())) {
-            return getSimpleName() + " -> " + selected.getName() + ":" + selected.getVersion();
-        }
         if (!selected.getVersion().equals(requested.getVersion())) {
             return getSimpleName() + " -> " + selected.getVersion();
         }
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy
index aa30ef4..82222e1 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/AbstractRenderableDependencyResultSpec.groovy
@@ -20,8 +20,8 @@ import org.gradle.api.artifacts.ModuleVersionIdentifier
 import org.gradle.api.artifacts.ModuleVersionSelector
 import spock.lang.Specification
 
-import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier.newId
+import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 
 /**
  * by Szczepan Faber, created at: 10/9/12
@@ -35,23 +35,20 @@ class AbstractRenderableDependencyResultSpec extends Specification {
         expect:
         dep(requested, newId('org.mockito', 'mockito-core', '1.0')).name == 'org.mockito:mockito-core:1.0'
         dep(requested, newId('org.mockito', 'mockito-core', '2.0')).name == 'org.mockito:mockito-core:1.0 -> 2.0'
-        dep(requested, newId('org.mockito', 'mockito', '1.0')).name == 'org.mockito:mockito-core:1.0 -> mockito:1.0'
+        dep(requested, newId('org.mockito', 'mockito', '1.0')).name == 'org.mockito:mockito-core:1.0 -> org.mockito:mockito:1.0'
         dep(requested, newId('com.mockito', 'mockito', '2.0')).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
     }
 
     private AbstractRenderableDependencyResult dep(ModuleVersionSelector requested, ModuleVersionIdentifier selected) {
         return new AbstractRenderableDependencyResult() {
-            @Override
-            protected ModuleVersionSelector getRequested() {
+            ModuleVersionSelector getRequested() {
                 return requested
             }
 
-            @Override
-            protected ModuleVersionIdentifier getActual() {
+            ModuleVersionIdentifier getActual() {
                 return selected
             }
 
-            @Override
             boolean isResolvable() {
                 throw new UnsupportedOperationException()
             }
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy
index 3593418..d085884 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableDependencyResultTest.groovy
@@ -16,12 +16,12 @@
 package org.gradle.api.tasks.diagnostics.internal.graph.nodes
 
 import org.gradle.api.artifacts.ModuleVersionSelector
+import org.gradle.api.artifacts.result.ResolvedDependencyResult
 import org.gradle.api.artifacts.result.ResolvedModuleVersionResult
 import spock.lang.Specification
 
 import static org.gradle.api.internal.artifacts.DefaultModuleVersionSelector.newSelector
 import static org.gradle.api.internal.artifacts.result.ResolutionResultDataBuilder.newModule
-import org.gradle.api.artifacts.result.ResolvedDependencyResult
 
 /**
  * by Szczepan Faber, created at: 10/9/12
@@ -39,7 +39,7 @@ class RenderableDependencyResultTest extends Specification {
         expect:
         dep(requested, same).name == 'org.mockito:mockito-core:1.0'
         dep(requested, differentVersion).name == 'org.mockito:mockito-core:1.0 -> 2.0'
-        dep(requested, differentName).name == 'org.mockito:mockito-core:1.0 -> mockito:1.0'
+        dep(requested, differentName).name == 'org.mockito:mockito-core:1.0 -> org.mockito:mockito:1.0'
         dep(requested, differentGroup).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
     }
 
diff --git a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy
index 65a5755..8a18941 100644
--- a/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy
+++ b/subprojects/diagnostics/src/test/groovy/org/gradle/api/tasks/diagnostics/internal/graph/nodes/RenderableUnresolvedDependencyResultTest.groovy
@@ -34,7 +34,7 @@ class RenderableUnresolvedDependencyResultTest extends Specification {
         expect:
         dep(requested, same).name == 'org.mockito:mockito-core:1.0'
         dep(requested, differentVersion).name == 'org.mockito:mockito-core:1.0 -> 2.0'
-        dep(requested, differentName).name == 'org.mockito:mockito-core:1.0 -> mockito:1.0'
+        dep(requested, differentName).name == 'org.mockito:mockito-core:1.0 -> org.mockito:mockito:1.0'
         dep(requested, differentGroup).name == 'org.mockito:mockito-core:1.0 -> com.mockito:mockito:2.0'
     }
 
diff --git a/subprojects/docs/docs.gradle b/subprojects/docs/docs.gradle
index 87bba4f..be98e7a 100755
--- a/subprojects/docs/docs.gradle
+++ b/subprojects/docs/docs.gradle
@@ -71,7 +71,7 @@ dependencies {
 
     groovy libraries.groovy
     testCompile "org.pegdown:pegdown:1.1.0"
-    testCompile "org.jsoup:jsoup:1.6.3"
+    testCompile libraries.jsoup
     testCompile "org.gebish:geb-spock:0.9.0-RC-1"
     testCompile 'org.seleniumhq.selenium:selenium-htmlunit-driver:2.28.0'
     testCompile project(":core")
@@ -362,7 +362,6 @@ task userguide {
 }
 
 import org.gradle.plugins.pegdown.PegDown
-import org.gradle.plugins.jsoup.Jsoup
 
 task editReleaseNotes() {
     group = "release notes"
@@ -372,29 +371,18 @@ task editReleaseNotes() {
 }
 
 task releaseNotesMarkdown(type: PegDown) {
+    group = "release notes"
     source "src/docs/release/notes.md"
-    destination "$buildDir/release-notes/notes-raw.html"
-}
-
-task decorateReleaseNotes(type: Jsoup) {
-    source releaseNotesMarkdown
-    destination "$buildDir/release-notes/notes-decorated.html"
-
-    inputs.files cssFiles
-    inputs.file "release-notes-transform.gradle"
-    apply from: "release-notes-transform.gradle"
+    destination "$buildDir/release-notes-raw/release-notes.html"
 }
 
 task releaseNotes(type: Copy) {
     group = "release notes"
     ext.fileName = "release-notes.html"
     into "$docsDir"
-    from decorateReleaseNotes, {
-        rename ".+", fileName
-        doFirst {
-            owner.filter(ReplaceTokens, tokens: [version: project.version.toString(), versionBase: rootProject.versionBase])
-        }
-    }
+    from releaseNotesMarkdown
+    jsoup.plugins "src/transforms/release-notes.gradle"
+    filter(ReplaceTokens, tokens: [version: project.version.toString(), versionBase: rootProject.versionBase])
 }
 
 task viewReleaseNotes(dependsOn: releaseNotes) {
diff --git a/subprojects/docs/release-notes-transform.gradle b/subprojects/docs/release-notes-transform.gradle
deleted file mode 100644
index 54e2346..0000000
--- a/subprojects/docs/release-notes-transform.gradle
+++ /dev/null
@@ -1,222 +0,0 @@
-buildscript {
-    repositories {
-        mavenCentral()
-    }
-    dependencies {
-        classpath ('com.uwyn:jhighlight:1.0') {
-            exclude module: "servlet-api"
-        }
-    }
-}
-
-import org.jsoup.nodes.Element
-import org.jsoup.select.Elements
-import com.uwyn.jhighlight.renderer.XhtmlRendererFactory
-
-decorateReleaseNotes {
-    
-    ext {
-        baseStyleFile = file("$cssFiles.dir/base.css")
-        releaseNotesStyleFile = file("$cssFiles.dir/release-notes.css")
-        scriptFile = file("src/docs/release/content/script.js")
-    }
-    
-    inputs.files([baseStyleFile, releaseNotesStyleFile, configurations.jquery, scriptFile])
-    
-    transform {
-        outputSettings().indentAmount(2).prettyPrint(true)
-        
-        prependChild(new org.jsoup.nodes.DocumentType("html", "", "", ""))
-
-        head().
-            append("<meta charset='utf-8'>").
-            append("<title>Gradle @version@ Release Notes</title>")
-
-        head().append("<style>p{}</style>").children().last().childNode(0).attr("data", baseStyleFile.text + releaseNotesStyleFile.text)
-        
-        head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", configurations.jquery.singleFile.text)
-        head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", scriptFile.text)
-    }
-
-    // Add the extra doc links
-    transform {
-        body().append("""
-            <h2>Gradle @version@ documentation links</h2>
-            <ul>
-                <li><a href='dsl/index.html'>DSL Reference</a></li>
-                <li><a href='userguide/userguide.html'>User Guide</a></li>
-                <li><a href='javadoc/index.html'>Javadoc</a></li>
-                <li><a href='groovydoc/index.html'>Groovdoc</a></li>
-            </ul>
-        """)
-    }
-
-    // wrap each h2 section in section.topic
-    transform {
-        def heading = body().select("h2").first()
-        def inSection = [heading]
-        Element next = heading.nextElementSibling()
-        while (true) {
-            if (next == null || next.tagName() == "h2") {
-                def section = heading.before("<section class='topic'/>").previousElementSibling()
-                Elements inSectionElements = new Elements(inSection)
-                section.html(inSectionElements.outerHtml())
-                inSectionElements.remove()
-
-                if (next == null) {
-                    break
-                } else {
-                    inSection = [next]
-                    heading = next
-                }
-            } else {
-                inSection << next
-            }
-
-            next = next.nextElementSibling()
-        }
-    }
-    
-    // wrap all content after the first element after a h3 (up to the next same level heading)
-    // in a section.major-detail block
-    transform {
-        for (heading in body().select(".topic").select("h3")) {
-            def detail = []
-            
-            Element next = heading.nextElementSibling()
-            while (next != null && next.tagName() != "h4") {
-                next = next.nextElementSibling()
-            }
-            
-            while (true) {
-                if (next == null || next.tagName() ==~ /h[123]/) {
-                    break
-                }
-                detail << next
-                next = next.nextElementSibling()
-            }
-
-            if (detail) {
-                def section = detail.first().before("<section class='major-detail'/>").previousElementSibling()
-                Elements detailElements = new Elements(detail)
-                section.html(detailElements.outerHtml())
-                detailElements.remove()
-            }
-        }
-    }
-    
-    // wrap all content after a h4 until the next heading in a section.minor-detail
-    transform {
-        for (heading in body().select("h4")) {
-            def detail = []
-            Element next = heading.nextElementSibling()
-            while (true) {
-                if (next == null || next.tagName() ==~ /h[1234]/) {
-                    break
-                }
-                detail << next
-                next = next.nextElementSibling()
-            }
-
-            if (detail) {
-                def section = detail.first().before("<section class='minor-detail'/>").previousElementSibling()
-                Elements detailElements = new Elements(detail)
-                section.html(detailElements.outerHtml())
-                detailElements.remove()
-            }
-        }
-    }
-    
-    // add anchors for all of the headings
-    transform {
-        for (heading in body().select("h2,h3")) {
-            def anchorName = heading.text().toLowerCase().replaceAll(' ', '-')
-            heading.attr("id", anchorName)
-        }
-    }
-    
-    // Add the TOC
-    transform {
-        def tocSection = body().select("section.topic").first().before("<section class='table-of-contents'/>").previousElementSibling()
-        tocSection.append("<h2>Table Of Contents</h2>")
-        def toc = tocSection.append("<ul class='toc'/>").children().last()
-        
-        for (topic in body().select(".topic")) {
-            def topicHeading = topic.select("h2").first()
-            def name = topicHeading.text()
-            def anchor = topicHeading.attr("id")
-            
-            toc.append("<li><a/></li>").children().last().select("a").first().text(name).attr("href", "#$anchor")
-            
-            def subs = topic.select("h3")
-            if (subs) {
-                def sublist = toc.children().last().append("<ul class='toc-sub'/>").children().last()
-                subs.each {
-                    def subName = it.text()
-                    def subAnchorName = it.attr("id")
-                    sublist.append("<li><a/></li>").children().last().select("a").first().text(subName).attr("href", "#$subAnchorName")
-                }
-            }
-            
-        }
-    }
-    
-    // Add the heading
-    transform {
-        body().prepend("<h3 class='releaseinfo'>Version @version@</h3>")
-        body().prepend("<h1>Gradle Release Notes</h1>")
-    }
-    
-    // Add the footer
-    transform {
-        def footer = body().append("<section class='footer'/>").children().last()
-        footer.html("Gradle @version@ Release Notes<br />")
-    }
-    
-    // Syntax highlighting
-    transform {
-        body().select("code").each { code ->
-            def parent = code.parent()
-            if (parent.tagName() == "pre") {
-                def text = code.text()
-                def input = new ByteArrayInputStream(code.text().getBytes("utf-8"))
-                def renderer = XhtmlRendererFactory.getRenderer("groovy")
-                def out = new ByteArrayOutputStream()
-                renderer.highlight("test", input, out, "utf-8", true)
-                code.html(new String(out.toByteArray(), "utf-8"))
-                code.select("br").remove()
-                code.childNodes().findAll { it.nodeName().equals("#comment") }*.remove()
-                code.html(code.html().trim())
-                parent.addClass("code")
-            }
-        }
-    }
-    
-    // Terminal styling
-    transform {
-        body().select("tt").each { tt ->
-            def parent = tt.parent()
-            if (parent.tagName() == "pre") {
-                tt.select("br").remove()
-                tt.childNodes().findAll { it.nodeName().equals("#comment") }*.remove()
-                tt.html(tt.html().trim())
-                parent.addClass("tt")
-            }
-        }
-    }
-
-    // Wrap the page in a text container to get the margins
-    transform {
-        def bodyContent = body().children().remove()
-        body().prepend("<div class='text-container'/>")
-        body().children()[0].html(bodyContent.outerHtml())
-    }
-
-    // Turn Gradle issue numbers into issue links
-    transform {
-        def rewritten = body().html().replaceAll(~/GRADLE-\d+/) {
-            "<a href='http://issues.gradle.org/browse/${it}'>${it}</a>"
-        }
-        body().html(rewritten)
-    }
-}
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/css/release-notes.css b/subprojects/docs/src/docs/css/release-notes.css
index c0445f8..476dd9b 100644
--- a/subprojects/docs/src/docs/css/release-notes.css
+++ b/subprojects/docs/src/docs/css/release-notes.css
@@ -5,7 +5,6 @@ img.logo {
 section.major-detail {
   margin: 0 10px 18px;
   padding: 18px;
-  border-left: 2px solid #999;
   border: 1px dotted #999;
   background-color: #FCFCFC;
   border-radius: 5px;
@@ -43,6 +42,32 @@ button.display-toggle {
   cursor: pointer;
 }
 
+h3.incubating {
+    display: inline-block;
+}
+
+a.incubating-marker {
+    display: inline-block;
+    clear: right;
+    color: white;
+    font-style: italic;
+    font-size: 1.0em;
+    text-shadow: 0 0 1px rgba(0, 0, 0, 0.4);
+    margin-left: 0.6em;
+    cursor: help;
+    border-radius: 6px;
+    background-color: rgb(160, 160, 160);
+    padding: 0 5px;
+    box-shadow: 1px 1px 3px 0 black;
+    vertical-align: text-bottom;
+}
+
+
+a.incubating-marker:hover {
+    text-decoration: none;
+    border-bottom: none
+}
+
 section.footer {
   font-size: 75%;
   margin-top: 3em;
@@ -69,4 +94,74 @@ section.footer {
 }
 .java_literal {
   color: #83C283;
+}
+
+/* TipTip CSS - Version 1.2 */
+
+#tiptip_holder {
+    display: none;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 99999;
+}
+
+#tiptip_holder.tip_top {
+    padding-bottom: 5px;
+}
+
+#tiptip_holder.tip_bottom {
+    padding-top: 5px;
+}
+
+#tiptip_holder.tip_right {
+    padding-left: 5px;
+}
+
+#tiptip_holder.tip_left {
+    padding-right: 5px;
+}
+
+#tiptip_content {
+    font-size: 13px;
+    color: #8B8B8B;
+    padding: 6px 10px;
+    border: 2px solid #8B8B8B;
+    background-color: #FFF;
+    border-radius: 8px;
+    box-shadow: 0 0 4px #acacac;
+    text-align: center;
+}
+
+#tiptip_arrow, #tiptip_arrow_inner {
+    position: absolute;
+    border-color: transparent;
+    border-style: solid;
+    border-width: 6px;
+    height: 0;
+    width: 0;
+}
+
+#tiptip_holder.tip_top #tiptip_arrow_inner {
+    margin-top: -7px;
+    margin-left: -6px;
+    border-top-color: #8B8B8B;
+}
+
+#tiptip_holder.tip_bottom #tiptip_arrow_inner {
+    margin-top: -5px;
+    margin-left: -6px;
+    border-bottom-color: #8B8B8B;
+}
+
+#tiptip_holder.tip_right #tiptip_arrow_inner {
+    margin-top: -6px;
+    margin-left: -5px;
+    border-right-color: #8B8B8B;
+}
+
+#tiptip_holder.tip_left #tiptip_arrow_inner {
+    margin-top: -6px;
+    margin-left: -7px;
+    border-left-color: #8B8B8B;
 }
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/dsl.xml b/subprojects/docs/src/docs/dsl/dsl.xml
index 3089f13..d045910 100644
--- a/subprojects/docs/src/docs/dsl/dsl.xml
+++ b/subprojects/docs/src/docs/dsl/dsl.xml
@@ -170,12 +170,24 @@
                 <td>org.gradle.api.publish.ivy.IvyPublication</td>
             </tr>
             <tr>
+                <td>org.gradle.api.publish.ivy.IvyArtifact</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.publish.ivy.IvyArtifactSet</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.publish.ivy.IvyModuleDescriptor</td>
             </tr>
             <tr>
                 <td>org.gradle.api.publish.maven.MavenPublication</td>
             </tr>
             <tr>
+                <td>org.gradle.api.publish.maven.MavenArtifact</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.publish.maven.MavenArtifactSet</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.publish.maven.MavenPom</td>
             </tr>
         </table>
@@ -190,6 +202,9 @@
                 <td>org.gradle.api.artifacts.ConfigurationContainer</td>
             </tr>
             <tr>
+                <td>org.gradle.api.artifacts.dsl.RepositoryHandler</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.artifacts.dsl.DependencyHandler</td>
             </tr>
             <tr>
@@ -268,6 +283,12 @@
                 <td>org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor</td>
             </tr>
             <tr>
+                <td>org.gradle.api.publish.maven.tasks.GenerateMavenPom</td>
+            </tr>
+            <tr>
+                <td>org.gradle.api.reporting.GenerateBuildDashboard</td>
+            </tr>
+            <tr>
                 <td>org.gradle.api.tasks.GradleBuild</td>
             </tr>
             <tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.xml
new file mode 100644
index 0000000..27fabf5
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.xml
@@ -0,0 +1,56 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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>ivy</td>
+            </tr>
+            <tr>
+                <td>maven</td>
+            </tr>
+            <tr>
+                <td>mavenRepo</td>
+            </tr>
+            <tr>
+                <td>mavenCentral</td>
+            </tr>
+            <tr>
+                <td>mavenLocal</td>
+            </tr>
+            <tr>
+                <td>flatDir</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepository.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepository.xml
new file mode 100644
index 0000000..6ae1635
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepository.xml
@@ -0,0 +1,53 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>url</td>
+            </tr>
+            <tr>
+                <td>resolve</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>artifactPattern</td>
+            </tr>
+            <tr>
+                <td>ivyPattern</td>
+            </tr>
+            <tr>
+                <td>layout</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepositoryMetaDataProvider.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepositoryMetaDataProvider.xml
new file mode 100644
index 0000000..7a43a1a
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.IvyArtifactRepositoryMetaDataProvider.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>dynamicMode</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.MavenArtifactRepository.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.MavenArtifactRepository.xml
new file mode 100644
index 0000000..0a7af99
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.artifacts.repositories.MavenArtifactRepository.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>url</td>
+            </tr>
+            <tr>
+                <td>artifactUrls</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>artifactUrls</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.distribution.DistributionContainer.xml
similarity index 100%
copy from subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml
copy to subprojects/docs/src/docs/dsl/org.gradle.api.distribution.DistributionContainer.xml
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.DistributionExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.DistributionExtension.xml
deleted file mode 100644
index 1ad3d3e..0000000
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.DistributionExtension.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<section>
-    <section>
-        <title>Properties</title>
-        <table>
-            <thead>
-                <tr><td>Name</td>
-                    <td>Default value</td>
-                </tr>
-            </thead>
-            <tr>
-                <td>name</td>
-                <td><literal>project.name</literal></td>
-            </tr>
-        </table>
-    </section>
-    <section>
-        <title>Methods</title>
-        <table>
-            <thead>
-                <tr><td>Name</td></tr>
-            </thead>
-        </table>
-    </section>
-</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.MavenRepositoryHandlerConvention.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.MavenRepositoryHandlerConvention.xml
new file mode 100644
index 0000000..6a2ae35
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.MavenRepositoryHandlerConvention.xml
@@ -0,0 +1,44 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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>mavenDeployer</td>
+            </tr>
+            <tr>
+                <td>mavenInstaller</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml
index 180ee4d..6cc225b 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.FindBugs.xml
@@ -41,6 +41,10 @@
                 <td><literal>project.findbugs.reportLevel</literal></td>
             </tr>
             <tr>
+                <td>maxHeapSize</td>
+                <td><literal>null</literal></td>
+            </tr>
+            <tr>
                 <td>visitors</td>
                 <td><literal>project.findbugs.visitors</literal></td>
             </tr>
@@ -75,4 +79,4 @@
             </tr>
         </table>
     </section>
-</section>
\ No newline at end of file
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Pmd.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Pmd.xml
index a777dba..e5ba89a 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Pmd.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.Pmd.xml
@@ -25,6 +25,10 @@
                 <td><literal>project.pmd.ignoreFailures</literal></td>
             </tr>
             <tr>
+                <td>targetJdk</td>
+                <td><literal>project.pmd.targetJdk</literal></td>
+            </tr>
+            <tr>
                 <td>source</td>
                 <td><literal><replaceable>sourceSet</replaceable>.allJava</literal></td>
             </tr>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.PmdExtension.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.PmdExtension.xml
index b88bd6d..b8316e8 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.PmdExtension.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.plugins.quality.PmdExtension.xml
@@ -24,6 +24,10 @@
                 <td>ruleSetFiles</td>
                 <td><literal>[]</literal></td>
             </tr>
+            <tr>
+                <td>targetJdk</td>
+                <td><literal>project.sourceCompatibility</literal></td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml
index f46d04e..5bf3ebb 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.PublicationContainer.xml
@@ -17,6 +17,9 @@
                     <td>Name</td>
                 </tr>
             </thead>
+            <tr>
+                <td>add</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyArtifact.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyArtifact.xml
new file mode 100644
index 0000000..bdcc7b4
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyArtifact.xml
@@ -0,0 +1,59 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>file</td>
+            </tr>
+            <tr>
+                <td>name</td>
+            </tr>
+            <tr>
+                <td>type</td>
+            </tr>
+            <tr>
+                <td>extension</td>
+            </tr>
+            <tr>
+                <td>classifier</td>
+            </tr>
+            <tr>
+                <td>conf</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>builtBy</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyArtifactSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyArtifactSet.xml
new file mode 100644
index 0000000..7edc4d1
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.ivy.IvyArtifactSet.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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>artifact</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenArtifact.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenArtifact.xml
new file mode 100644
index 0000000..c3a62df
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenArtifact.xml
@@ -0,0 +1,34 @@
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>file</td>
+            </tr>
+            <tr>
+                <td>extension</td>
+            </tr>
+            <tr>
+                <td>classifier</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>builtBy</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenArtifactSet.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenArtifactSet.xml
new file mode 100644
index 0000000..7edc4d1
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenArtifactSet.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<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>artifact</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml
index 292a221..c3412b4 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.MavenPublication.xml
@@ -10,6 +10,9 @@
             <tr>
                 <td>pom</td>
             </tr>
+            <tr>
+                <td>artifacts</td>
+            </tr>
         </table>
     </section>
     <section>
@@ -23,6 +26,15 @@
             <tr>
                 <td>pom</td>
             </tr>
+            <tr>
+                <td>from</td>
+            </tr>
+            <tr>
+                <td>artifact</td>
+            </tr>
+            <tr>
+                <td>setArtifacts</td>
+            </tr>
         </table>
     </section>
 </section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.tasks.GenerateMavenPom.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.tasks.GenerateMavenPom.xml
new file mode 100644
index 0000000..a69214a
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.publish.maven.tasks.GenerateMavenPom.xml
@@ -0,0 +1,40 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr><td>destination</td></tr>
+            <tr><td>pom</td></tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.GenerateBuildDashboard.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.GenerateBuildDashboard.xml
new file mode 100644
index 0000000..ad43461
--- /dev/null
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.reporting.GenerateBuildDashboard.xml
@@ -0,0 +1,50 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<section>
+    <section>
+        <title>Properties</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>inputReportsFiles</td>
+            </tr>
+            <tr>
+                <td>reports</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Methods</title>
+        <table>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>reports</td>
+            </tr>
+            <tr>
+                <td>aggregate</td>
+            </tr>
+        </table>
+    </section>
+</section>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.Zip.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.Zip.xml
index f0d7221..74c7ae5 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.Zip.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.Zip.xml
@@ -9,6 +9,10 @@
                 </tr>
             </thead>
             <tr>
+                <td>entryCompression</td>
+                <td><literal>ZipEntryCompression.DEFLATED</literal></td>
+            </tr>
+            <tr>
                 <td>extension</td>
                 <td><literal>zip</literal></td>
             </tr>
@@ -24,4 +28,4 @@
             </thead>
         </table>
     </section>
-</section>
\ No newline at end of file
+</section>
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
index f3d1990..a5dde5f 100644
--- a/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
+++ b/subprojects/docs/src/docs/dsl/org.gradle.plugins.ide.idea.model.IdeaProject.xml
@@ -39,6 +39,11 @@
                 <td><literal><replaceable>${project.projectDir}</replaceable>/<replaceable>${project.name}</replaceable>.ipr</literal></td>
                 <td/>
             </tr>
+            <tr>
+                <td>projectLibraries</td>
+                <td><literal>[]</literal> (<literal>[scala-library]</literal> with <literal>scala-base</literal> plugin)</td>
+                <td><literal>[]</literal> (<literal>[scala-library]</literal> with <literal>scala-base</literal> plugin)</td>
+            </tr>
         </table>
     </section>
     <section>
diff --git a/subprojects/docs/src/docs/dsl/plugins.xml b/subprojects/docs/src/docs/dsl/plugins.xml
index 476b2a7..8920812 100755
--- a/subprojects/docs/src/docs/dsl/plugins.xml
+++ b/subprojects/docs/src/docs/dsl/plugins.xml
@@ -45,9 +45,10 @@
     <plugin id="application" description="Application Plugin">
         <extends targetClass="org.gradle.api.Project" mixinClass="org.gradle.api.plugins.ApplicationPluginConvention"/>
     </plugin>
-    <plugin id="java-library-distribution" description="Java Library Distribution Plugin">
-        <extends targetClass="org.gradle.api.Project" id="distribution" extensionClass="org.gradle.api.plugins.DistributionExtension"/>
+    <plugin id="distribution" description="Distribution Plugin">
+        <extends targetClass="org.gradle.api.Project" id="distributions" extensionClass="org.gradle.api.distribution.DistributionContainer"/>
     </plugin>
+    <plugin id="java-library-distribution" description="Java Library Distribution Plugin"/>
     <plugin id="signing" description="Signing Plugin">
         <extends targetClass="org.gradle.api.Project" id="signing" extensionClass="org.gradle.plugins.signing.SigningExtension"/>
     </plugin>
diff --git a/subprojects/docs/src/docs/release/content/script.js b/subprojects/docs/src/docs/release/content/script.js
index c30e11a..0d6d9d3 100644
--- a/subprojects/docs/src/docs/release/content/script.js
+++ b/subprojects/docs/src/docs/release/content/script.js
@@ -1,3 +1,25 @@
+/*
+ * TipTip
+ * Copyright 2010 Drew Wilson
+ * www.drewwilson.com
+ * code.drewwilson.com/entry/tiptip-jquery-plugin
+ *
+ * Version 1.3   -   Updated: Mar. 23, 2010
+ *
+ * This Plug-In will create a custom tooltip to replace the default
+ * browser tooltip. It is extremely lightweight and very smart in
+ * that it detects the edges of the browser window and will make sure
+ * the tooltip stays within the current window size. As a result the
+ * tooltip will adjust itself to be displayed above, below, to the left
+ * or to the right depending on what is necessary to stay within the
+ * browser window. It is completely customizable as well via CSS.
+ *
+ * This TipTip jQuery plug-in is dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ */
+(function($){$.fn.tipTip=function(options){var defaults={activation:"hover",keepAlive:false,maxWidth:"200px",edgeOffset:3,defaultPosition:"bottom",delay:400,fadeIn:200,fadeOut:200,attribute:"title",content:false,enter:function(){},exit:function(){}};var opts=$.extend(defaults,options);if($("#tiptip_holder").length<=0){var tiptip_holder=$('<div id="tiptip_holder" style="max-width:'+opts.maxWidth+';"></div>');var tiptip_content=$('<div id="tiptip_content"></div>');var tiptip_arrow=$('<div  [...]
+
 $(function() {
   function elementInViewport(el) {
     var rect = el.getBoundingClientRect();
@@ -40,32 +62,29 @@ $(function() {
     });
   }
 
-  // Do fixed issues
-  var fixedIssues = $("h2#fixed-issues");
-  if (fixedIssues.size() > 0) {
-    $("<p class='loading-issues'>Retrieving the fixed issue information for @versionBase@ …</p>").insertAfter(fixedIssues);
-    var para = $("p.loading-issues")
+  function injectIssues(url, insertAfter, idBase, loadingText, messageFunction) {
+    var loadingPara = $("<p class='" + idBase + "-loading'>" + loadingText + " …</p>").insertAfter(insertAfter);
     var animate = true;
     var paraFadeOut = function() {
-      para.fadeOut("80", animate ? paraFadeIn : null);
+      loadingPara.fadeOut("80", animate ? paraFadeIn : null);
     };
     var paraFadeIn = function() {
-      para.fadeIn("80", animate ? paraFadeOut : null);
+      loadingPara.fadeIn("80", animate ? paraFadeOut : null);
     };
     var finishAnimation = function() {
       animate = false;
-      para.remove();
+      loadingPara.remove();
     };
     paraFadeOut();
 
-    $.ajax("http://services.gradle.org/fixed-issues/@versionBase@?callback=?", {
+    $.ajax(url + "?callback=?", {
       dataType: "jsonp",
       cache: true,
       success: function(data, textStatus, jqXHR) {
         finishAnimation();
-        var para = $("<p>" + data.length + " issues have been fixed in Gradle @versionBase at .</p>").insertAfter(fixedIssues);
+        var para = $("<p>" + messageFunction(data.length) + "</p>").insertAfter(insertAfter);
         if (data.length > 0) {
-          var list = $("<ul id='fixed-issues-list'></ul>").hide().insertAfter(para)
+          var list = $("<ul id='" + idBase + "-list'></ul>").hide().insertAfter(para);
           $.each(data, function (i, issue) {
             $("<li>[<a href='" + issue["link"] + "'>"+ issue["key"] + "</a>] - " + issue["summary"] + "</li>").appendTo(list);
           });
@@ -75,63 +94,43 @@ $(function() {
       timeout: 10000,
       error: function() {
         finishAnimation();
-        $("<p>Unable to retrieve the fixed issue information. You may not be connected to the Internet, or there may have been an error.</p>").insertAfter(fixedIssues).css({fontWeight: "bold", color: "red"});
+        $("<p>Unable to retrieve the issue information. You may not be connected to the Internet, or there may have been an error.</p>").insertAfter(insertAfter).css({fontWeight: "bold", color: "red"});
       }
     });
   }
 
-  // Do known issues
-  var knownIssues = $("h2#known-issues");
-  if (knownIssues.size() > 0) {
-    var insertAfter = knownIssues;
+  $('a.incubating-marker').tipTip({
+      maxWidth: '500px',
+      delay: 10
+  });
 
-    var knownIssuesNext = knownIssues.next();
-    if (knownIssuesNext.is("p")) {
-        insertAfter = knownIssuesNext;
+  injectIssues(
+    "http://services.gradle.org/fixed-issues/@versionBase@", 
+    $("h2#fixed-issues"), 
+    "fixed-issues", 
+    "Retrieving the fixed issue information for @versionBase@", 
+    function(i) {
+      return i + " issues have been fixed in Gradle @versionBase at .";
     }
-    $("<p class='loading-issues'>Retrieving the known issue information for @versionBase@ …</p>").insertAfter(insertAfter);
-    var para = $("p.loading-issues")
-    var animate = true;
-    var paraFadeOut = function() {
-      para.fadeOut("80", animate ? paraFadeIn : null);
-    };
-    var paraFadeIn = function() {
-      para.fadeIn("80", animate ? paraFadeOut : null);
-    };
-    var finishAnimation = function() {
-      animate = false;
-      para.remove();
-    };
-    paraFadeOut();
-
-    $.ajax("http://services.gradle.org/known-issues/@versionBase@?callback=?", {
-      dataType: "jsonp",
-      cache: true,
-      success: function(data, textStatus, jqXHR) {
-        finishAnimation();
-
-        if (data.length > 0) {
-          var para = $("<p>There are " + data.length + " known issues of Gradle @versionBase at .</p>").insertAfter(insertAfter);
-          var list = $("<ul id='fixed-issues-list'></ul>").hide().insertAfter(para)
-          $.each(data, function (i, issue) {
-            $("<li>[<a href='" + issue["link"] + "'>"+ issue["key"] + "</a>] - " + issue["summary"] + "</li>").appendTo(list);
-          });
-          list.slideDown("slow");
-        } else {
-            $("<p>There are no known issues of Gradle @versionBase@ at this time.</p>").insertAfter(insertAfter);
-        }
-      },
-      timeout: 10000,
-      error: function() {
-        finishAnimation();
-        $("<p>Unable to retrieve the known issue information. You may not be connected to the Internet, or there may have been an error.</p>").insertAfter(insertAfter).css({fontWeight: "bold", color: "red"});
+  );
+  
+  injectIssues(
+    "http://services.gradle.org/known-issues/@versionBase@", 
+    $("h2#known-issues").next("p"), 
+    "known-issues", 
+    "Retrieving the known issue information for @versionBase@", 
+    function(i) {
+      if (i > 0) {
+        return i + " issues have been fixed in Gradle @versionBase at .";
+      } else {
+        return "There are no known issues of Gradle @versionBase@ at this time.";
       }
-    });
-  }
+    } 
+  );
 
   $("section.major-detail").each(function() {
     addDetailCollapsing($(this));
   });
 
+});
 
-});
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md
index 66d5258..a4aaf63 100644
--- a/subprojects/docs/src/docs/release/notes.md
+++ b/subprojects/docs/src/docs/release/notes.md
@@ -1,587 +1,388 @@
-Together with the usual set of bug fixes and new features, this release comes with some [major performance improvements](#performance-and-memory-consumption).
-Expect to see faster builds that use less heap space with Gradle 1.4.
-
-If you work with Scala and/or Groovy projects, you'll find Gradle more convenient with the auto-detection of [Scala](#automatic-configuration-of-scala-dependency-used-by-scalacompile-and-scaladoc-tasks)
-and [Groovy](#automatic-configuration-of-groovy-dependency-used-by-groovycompile-and-groovydoc-tasks) libraries from the classpath. A Gradle build no longer needs to specifically
-configure a 'groovy' or 'scalaTools' configuration to use these tools.
-Automatic configuration makes it easier to build multiple variants of your software targeting different Scala/Groovy versions, or to use Scala/Groovy only for selected sourceSets.
-
-Dependency management continues to be an important focus and this release brings [some significant improvements](#dependency-resolution-improvements),
-[better reporting](#dependencies-that-cannot-be-resolved-are-listed-in-dependency-reports),
- and a [new, powerful mechanism](#dependency-resolve-rules) providing fine-grained control over dependency resolution.
-
-Keeping Gradle at the forefront of software automation are some exciting new incubating features.
-[Configuration-on-demand](#improved-scalability-via-configuration-on-demand) can reduce startup time for large multi-project builds,
-the [java-library-distribution plugin](#the-new-'java-library-distribution'-plugin) makes it easy to bundle your java project into a distribution,
-and the new [TestReport task](#stand-alone-test-report-task) makes it trivial to aggregate all of your test results into a single report.
-
-TestNG users haven't been forgotten, either. They now enjoy the same [high-quality reports](#brand-new-testng-reports-are-generated-by-default) that JUnit users have long had access to.
-
-And that's just the highlights. Read on for more details on why you should upgrade to Gradle 1.4.
-As always, please share your feedback and experiences with Gradle 1.4 via the [Gradle Forums](http://forums.gradle.org).
+Continuing on with the performance improvements delivered in recent Gradle versions, 1.5 brings wide reaching optimizations to dependency resolution as well as important 
+improvements to two recent features; configure-on-demand and parallel execution. 
+Gradle continues to embrace the challenges of large scale build automation. Along with the performance improvements comes the usual mix of new features, bug fixes, usability improvements and refinements. 
+
+The dependency resolve rule feature introduced in 1.4 gains new capabilities in 1.5. Trouble dependencies can now be completely substituted at resolution time which enables solving 
+some very tricky issues with complex dependency graphs. On the publication side, the new (incubating) Maven and Ivy publishing plugins gain new capabilities making them able 
+to solve more publishing use cases with elegance.
+
+A very exciting trend is the increase in code and documentation contributions from the community, both large and small. Some very useful new features such as the "build dashboard" 
+and "distributions" plugins added in this release, to name a few, came by way of contribution. If you'd like to get involved and contribute to Gradle, a great place to 
+start is [gradle.org/contribute](http://www.gradle.org/contribute).
+
+The Gradle team is also excited to announce the first ever [“Gradle Summit”](http://gradlesummit.com/) (Sponsored by [Gradleware](http://gradleware.com/)),
+held June 13th - 14th in Santa Clara, California. The summit will be two fully packed days of technical sessions given by Gradle core developers ranging from introductory 
+to deep dive as well as informational sessions by several large organizations on how they get the most out of Gradle. In between sessions there'll be plenty of opportunity 
+for talking to other Gradle users and the Gradle development team. This is an event not to miss. 
+Registration is [now open](http://gradlesummit.com/conference/santa_clara/2013/06/gradle/event_register).
+    
+Read on for more details on why you should upgrade to Gradle 1.5. As always, please share your feedback and experiences with Gradle 1.5 via the Gradle Forums.
 
 ## New and noteworthy
 
-### Performance and memory consumption
-
-Gradle 1.4 introduces some important performance improvements, resulting in faster builds that use less heap space. These improvements affect
-dependency resolution, task up-to-date checks and test execution. In other words, everything that a typical Java based project uses in its build.
-
-A typical Java project might see a 10-20% improvement in build time. For builds with many small projects and many unit tests, in combination with the
-Gradle daemon, we are seeing a 50% improvement in build time.
-
-#### Faster dependency resolution
-
-As mentioned below, resolution of Maven SNAPSHOT versions is now faster, due to fewer network requests and various other internal changes.
-
-Artifact meta-data is now cached more efficiently, making it much faster to read and write to disk. This means that resolution of all
-dependencies can be performed more quickly. In particular, reading of cached meta-data is much faster, so that resolution of dependencies that
-have been cached is much faster.
-
-This in turn means that task up-to-date checks are faster, as typically a large portion of the time to perform an up-to-date check is made up of
-the time it takes to resolve the dependencies that form the task inputs.
-
-#### Faster test execution
-
-Test execution typically has a significant effect on build time. While your actual test code won't run any faster in Gradle 1.4, Gradle is now much
-more efficient in managing test execution and generating result and report files.
-
-Gradle runs tests in a separate worker JVM, to keep the build and tests isolated from each other. The mechanism for controlling these workers and
-dispatching tests to them is now much more efficient, meaning that tests will start to execute in the worker processes more quickly, and that the CPU
-cores can spend more of their time executing tests. This mechanism is now much more robust and can handle a very large number of test classes, fixing a
-number of deadlock conditions.
-
-Previously, Gradle collected the test results as XML files and generated the HTML report from these XML files. Test results are now written in an
-efficient binary format, and the XML files and HTML report generated from this. The work of generating the results has moved from the test worker
-processes to the parent build process, meaning that the workers can spend more of their time executing tests, while the results are generated
-asynchronously by the parent. In addition, all result and report file generation streams the content to file, keeping heap usage at a minimum.
-
-### Dependency resolution improvements
-
-As with every release, the dependency resolution engine has been improved with bug fixes and performance optimizations.
-
-These improvements are in addition to the new incubating support for [Dependency Resolve Rules](#dependency-resolve-rules), which give you more control over dependency resolution.
-
-#### Fewer network requests when checking for Maven SNAPSHOT artifact updates (performance)
-
-When checking whether a Maven SNAPSHOT dependency has been updated remotely, fewer network requests are now made to the repository. Previously, multiple 
-requests were made to the `maven-metadata.xml` file where now only one request is made (GRADLE-2585).
-
-This results in faster dependency resolution when using Maven SNAPSHOT dependencies, in particular when importing into an IDE.
-
-#### Faster searching for locally available dependency artifacts (performance)
-
-Before Gradle downloads a dependency artifact from a remote repository, it will selectively search the local file system for that exact same file 
-(i.e. a file with the exact same checksum). For example, Gradle will search the user's “local maven” repository. If the file is found, it will be 
-copied from this location which is much faster than downloading the file (which would be exactly the same) over the network. This is completely 
-transparent and safe.
-
-The algorithm used to search for “local candidates” has been improved and is now faster. This affects all builds using dependency management, especially when building for the first time (GRADLE-2546).
-
-#### Maven SNAPSHOT artifacts with classifiers are now correctly “changing”
-
-For dependencies originating in Maven repositories, Gradle follows Maven semantics and treats any dependency artifact whose version number ends 
-in '`-SNAPSHOT`' as “changing”, 
-which means that it can change over time and Gradle should periodically check for updates instead of caching indefinitely 
-(see “[controlling caching](userguide/dependency_management.html#sec:controlling_caching)”). Previously, artifacts with classifiers
-(e.g `sources` or `javadoc`) were not being checked for changes. This has been fixed in this release (GRADLE-2175).
-
-#### More robust `--offline` mode
-
-Previously, Gradle discarded cached artifacts just prior to attempting to fetch an updated version from the remote source. If the fetch of the remote 
-artifact failed (e.g. network disruption), there was no longer a cached version available to be used in `--offline` mode.
-This resulted in some situations where trying to use `--offline` mode in response to unexpected network issues would not work well.
-
-Gradle now only discards old artifacts after a newer version has been cached, making `--offline` mode more reliable and useful (GRADLE-2364).
-
-#### Using a “maven” layout with an Ivy repository
-
-By default, an Ivy repository will store the module "`org.my.group:module:version`" with a directory pattern like `baseurl/org.my.group/module`,
-while a maven repository would store the same module under `baseurl/org/my/group/module`.
-It is now possible to configure an `ivy` repository that uses the maven directory layout, [using the new `m2compatible` flag with the `pattern` layout](userguide/dependency_management.html#N145D5).
-
-### Dependencies that cannot be resolved are listed in dependency reports
+Here are the new features introduced in this Gradle release.
 
-With earlier versions of Gradle, it was not possible to generate a dependency resolution report if one of the dependencies could not be resolved. This has been rectified.
-Not only will the dependency resolution reports run successfully if a dependency cannot be resolved, the reports will now include details of those dependencies that couldn't be resolved.
+### Performance improvements 
 
-Here is an example for the `dependencies` task:
+This release brings performance improvements for JUnit and TestNG test execution as well as dependency resolution improvements for all builds that use dependency resolution.
 
-<pre><tt>compile - Classpath for compiling the sources.
-\--- foo:bar:1.0
-     \--- foo:baz:2.0 FAILED
-</tt>
-</pre>
+#### Test execution performance
 
-The `FAILED` marker indicates that `foo:baz:2.0`, which is depended upon by `foo:bar:1.0`, couldn't be resolved.
+Test execution has been further optimized in this release, continuing on the work in Gradle 1.4. The test report is now generated more efficiently, so that it take less
+time and heap space to generate the HTML report. In addition, for those projects that use the TestNG framework, the test JVM now starts more quickly, meaning that test
+execution starts earlier than it did in previous Gradle releases.
 
-A similar improvement has been made to the `dependencyInsight` task:
+#### Dependency resolution performance
 
-<pre><tt>foo:baz:2.0 (forced) FAILED
+Gradle's [dependency cache](userguide/dependency_management.html#sec:dependency_cache) is multi process safe, which requires the use of locking mechanisms.
+Improvements to the way the locks are utilised in this release have increased the dependency resolution speed by up to 30%
+for builds that use local repositories or maven local.
+Builds that don't use local repositories should also exhibit slightly faster dependency resolution.
+Every build that resolves dependencies benefits from this improvement.
 
-foo:baz:1.0 -> 2.0 FAILED
-\--- foo:bar:1.0
-     \--- compile
-</tt>
-</pre>
+### Substituting dependencies via dependency resolve rules (i)
 
-In this example, `foo:baz` was forced to version `2.0`, and that version couldn't be resolved.
+Gradle 1.4 [introduced the ability](http://www.gradle.org/docs/1.4/release-notes#dependency-resolve-rules) to dynamically change the version of a dependency to be resolved via dependency resolve rules.
+It is now possible to change the group, name and/or version of a requested dependency, allowing a dependency to be substituted with a completely
+different dependency during resolution.
 
-### Filter dependency resolution reports by configuration
-
-The `dependencies` task now accepts an optional `--configuration` parameter that restricts its output to a particular configuration:
-
-    $ gradle dependencies --configuration compile
-
-This command will display the dependency tree rooted at the `compile` configuration, and (assuming a standard
-Java project) omit the dependency trees for the `runtime`, `testCompile`, and `testRuntime` configurations.
-
-### Automatic configuration of Groovy dependency used by `GroovyCompile` and `Groovydoc` tasks
-
-The `groovy-base` plugin now automatically detects the Groovy dependency used on the compile class path of any `GroovyCompile` or `Groovydoc` task,
-and appropriately configures the task's `groovyClasspath`.
-As a consequence, the Groovy dependency can now be configured directly for the configuration(s) that need it, and it is no longer necessary
-to use the `groovy` configuration.
-
-Old (and still supported):
-
-    dependencies {
-        groovy "org.codehaus.groovy:groovy-all:2.0.5"
-    }
-
-New (and now preferred):
-
-    dependencies {
-        compile "org.codehaus.groovy:groovy-all:2.0.5"
-    }
-
-Automatic configuration makes it easier to build multiple artifact variants targeting different Groovy versions, or to only use Groovy
-for selected source sets:
-
-    dependencies {
-        testCompile "org.codehaus.groovy:groovy-all:2.0.5"
-    }
-
-Apart from the `groovy-all` Jar, Gradle also detects usages of the `groovy` Jar and `-indy` variants. Automatic configuration is disabled
-if a task's `groovyClasspath` is non-empty (for example because the `groovy` configuration is used) or no repositories are declared
-in the project.
-
-### Automatic configuration of Scala dependency used by `ScalaCompile` and `Scaladoc` tasks
-
-The `scala-base` plugin now automatically detects the `scala-library` dependency used on the compile class path of any `ScalaCompile` or `ScalaDoc` task,
-and appropriately configures for the task's `scalaClasspath`.
-As a consequence, it is no longer necessary to use the `scalaTools` configuration.
-
-Old (and still supported):
-
-    dependencies {
-        scalaTools "org.scala-lang:scala-compiler:2.9.2"
-        compile "org.scala-lang:scala-library:2.9.2"
-    }
-
-New (and now preferred):
-
-    dependencies {
-        compile "org.scala-lang:scala-library:2.9.2"
-    }
-
-Automatic configuration makes it easier to build multiple artifact variants targeting different Scala versions. Here is one way to do it:
-
-    apply plugin: "scala-base"
-
-    sourceSets {
-        scala2_8
-        scala2_9
-        scala2_10
-    }
-
-    sourceSets.all { sourceSet ->
-        scala.srcDirs = ["src/main/scala"]
-        resources.srcDirs = ["src/main/resources"]
-
-        def jarTask = task(sourceSet.getTaskName(null, "jar"), type: Jar) {
-            baseName = sourceSet.name
-            from sourceSet.output
-        }
-
-        artifacts {
-            archives jarTask
+    configurations.all {
+        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
+            if (details.requested.name == 'groovy-all') {
+                details.useTarget group: details.requested.group, name: 'groovy', version: details.requested.version
+            }
         }
     }
 
-    repositories {
-        mavenCentral()
-    }
-
-    dependencies {
-        scala2_8Compile "org.scala-lang:scala-library:2.8.2"
-        scala2_9Compile "org.scala-lang:scala-library:2.9.2"
-        scala2_10Compile "org.scala-lang:scala-library:2.10.0-RC5"
-    }
-
-Note that we didn't have to declare the different `scala-compiler` dependencies, nor did we have to assign them
-to the corresponding `ScalaCompile` and `ScalaDoc` tasks. Nevertheless, running `gradle assemble` produces:
-
-<pre><tt>$ ls build/libs
-scala2_10.jar scala2_8.jar  scala2_9.jar
-</tt>
-</pre>
-
-With build variants becoming a first-class Gradle feature, building multiple artifact variants targeting different
-Scala versions will only get easier.
-
-Automatic configuration isn't used if a task's `scalaClasspath` is non-empty (for example because the `scalaTools`
-configuration is used) or no repositories are declared in the project.
-
-### Brand new TestNG reports are generated by default
-
-Gradle 1.3 introduced several incubating improvements to TestNG reports.
-In Gradle 1.4 the improved reports are turned on by default.
-The TestNG users will be delighted to learn that:
-
-* The new HTML report is much easier to read and browse than the standard TestNG report. It's also quite pretty.
-* Both reports, XML (for your CI server) and HTML (for you), contain the test output (i.e. messages logged to the standard streams or via the standard logging toolkits).
-This is extremely useful for debugging certain test failures.
-* The reports neatly work with TestNG and Gradle parallel testing ([test.maxParallelForks](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:maxParallelForks)).
-
-The implementation of the new reports is now a part of Gradle.
-Previously, the report generation was delegated to TestNG's default listeners that are shipped with TestNG library.
-You can switch off the HTML report generation by configuring the [test.testReport](dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:testReport) property.
-If you prefer the old TestNG reports please refer to the [documentation](groovydoc/org/gradle/api/tasks/testing/testng/TestNGOptions.html#useDefaultListeners).
-
-### Easier embedding of Gradle via Tooling API
-
-The [Tooling API](userguide/embedding.html), the standard way to embed Gradle, is now more convenient to use.
-As of Gradle 1.4 it ships as a single jar with the only external dependency being an SLF4J implementation.
-All other dependencies are packaged inside the Jar and shaded to avoid conflicts with the embedder's classpath.
-
-<!--
-## Promoted features
+Dependency resolve rules can now be used to solve some interesting dependency resolution problems:
 
-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.
+- Substituting an alternative implementation for some module. For example, replace all usages of `log4j` with a compatible version of `log4j-over-slf4j`.
+- Dealing with conflicting implementations of some module. For example, replace all usages of the various `slf4j` bindings with `slf4j-simple`.
+- Dealing with conflicting packaging of some module. For example, replace all usages of `groovy-all` with `groovy`.
+- Dealing with modules that have changed their (group, module) identifier. For example, replace `ant:ant:*` with `org.apache.ant:ant:1.7.0` and let conflict resolution take care of the rest.
+- Substituting different implementations at different stages. For example, substitute all servlet API dependencies with `'javax.servlet:servlet-api:2.4'` at compile time and the jetty implementation at test runtime.
 
-The following are the features that have been promoted in this Gradle release.
+For more information, including more code samples, please refer to [the user guide](userguide/dependency_management.html#sec:dependency_resolve_rules).
 
+### New Sonar Runner plugin (i)
 
-### Example promoted
--->
+Gradle 1.5 ships with a new `sonar-runner` plugin that is set to replace the existing Sonar plugin. As its name indicates,
+the new plugin is based on the [Sonar Runner](http://docs.codehaus.org/display/SONAR/Analyzing+with+Sonar+Runner),
+the new and official way to integrate with Sonar. Unlike the old Sonar plugin, the new Sonar Runner plugin
+is compatible with the latest Sonar versions (3.4 and above). To learn more, check out the [Sonar Runner Plugin](userguide/sonar_runner_plugin.html)
+chapter in the Gradle user guide, and the `sonarRunner` samples in the full Gradle distribution.
 
-## Fixed issues
-
-## Incubating features
-
-Incubating features are intended to be used, but not yet guaranteed to be backwards compatible.
-By giving early access to new features, real world feedback can be incorporated into their design.
-See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
-
-The following are the new incubating features or changes to existing incubating features in this Gradle release.
+### IDEA plugin is now Scala aware
 
-### Dependency resolve rules
+When the IDEA plugin encounters a Scala project, it will now add additional configuration to make the
+project compile in IntelliJ IDEA out of the box. In particular, the plugin adds a Scala facet and
+a Scala compiler library that matches the Scala version used on the project's class path.
 
-A “dependency resolve rule” is a user specified algorithm that can influence the resolution of a particular dependency.
-Dependency resolve rules can be used to solve many challenging dependency management problems.
-
-For example, a dependency resolve rule can be used to force all versions with a particular “group” to be of the same version:
-
-    configurations.all {
-        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
-            if (details.requested.group == 'org.gradle') {
-                details.useVersion '1.4'
-            }
-        }
-    }
+### Configure-on-demand improvements (i)
 
-The rule (i.e. the closure given to the `eachDependency` method above) is called for each dependency that is to be resolved. The 
-[`DependencyResolveDetails`](javadoc/org/gradle/api/artifacts/DependencyResolveDetails.html)
-object passed to the rule implementation represents the originally _requested_ and the finally _selected_ version (after conflict resolution
-has been applied). The rule can make a programmatic choice to change how the dependency should be resolved.
+Gradle 1.4 introduced a [new operational mode called “configure-on-demand”](http://www.gradle.org/docs/1.4/release-notes#improved-scalability-via-configuration-on-demand) designed
+to improve Gradle performance on large projects.  This release brings the following improvements to this new feature:
 
-This is an “incubating” feature. In Gradle 1.4, it is only possible to affect the version of the dependency that will be resolved. In future versions,
-more control will be allowed via the `DependencyResolveDetails` type.
+* [Tooling API](userguide/embedding.html) compatibility.
+* [buildSrc](userguide/organizing_build_logic.html#sec:build_sources) is now fully supported.
+* Task dependencies declared via task path are now supported.
 
-Dependency resolve rules are a powerful feature that allow you to do much more than just enforcing a certain version of a dependency in advance
-(which [you can also do](dsl/org.gradle.api.artifacts.ResolutionStrategy.html) with Gradle).
-Many interesting use cases can be implemented with the dependency resolve rules:
+Enabling this new mode is now more convenient. It can be enabled at invocation time via the new `--configure-on-demand` flag, or via the `org.gradle.configureondemand` project property.
+The project property can be set permanently for a project, or permanently for a user just like other [build settings](userguide/build_environment.html#sec:gradle_configuration_properties).
 
-* [Blacklisting a version](userguide/dependency_management.html#sec:blacklisting_version) with a replacement
-* Implementing a [custom versioning scheme](userguide/dependency_management.html#sec:custom_versioning_scheme)
-* [Modelling a releasable unit](userguide/dependency_management.html#sec:releasable_unit) - a set of related libraries that require a consistent version
+For example, by adding a `gradle.properties` file to root of the project with the following content Gradle will always use configure-on-demand mode for the project.
 
-For more information, including more code samples, please refer to this [user guide section](userguide/dependency_management.html#sec:dependency_resolve_rules).
+    #gradle.properties file
+    org.gradle.configureondemand=true
 
-### Improved scalability via configuration on demand
+For more information on configure-on-demand please consult [the user guide](userguide/multi_project_builds.html#sec:configuration_on_demand).
 
-In Gradle, all projects are configured before any task gets executed (see [the build lifecycle](userguide/build_lifecycle.html#sec:build_phases)).
-Huge multi-project builds may have a noticeable configuration time for that reason.
-To improve the experience of working with large multi-project builds "configuration on demand" mode is introduced, where only those projects required
-by the build are configured. This mode is incubating and currently it is not guaranteed to work with every multi-project build.
-It should work very well with builds that have [decoupled projects](userguide/multi_project_builds.html#sec:decoupled_projects)
-(e.g. avoiding having a subproject accessing the model of another project).
-Before you start configuring on demand, please read the section in the [user guide](userguide/multi_project_builds.html#sec:configuration_on_demand).
-Then update your gradle.properties file:
+### Parallel execution improvements (i)
 
-    #gradle.properties file
-    systemProp.org.gradle.configuration.ondemand=true
+Gradle 1.2 introduced a [parallel execution](userguide/multi_project_builds.html#sec:parallel_execution) mode for multi-project builds.
+This release brings significantly improved utilisation of the parallel workers.
 
-### The new 'java-library-distribution' plugin
+Previously, workers where statically assigned to projects and often waited for the upstream dependencies to be built.
+This caused workers to stay idle when there was work they could be doing. The distribution of work is now more dynamic which has resulted in highly parallelizable builds building up to 30% faster.
 
-The new incubating '[`java-library-distribution`](userguide/javaLibraryDistribution_plugin.html)' plugin, contributed by Sébastien Cogneau, makes it is much easier to create a
-standalone distribution for a JVM library.
+It is also now possible to enable parallel building via a [build setting](userguide/build_environment.html#sec:gradle_configuration_properties).
+For example, by adding a `gradle.properties` file to root of the project with the following content Gradle will always build the project in parallel.
 
-Let's walk through a small example. Assume a project with the following layout:
+    //gradle.properties file
+    org.gradle.parallel=true
 
-<pre><tt>MyLibrary
-|____build.gradle
-|____libs // a directory containing third party libraries
-| |____a.jar
-|____src
-| |____main
-| | |____java
-| | | |____SomeLibrary.java
-| |____dist // additional files that should go into the distribution
-| | |____dir2
-| | | |____file2.txt
-| | |____file1.txt
-</tt>
-</pre>
+### New distribution plugin (i)
 
-In the past, it was necessary to declare a custom `zip` task that assembles the distribution. Now, the 'java-library-distribution' will do the job for you:
+Thanks to a contribution from [Sébastien Cogneau](https://github.com/scogneau), a new `distribution` plugin has been added. This plugin adds general-purpose for support bundling and installing distributions.
 
-    apply plugin: 'java-library-distribution'
+This plugin adds a `main` distribution, and you can add additional distributions. For each distribution, tasks are added to create a ZIP or TAR file for the distribution and
+to install the distribution.
 
-    dependencies {
-        runtime files('libs/a.jar')
-    }
+You can define multiple distributions:
 
-    distribution {
-        name = 'MyLibraryDistribution'
+    distributions {
+        enterprise
+        community
     }
 
-Given this configuration, running `gradle distZip` will produce a zip file named `MyLibraryDistribution.zip` that contains the library itself,
-its runtime dependencies, and everything in the `src/dist` directory.
+To build the additional distributions you can run the generated `Zip` tasks `enterpriseDistZip` and `communityDistZip`. For more information, please consult the 
+[user guide](userguide/distribution_plugin.html).
 
-To add further files to the distribution, configure the `distZip` task accordingly:
+### Improved Java library distribution plugin (i)
 
-    distZip {
-        from('aFile')
-        from('anotherFile') {
-            into('dist')
-        }
-    }
+The Java library distribution plugin now extends the newly introduced distribution plugin. Thanks to this, you can now create tar files and install Java library distributions.
 
-### Stand-alone test report task
+For more information, please consult the [user guide](userguide/javaLibraryDistribution_plugin.html).
 
-A new, incubating `TestReport` task type is now available. This task takes the test results generated by one or more `Test` tasks and generates
-a combined HTML test report from them. For example, you can use this task to generate a single test report for all the projects in the build:
+### Improved usability of project dependencies
 
-    task testReport(type: TestReport) {
-        destinationDir = file("$buildDir/reports/all-tests")
-        reportOn subprojects*.test
-    }
+Project dependencies at configuration time are now fully supported.
+Prior to this change, any resolution of a project dependency at configuration time may have led to confusing behavior as the target project may not have been configured yet.
+Now the resolution of the project dependency implies configuration of the target project.
+This means that the order in which projects are configured may now be different (i.e. it will be correct).
+This change should not cause any trouble in existing builds and it fixes up the confusing behavior with project dependencies.
 
-The test report task currently combines test results, but does not aggregate the test results for a given class. So, if a given class is run by
-multiple `Test` tasks, only one execution of the class will be included in the report and the other executions of that class
-will be discarded. This will be addressed in a later Gradle version.
+### Improvements to the new '`maven-publish`' and '`ivy-publish`' plugins (i)
 
-For more details, see the [user guide](userguide/java_plugin.html#test_reporting)
+The '`maven-publish`' and '`ivy-publish`' plugins gain new features and capabilities in this Gradle release.
 
-### Generate `ivy.xml` without publishing
+#### Easy publication of software components
 
-The incubating '`ivy-publish`' plugin introduces a new `GenerateIvyDescriptor` task generates the Ivy metadata file (a.k.a. `ivy.xml`) for publication.
-The task name for the default Ivy publication is '`generateIvyModuleDescriptor`'.
+Gradle 1.5 introduces the concept of a “Software Component”, which defines something that can be produced by a Gradle project such as a Java library or a web application.
+Both the '`ivy-publish`' and '`maven-publish`' plugins are component-aware, simplifying the process of publishing a module. The component defines the set of artifacts and dependencies for publishing.
 
-This function used to be performed internally by the `PublishToIvyRepository` task. By having this function be performed by a separate task
-you can generate the `ivy.xml` metadata file without having to publish your module to an Ivy repository, which makes it easier to test/check
-the descriptor. 
+Presently, the set of components available for publishing is limited to '`java`' and '`web`', added by the '`java`' and '`war`' plugins respectively. In the future it will be possible to
+create new components and new component types.
 
-The `GenerateIvyDescriptor` task also allows the location of the generated Ivy descriptor file to changed from its default location at ‘`build/publications/ivy/ivy.xml`’.
-This is done by setting the `destination` property of the task:
+Publishing the '`web`' component will result in the war file being published with no runtime dependencies (dependencies are bundled in the war):
 
-    apply plugin: 'ivy-publish'
+    apply plugin: 'war'
+    apply plugin: 'maven-publish'
 
     group = 'group'
     version = '1.0'
 
     // … declare dependencies and other config on how to build
 
-    generateIvyModuleDescriptor {
-        destination = 'generated-ivy.xml'
+    publishing {
+        repositories {
+            maven { url 'http://mycompany.org/mavenRepo' }
+            ivy { url 'http://mycompany.org/ivyRepo' }
+        }
+        publications {
+            mavenWeb(MavenPublication) {
+                from components.web
+            }
+            ivyWeb(IvyPublication) {
+                from components.java // Include the standard java artifacts
+            }
+        }
     }
 
-Executing `gradle generateIvyModuleDescriptor` will result in the Ivy module descriptor being written to the file specified. This task is automatically wired
-into the respective `PublishToIvyRepository` tasks, so you do not need to explicitly call this task to publish your module.
-
-### The new ‘maven-publish’ plugin
+#### Publishing custom artifacts
 
-The new incubating ‘maven-publish’ plugin is an alternative to the existing ‘maven’ plugin, and will eventually replace it. This plugin builds on the new publishing model
-that was introduced in Gradle 1.3 with the ‘ivy-publish’ plugin. The new publication mechanism (which is currently “incubating”, including this plugin) will
-expand and improve over the subsequent Gradle releases to provide more convenience and flexibility than the existing publication mechanism plus very powerful features
-to wire your components across builds & teams.
+This release introduces the ability to customize the set of artifacts to publish to a Maven repository or an Ivy repository.
+This gives complete control over which artifacts are published, and the classifier/extension used to publish them.
 
-In the simplest case, publishing to a Maven repository looks like:
+Due to differences in the capabilities of Ivy vs Maven repositories, the DSL is slightly different for each repository format.
 
     apply plugin: 'java'
     apply plugin: 'maven-publish'
+    apply plugin: 'ivy-publish'
 
     group = 'group'
     version = '1.0'
 
     // … declare dependencies and other config on how to build
 
+    task sourceJar(type: Jar) {
+        from sourceSets.main.allJava
+        classifier "source"
+    }
+
     publishing {
         repositories {
-            maven {
-                url 'http://mycompany.org/repo'
-            }
+            maven { url 'http://mycompany.org/mavenRepo' }
+            ivy { url 'http://mycompany.org/ivyRepo' }
         }
-    }
-
-To publish, you simply run the `publish` task. The POM file will be generated and the main artifact uploaded to the declared repository.
-To publish to your local Maven repository (ie 'mvn install') simply run the `publishToMavenLocal` task. You do not need to have `mavenLocal` in your
-`publishing.repositories` section.
-
-To modify the generated POM file, you can use a programmatic hook that modifies the descriptor content as XML.
-
-    publications {
-        maven {
-            pom.withXml {
-                asNode().appendNode('description', 'A demonstration of maven POM customisation')
+        publications {
+            mavenCustom(MavenPublication) {
+                from components.java // Include the standard java artifacts
+                artifact sourceJar {
+                    classifier "source"
+                }
+                artifact("project-docs.htm") {
+                    classifier "docs"
+                    extension "html"
+                    builtBy myDocsTask
+                }
+            }
+            ivyCustom(IvyPublication) {
+                from components.java // Include the standard java artifacts
+                artifact(sourceJar) {
+                    type "source"
+                    conf "runtime"
+                    classifier "source"
+                }
+                artifact("project-docs.htm") {
+                    classifier "docs"
+                    extension "html"
+                    builtBy myDocsTask
+                }
             }
         }
     }
 
-In this example we are adding a ‘`description`’ element for the generated POM. With this hook, you can modify any aspect of the POM.
-For example, you could replace the version range for a dependency with the actual version used to produce the build.
-This allows the POM file to describe how the module should be consumed, rather than be a description of how the module was built.
-
-For more information on the new publishing mechanism, see the [new User Guide chapter](userguide/publishing_maven.html).
-
-## Deprecations
-
-### Changing certain task configuration during and after execution
-
-Much of a task's configuration influences how, or even if, a task should execute. After the task has executed, changing the configuration has no useful effect.
-For example, it does not make sense to add an action via the `doFirst()` method to a task that is executing or has already executed.
-Changing such configuration has been deprecated and this will become an error condition in Gradle 2.0.
+Be sure to check out the DSL reference for [MavenPublication](dsl/org.gradle.api.publish.maven.MavenPublication.html) and [IvyPublication](dsl/org.gradle.api.publish.ivy.IvyPublication.html)
+for complete details on how the set of artifacts can be customized.
 
-#### Changing the action list
+For more information about using the new '`maven-publish`' and '`ivy-publish`' plugins in general, please consult the user guide ([maven](userguide/publishing_maven.html)) ([ivy](userguide/publishing_ivy.html)).
 
-Once a task has started executing, its action list should no longer be changed. This includes calling the following methods on `Task` objects:
+#### Generate POM file without publishing
 
-* `setActions()`
-* `doLast()` - including using the synonymous `<<` operator
-* `doFirst()`
+POM file generation has been moved into a separate task, so that it is now possible to generate the POM file without actually publishing your project. All details of
+the publishing model are still considered in POM generation, including `components`, custom `artifacts`, and any modifications made via `pom.withXml`.
 
-Mutating the collection returned by `getActions()` is also deprecated after the task has started executing.
+The task for generating the POM file is of type [`GenerateMavenPom`](dsl/org.gradle.api.publish.maven.tasks.GenerateMavenPom.html), and is given a name based on the name
+of the publication: `generatePomFileFor<publication-name>Publication`. So in the above example where the publication is named '`mavenCustom`',
+the task will be named `generatePomFileForMavenCustomPublication`.
 
-#### Changing task dependencies 
+#### Full support for Unicode in publication identifiers
 
-Once a task has started executing, its dependencies should no longer be changed. This includes calling the following methods on `Task` objects:
+Where supported by the underlying metadata format, Gradle will now handle any valid Unicode character in module group, name and version as well as artifact name, extension and classifier.
 
-* `dependsOn()`
-* `setDependsOn()`
+The only values that are explicitly prohibited are '\\', '/' and any ISO control character. Supplied values are validated early in publication. 
 
-#### Changing execution conditions
+A couple of caveats to the Unicode support:
 
-Once a task has started executing, its configuration controlling whether it will be execution should no longer be changed. 
-This includes calling the following methods on `Task` objects:
+- Maven restricts '`groupId`' and '`artifactId`' to a limited character set (`[A-Za-z0-9_\\-.]+`) and Gradle enforces this restriction.
+- Certain repositories will not be able to handle all supported characters. For example, the '`:`' character cannot be used
+  as an identifier when publishing to a filesystem-backed repository on Windows.
 
-* `onlyIf()`
-* `setOnlyIf()`
-* `setEnabled()`
+### Support for Ivy dynamic resolve mode (i)
 
-#### Changing task inputs
+It is now possible to enable the equivalent of Ivy's _dynamic resolve_ mode when resolving dependencies. This is only supported for Ivy repositories.
 
-Once a task has started executing, its “inputs” configuration should no longer be changed. 
-This includes calling the following methods on `TaskInputs` objects:
+See the [user guide](userguide/dependency_management.html#ivy_dynamic_resolve_mode) for examples and further details.
 
-* `files()`
-* `file()`
-* `dir()`
-* `property()`
-* `properties()`
-* `source()`
-* `sourceDir()`
+### New build dashboard Plugin (i)
 
-#### Changing task outputs
+Thanks to a contribution from [Marcin Erdmann](https://github.com/erdi), a new `build-dashboard` plugin has been added. This plugin adds a task to projects to generate a build dashboard HTML report which contains
+references to all reports that were generated during the build. In the following example, the `build-dashboard` plugin is added to a project which has also the `groovy` and
+the `codenarc` plugin applied:
 
-Once a task has started executing, its “outputs” configuration should no longer be changed. 
-This includes calling the following methods on `TaskOutputs` objects:
+    apply plugin: 'groovy'
+    apply plugin: 'build-dashboard'
+    apply plugin: 'codenarc'
 
-* `upToDateWhen()`
-* `files()`
-* `file()`
-* `dir()`
+By running the `buildDashboard` task after other tasks that generate reports (e.g. by running `gradle check buildDashboard`), the generated build dashboard contains links to the 
+`codenarc` reports. This version of the build dashboard does not include links to test reports. This plugin is in the early stages of development and will be significantly improved in future Gradle releases.
 
-## Potential breaking changes
-
-### `DependencyReportTask` and `DependencyInsightReportTask` no longer fail when dependencies cannot be resolved
+More information on the `build-dashboard` plugin can be found in the [user guide](userguide/buildDashboard_plugin.html).
+  
+## Fixed issues
 
-Previously, these tasks types would fail if one or more dependencies could not be resolved. Now, they no longer fail and instead display the failed
-dependencies in the appropriate place in the output.
+## Deprecations
 
-### `DependencyInsightReportTask` throws better exception on bad configuration
+Features that have become superseded or irrelevant due to the natural evolution of Gradle become *deprecated*, and scheduled to be removed
+in the next major Gradle version (Gradle 2.0). See the User guide section on the “[Feature Lifecycle](userguide/feature_lifecycle.html)” for more information.
 
-Previously, when the task's configuration was invalid a `ReportException` would be thrown when the task started to execute. For consistency with other tasks, it
-now throws a `InvalidUserDataException`.
+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).
 
-### Copying a `Configuration` also copies its resolution strategy
+### `ArtifactRepositoryContainer.getResolvers()`
 
-Previously, a copied `Configuration` object shared the same `resolutionStrategy` object as the `Configuration` that it was copied from. This meant that changes 
-to `resolutionStrategy` of the source or the copy effected both instances and resulted in undesirable side affects. Copying a `Configuration` now also creates a
-discrete copy of the `resolutionStrategy`.
+This method exposes internal implementation details that will be subject to change in the future. Its use should be avoided.
 
-### Removed `Jvm.getSupportsAppleScript()`
+## Potential breaking changes
 
-In the deprecated internal class `org.gradle.util.Jvm` the method `getSupportsAppleScript()` has been removed.
+### Changes to incubating Maven publishing support
 
-If you need to check if the running JVM supports AppleScript, you can use the following code:
+Breaking changes have been made to the incubating '`maven-publish`' plugin, which provides an alternative means to publish to Maven repositories.
 
-    import javax.script.ScriptEngine
-    import javax.script.ScriptEngineManager
+- A MavenPublication must be explicitly added to the `publications` container; no publication is added implicitly by the `maven-publish` plugin.
+    - If no `MavenPublication` is configured then nothing will be published.
+- A `MavenPublication` does not include any artifacts or dependencies by default; these must be added directly or via a `SoftwareComponent`.
+    - If no artifacts are configured, a Maven POM file will be published with no artifacts or dependencies declared.
+- The `groupId`, `artifactId` and `version` in the published pom cannot be changed via `MavenPom.withXml()`:
+   it was previously possible change these values, but any interproject dependency would not pick up these changes.
+    - In the future Gradle will provide a robust mechanism for modifying publication identity prior to publication.
+- Identifiers used in Maven publications (`groupId`, `artifactId`, `version`, `ext`, `classifier`) have new character restrictions:
+  these identifiers may not contain '`/`', '`\`' or any ISO Control Characters. Using these values generally made it impossible to resolve these modules, so this is now prevented
+  at the time of publication.
+   - `groupId` and `artifactId` are further restricted to "`[A-Za-z0-9_\-.]+`": this is a Maven restriction, so it is enforced at the time of publication.
+- The `GenerateMavenPom` task for a publication is not created until the publishing extension is first accessed. Any attempt to configure a `GenerateMavenPom` task
+  should be enclosed within a `publishing` block.
+- Once the publishing extension is accessed as a property, it is no longer possible to further configure the extension using a `publishing` block.
 
-    ScriptEngineManager mgr = new ScriptEngineManager();
-    ScriptEngine engine = mgr.getEngineByName("AppleScript");
-    boolean isAppleScriptAvailable = engine != null;
+Be sure to check out the [Maven Publishing User Guide Chapter](userguide/publishing_maven.html) and the [MavenPublication DSL reference](dsl/org.gradle.api.publish.maven.MavenPublication.html)
+for complete description and examples of the new Maven Publishing support.
 
-### Changes to new Ivy publishing support
+### Changes to incubating Ivy publishing support
 
-Breaking changes have been made to the new, incubating, Ivy publishing support.
+Breaking changes have been made to the incubating '`ivy-publish`' plugin, which provides an alternative means to publish to Ivy repositories.
 
-Previously, it was possible to set the `descriptorFile` property on an IvyPublication object. This property has been removed with the introduction of the new
-`GenerateIvyDescriptor` task. To specify where the `ivy.xml` file should be generated, set the `destination` property of the `GenerateIvyDescriptor` task.
+- An `IvyPublication` must be explicitly added to the `publications` container; no publication is added implicitly by the `ivy-publish` plugin.
+    - If no `IvyPublication` is configured then nothing will be published.
+- An `IvyPublication` does not include any artifacts or dependencies by default; these must be added directly or via a `SoftwareComponent`.
+    - If no artifacts are configured, an `ivy.xml` file will be published with no artifacts or dependencies declared.
+- The `organisation`, `name` and `revision` cannot be changed via `IvyDescriptor.withXml()`:
+   it was previously possible to do this, although it did not change the actual identity of the published module.
+    - In the future Gradle will provide a robust mechanism for modifying publication identity prior to publication.
+- Identifiers in ivy modules (`organisation`, `module`, `revision`) and artifacts (`name`, `ext`, `type`, `classifier`) have new character restrictions:
+  these identifiers may not contain '`/`', '`\`' or any ISO Control Characters. Using these values generally made it impossible to resolve these modules, so this is now prevented
+  at the time of publication.
+- Removed `GenerateIvyDescriptor.xmlAction` property. The `ivy.descriptor.withXml()` method provides a way to customise the generated module descriptor.
+- The `GenerateIvyDescriptor` task for a publication is not created until the publishing extension is first accessed. Any attempt to configure a `GenerateIvyDescriptor`
+  should be enclosed within a `publishing` block.
+- Once the publishing extension is accessed as a property, it is no longer possible to further configure the extension using a `publishing` block.
 
-Previously _all_ configurations of the project were published. Now, only the ‘`archives`’ configuration together with the ‘`default`’ configuration and its 
-ancestors will be published. In practice, this means that a Java project's `testCompile` and `testRuntime` configurations will no longer be published by default.
+Be sure to check out the [Ivy Publishing User Guide Chapter](userguide/publishing_ivy.html) and the [IvyPublication DSL reference](dsl/org.gradle.api.publish.ivy.IvyPublication.html)
+for complete description and examples of the new Ivy Publishing support.
 
-### Changed default value for `TestNGOptions.useDefaultListeners`
+### Project configuration order
 
-The default value for [TestNGOptions.useDefaultListeners](groovydoc/org/gradle/api/tasks/testing/testng/TestNGOptions.html#useDefaultListeners) has changed from `true` to `false`
-so that Gradle can take over generation of the reports.
-This way Gradle can provide invaluable improvements to the reporting - for more information read the earlier section on TestNG reports.
+Improving the usability of project dependencies (see the section above) might change the order in which projects are configured.
+This is not expected to cause problems in existing builds, but is mentioned for completeness.
 
-### Updated default versions of Checkstyle and CodeNarc
+### Optimized order of task execution in parallel execution mode
 
-The default version of Checkstyle used for the '`checkstyle`' plugin has been updated from `5.5` to `5.6`.
+Parallel builds are now much faster due to better utilisation of parallel workers. However, this means that tasks may be executed in different order in parallel builds.
+This will not cause problems in a correctly [decoupled build](userguide/multi_project_builds.html#sec:decoupled_projects) but may bring problems to light in builds that are not properly decoupled.
 
-The default version of CodeNarc used for the '`codenarc`' plugin has been updated from `0.16.1` to `0.18`.
+### Changes to the incubating Java library distribution plugin
 
-### `eclipseWtpComponent` task overrides dependent modules
+The `distribution` extension that is added by the `java-library-distribution` plugin was removed. The `main` distribution is now accessible using the `distributions` extension:
 
-Previously, the `eclipse-wtp` plugin's `eclipseWtpComponent` task would add generated `dependent-module` entries to those already contained in the
-`.settings/org.eclipse.wst.common.component` file. This could lead to stale and duplicated entries (see `GRADLE-2526`). Now, existing
-entries are overridden with generated entries, just like it's done for `classpathentry` elements in `.classpath` files.
+    distributions {
+        main {
+            ...
+        }
+    }
 
 ## External contributions
 
-We would like to thank the following community members for making contributions to this release of Gradle.
-
-* Sébastien Cogneau - Contributing the `java-library-distribution` plugin
-* James Bengeyfield - `showViolations` flag for `Checkstyle` task (GRADLE-1656)
-* Dalibor Novak - `m2compatible` flag on `PatternRepositoryLayout` (GRADLE-1919)
-* Brian Roberts, Tom Denley - Support multi-line JUnit test names (for better ScalaTest compatibility) (GRADLE-2572)
-* Sean Gillespie - Extend the application plugin to build a tar distribution
+We would like to thank the following community members for making excellent contributions to this release of Gradle.
+
+* [Joe Sortelli](https://github.com/sortelli) - Fixed incorrect handling of `ivy.xml` where dependency configuration contained wildcard values (GRADLE-2352)
+* [David M. Carr](https://github.com/davidmc24)
+    * When JUnit tests have assumption failures, treat them as "skipped" (GRADLE-2454)
+    * Documentation cleanups.
+* [Sébastien Cogneau](https://github.com/scogneau) - Introduce the distribution plugin
+* [Kenny Stridh](https://github.com/kensi)
+    * Allow specifying `targetJdk` for PMD code analysis (GRADLE-2106)
+    * Added support for PMD version 5.0.+
+* [Marcin Erdmann](https://github.com/erdi)
+    * Add`build-dashboard` plugin
+    * Make notify-send notifications transient in Gnome Shell
+* [Michael R. Maletich](https://github.com/HawaiianSpork)
+    * Add `maxHeapSize` property to `FindBugs` task to allow setting the max heap size for spawned FindBugs java process
+    * Add `contentsCompression` property to the `Zip` task type to specify the compression level of the archive
+* [Barry Pitman](https://github.com/barrypitman) - Fixed Maven conversion problem (GRADLE-2645)
+* [Kallin Nagelberg](https://github.com/Kallin) - Fixed grammar in the `SourceSetOutput` documentation
+* [Klaus Illusioni](https://github.com/illusioni) - Fixed Eclipse wtp component generation issue (GRADLE-2653)
+* [Alex Birmingham](https://github.com/abirmingham) - Fixed PMD Javadoc
+* [Matthieu Leclercq](https://github.com/mleclercq) - Fixed the nested configuration resolution issue (GRADLE-2477)
+* [Dan Stine](https://github.com/dstine) - Userguide cleanups.
 
 We love getting contributions from the Gradle community. For information on contributing, please see [gradle.org/contribute](http://gradle.org/contribute).
 
diff --git a/subprojects/docs/src/docs/userguide/artifactMngmt.xml b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
index 38608ba..604efb2 100644
--- a/subprojects/docs/src/docs/userguide/artifactMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/artifactMngmt.xml
@@ -21,7 +21,7 @@
             While this new mechanism is <emphasis>incubating</emphasis> and not yet complete, it introduces some new concepts and features that do (and will) make Gradle publishing even more powerful.
         </para>
         <para>
-            You can read about the new publishing plugins in <xref linkend="publishing_ivy"/> and <xref linkend="publishing_maven"/>.
+            You can read about the new publishing plugins in <xref linkend="publishing_ivy"/> and <xref linkend="publishing_maven"/>. Please try them out and give us feedback.
         </para>
     </note>
     <section>
@@ -97,7 +97,7 @@
         <para>If an upload repository is defined with multiple patterns, Gradle must choose a pattern to use for uploading each file.
             By default, Gradle will upload to the pattern defined by the <literal>url</literal> parameter, combined with the optional <literal>layout</literal> parameter.
             If no <literal>url</literal> parameter is supplied, then Gradle will use the first defined <literal>artifactPattern</literal> for uploading,
-            or the first defined <literal>ivyPattern</literal> for uploading ivy files, if this is set.
+            or the first defined <literal>ivyPattern</literal> for uploading Ivy files, if this is set.
         </para>
         <para>Uploading to a Maven repository is described in <xref linkend="uploading_to_maven_repositories"/>.</para>
     </section>
@@ -116,7 +116,7 @@
             is not specified, the <code>default</code> configuration is used (see <xref linkend="sec:dependency_configurations"/>).
             Using your project as a library
             can either happen from within a multi-project build or by retrieving your project from a repository. In
-            the latter case, an ivy.xml descriptor in the repository is supposed to contain all the necessary information. If you
+            the latter case, an <filename>ivy.xml</filename> descriptor in the repository is supposed to contain all the necessary information. If you
             work with Maven repositories you don't have the flexibility as described above. For how to publish to a Maven
             repository, see the section <xref linkend="uploading_to_maven_repositories"/>.
         </para>
diff --git a/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml b/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml
index 0f34fc2..837e740 100644
--- a/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/bootstrapPlugin.xml
@@ -24,7 +24,7 @@
         The api, plugin and task names may change before the final release.
         Please let us know your feedback or report any issues.</para>
     <para>
-        The plugin works by obtaining the effective pom of the current project
+        The plugin works by obtaining the effective POM of the current project
         by executing external 'mvn' command. Then it reads the dependencies
         and other information to generate build.gradle scripts.</para>
     <para>
@@ -36,8 +36,8 @@
     <section>
         <title>Maven conversion - features</title>
         <itemizedlist>
-            <listitem>Uses effective pom and effective settings
-            (support for pom inheritance, dependency management, properties)</listitem>
+            <listitem>Uses effective POM and effective settings
+            (support for POM inheritance, dependency management, properties)</listitem>
             <listitem>Supports both single module and multimodule projects.
                 Generates settings.gradle for multimodule projects (*).</listitem>
             <listitem>Supports custom module names (that differ from directory names)</listitem>
@@ -45,7 +45,7 @@
             <listitem>Applies maven, java and war plugins (as needed)</listitem>
             <listitem>Supports packaging war projects as jars if needed</listitem>
             <listitem>Generates dependencies (both external and inter-module)</listitem>
-            <listitem>Generates download repositories (inc. local maven repository)</listitem>
+            <listitem>Generates download repositories (inc. local Maven repository)</listitem>
             <listitem>Adjusts java compiler settings</listitem>
             <listitem>Supports packaging of sources and tests</listitem>
             <listitem>Supports testng runner</listitem>
@@ -64,25 +64,25 @@
 
     <section>
         <title>Usage</title>
-        <para>To convert a maven project follow the steps:</para>
+        <para>To convert a Maven project follow the steps:</para>
         <itemizedlist>
-            <listitem>Make sure your maven project builds and uses maven3.</listitem>
+            <listitem>Make sure your Maven project builds and uses maven3.</listitem>
             <listitem>Make sure <code>mvn</code> command can be executed and it runs maven3.</listitem>
-            <listitem>Create <filename>build.gradle</filename> file in the root folder of your maven project.</listitem>
+            <listitem>Create <filename>build.gradle</filename> file in the root folder of your Maven project.</listitem>
             <listitem>Specify <code>apply plugin: 'maven2Gradle'</code> and nothing else
                 in the <filename>build.gradle</filename> file.</listitem>
             <listitem>Make sure you are using the Gradle version that contains the plugin.
                 If necessary download the required Gradle version.
                 Until Gradle 1.2 is released you should use the
                 <ulink url="website:nightly">nightly build</ulink>.
-                You only need this version for conversion of the maven project.
+                You only need this version for conversion of the Maven project.
                 When converting is complete feel free to use the desired Gradle version, for example 1.1.
             </listitem>
             <listitem>Run <code>gradle tasks</code>. You should see <code>maven2Gradle</code> task available.</listitem>
             <listitem>Run <code>gradle maven2Gradle</code>.</listitem>
             <listitem>Advanced users: you can configure following boolean properties on the <code>maven2Gradle</code> task:
-                <code>verbose</code> (shows more output, including the effective pom)
-                and <code>keepFile</code> (keeps the obtained effective pom file).</listitem>
+                <code>verbose</code> (shows more output, including the effective POM)
+                and <code>keepFile</code> (keeps the obtained effective POM file).</listitem>
         </itemizedlist>
     </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml b/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml
index ade0fda..fb9d064 100644
--- a/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/buildAnnouncementsPlugin.xml
@@ -2,7 +2,7 @@
     <title>The Build Announcements Plugin</title>
     <note>
         <para>
-            The build announcements is incubating and should not be considered stable.
+            The build announcements is incubating (see <xref linkend="sec:incubating_state"/>).
         </para>
     </note>
     <para>The build announcements plugin uses the <link linkend="announce_plugin">announce</link> plugin to send local announcements on important events in the build.</para>
diff --git a/subprojects/docs/src/docs/userguide/buildDashboardPlugin.xml b/subprojects/docs/src/docs/userguide/buildDashboardPlugin.xml
new file mode 100644
index 0000000..47a64a6
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/buildDashboardPlugin.xml
@@ -0,0 +1,76 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<chapter id="buildDashboard_plugin">
+    <title>The Build Dashboard Plugin</title>
+    <note>
+        <para>
+            The Build Dashboard plugin is incubating (see <xref linkend="sec:incubating_state"/>).
+        </para>
+    </note>
+    <para>The Build Dashboard plugin adds a task to projects which generates build dashboard report.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Build Dashboard plugin, include the following in your build script:</para>
+        <sample id="useBuildDashboardPlugin" dir="buildDashboard" title="Using the Build Dashboard plugin">
+            <sourcefile file="build.gradle" snippet="use-build-dashboard-plugin"/>
+        </sample>
+        <para>You can then generate the report by running the <userinput>buildDashboard</userinput> task after running any tasks that
+            generate reports, for example: <userinput>gradle check buildDashboard</userinput>.</para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Build Dashboard plugin adds the following task to the project:</para>
+        <table>
+            <title>Build Dashboard plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>buildDashboard</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.reporting.GenerateBuildDashboard"/></td>
+                <td>Generates build dashboard report.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The Build Dashboard plugin does not require any particular project layout.</para>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The Build Dashboard plugin does not define any dependency configurations.</para>
+    </section>
+
+    <section>
+        <title>Configuration</title>
+        <para>You can influence the location of build dashboard plugin generation via <apilink class="org.gradle.api.reporting.ReportingExtension"/>.</para>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/buildEnvironment.xml b/subprojects/docs/src/docs/userguide/buildEnvironment.xml
index 3ca5172..8171748 100644
--- a/subprojects/docs/src/docs/userguide/buildEnvironment.xml
+++ b/subprojects/docs/src/docs/userguide/buildEnvironment.xml
@@ -59,6 +59,18 @@
                     At the moment the default settings are pretty generous with regards to memory.</para>
                 </listitem>
             </varlistentry>
+            <varlistentry>
+                <term><literal>org.gradle.configureondemand</literal></term>
+                <listitem><para>Enables new incubating mode that makes Gradle selective when configuring projects.
+                    Only relevant projects are configured which results in faster builds for large multi-projects.
+                    See <xref linkend="sec:configuration_on_demand"/>.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><literal>org.gradle.parallel</literal></term>
+                <listitem><para>When configured, Gradle will run in incubating parallel mode.</para>
+                </listitem>
+            </varlistentry>
         </para>
         <section>
             <title>Forked java processes</title>
diff --git a/subprojects/docs/src/docs/userguide/commandLine.xml b/subprojects/docs/src/docs/userguide/commandLine.xml
index 35f69e5..014e60d 100644
--- a/subprojects/docs/src/docs/userguide/commandLine.xml
+++ b/subprojects/docs/src/docs/userguide/commandLine.xml
@@ -61,6 +61,16 @@
             </listitem>
         </varlistentry>
         <varlistentry>
+            <term>
+                <option>--configure-on-demand (incubating)</option>
+            </term>
+            <listitem>
+                <para>Only relevant projects are configured in this build run. This means faster builds for large multi-projects.
+                    See <xref linkend="sec:configuration_on_demand"/>.
+                </para>
+            </listitem>
+        </varlistentry>
+        <varlistentry>
             <term><option>-D</option>, <option>--system-prop</option>
             </term>
             <listitem>
@@ -156,7 +166,7 @@
         </varlistentry>
         <varlistentry>
             <term>
-                <option>--parallel</option>
+                <option>--parallel (incubating)</option>
             </term>
             <listitem>
                 <para>
@@ -167,7 +177,7 @@
         </varlistentry>
         <varlistentry>
             <term>
-                <option>--parallel-threads</option>
+                <option>--parallel-threads (incubating)</option>
             </term>
             <listitem>
                 <para>
diff --git a/subprojects/docs/src/docs/userguide/comparingBuilds.xml b/subprojects/docs/src/docs/userguide/comparingBuilds.xml
index 3326eca..8d99896 100644
--- a/subprojects/docs/src/docs/userguide/comparingBuilds.xml
+++ b/subprojects/docs/src/docs/userguide/comparingBuilds.xml
@@ -148,7 +148,7 @@
                 This includes <literal>jar</literal>, <literal>war</literal> and <literal>ear</literal> archives.
             </para>
             <para>
-                Future versions will provide support for comparing outcomes such as test execution (i.e. which tests were executed, which tests failed etc.)
+                Future versions will provide support for comparing outcomes such as test execution (i.e. which tests were executed, which tests failed, etc.)
             </para>
         </section>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/depMngmt.xml b/subprojects/docs/src/docs/userguide/depMngmt.xml
index 44a35c6..df42f66 100644
--- a/subprojects/docs/src/docs/userguide/depMngmt.xml
+++ b/subprojects/docs/src/docs/userguide/depMngmt.xml
@@ -17,167 +17,199 @@
     <title>Dependency Management</title>
     <section id='sec:Introduction'>
         <title>Introduction</title>
-        <para>Gradle offers a very good support for dependency management. If you are familiar with Maven or Ivy approach you will be delighted to learn that:
+        <para>Dependency management is a critical feature of every build, and Gradle has placed an emphasis on offering first-class dependency management that is both easy-to-understand and
+            compatible with a wide variety of approaches. If you are familiar with the approach used by either Maven or Ivy you will be delighted to learn that Gradle is fully compatible with both
+            approaches in addition to being flexible enough to support fully-customised approaches.
+        </para>
+
+        <para>Here are the major highlights of Gradle's support for dependency management:</para>
         <itemizedlist>
             <listitem>
-                <para>Gradle fully supports transitive dependency management. Gradle also works <emphasis>perfectly</emphasis> with your existent dependency management
-                    infrastructure, be it Maven or Ivy. All the repositories you have set up with your custom POM or
-                    ivy files can be used as they are. No changes necessary.
+                <para><emphasis>Transitive dependency management</emphasis>: Gradle gives you full control of your project's dependency tree.
                 </para>
             </listitem>
             <listitem>
-                <para>If you don't use transitive dependency management and your external libraries live just as files in version control or on some shared drive, Gradle provides powerful functionality
+                <para><emphasis>Support for non-managed dependencies</emphasis>: If your dependencies are simply files in version control or a shared drive, Gradle provides powerful functionality
                     to support this.
                 </para>
             </listitem>
             <listitem>
-                <para>Gradle provides an additional, optional support for transitive dependency management that is not based on XML descriptor files called Module Dependencies, where you describe
-                    the dependency hierarchy in the build script.
+                <para><emphasis>Support for custom dependency definitions.</emphasis>: Gradle's Module Dependencies give you the ability to describe the dependency hierarchy in the build script.
+                </para>
+            </listitem>
+            <listitem>
+                <para><emphasis>A fully customisable approach to Dependency Resolution</emphasis>: Gradle provides you with the ability to customize resolution rules making dependency substitution
+                    easy.
                 </para>
             </listitem>
             <listitem>
-                <para>The job of a build system is to support all major patterns for how people deal with dependencies, not to force people in a certain way of doing things. In particular for migration scenarios
-                    it is extremely important that any current approach is supported so that you can use the same input structure in the new evolving Gradle build than in the existing build as long as it
-                    is in production. That enables you to compare the results. Gradle is extremely flexible. So even if your project is using a custom dependency management or say an Eclipse .classpath file as master data for dependency management,
-                    it would be very easy to write a little adaptor plugin to use this data in Gradle. For migration purposes this is a common technique with Gradle. Once you have migrated, it might be a good idea though not to
-                    use a .classpath file for dependency metadata any longer :).
+                <para><emphasis>Full Compatibility with Maven and Ivy</emphasis>: If you have defined dependencies in a Maven POM or an Ivy file, Gradle provide seamless integration with a range of
+                    popular build tools.
+                </para>
+            </listitem>
+            <listitem>
+                <para><emphasis>Integration with existing dependency management infrastructure</emphasis>: Gradle is compatible with both Maven and Ivy repositories. If you use Archiva, Nexus, or
+                    Artifactory, Gradle is 100% compatible with all repository formats.
                 </para>
             </listitem>
         </itemizedlist>
+
+        <para>
+            With hundreds of thousands of interdependent open source components each with a range of versions and incompatibilities, dependency management has a habit of causing problems as builds
+            grow in complexity. When a build's dependency tree becomes unwieldy, your build tool shouldn't force you to adopt a single, inflexible approach to dependency management. A proper build
+            system has to be designed to be flexible, and Gradle can handle any situation.
         </para>
+
+        <section id='sub:dependency_management_and_migrations'>
+            <title>Flexible dependency management for migrations</title>
+            <para>
+                Dependency management can be particularly challenging during a migration from one build system to another. If you are migrating from a tool like Ant or Maven to Gradle, you may be
+                faced with some difficult situations. For example, one common pattern is an Ant project with version-less jar files stored in the filesystem. Other build systems require a wholesale
+                replacement of this approach before migrating. With Gradle, you can adapt your new build to any existing source of dependencies or dependency metadata. This makes incremental migration
+                to Gradle much easier than the alternative. On most large projects, build migrations and any change to development process is incremental because most organizations can't afford to
+                stop everything and migrate to a build tool's idea of dependency management.
+            </para>
+
+            <para>Even if your project is using a custom dependency management system or something like an Eclipse .classpath file as master data for dependency management, it is very easy to write a
+                Gradle plugin to use this data in Gradle. For migration purposes this is a common technique with Gradle. (But, once you've migrated, it might be a good idea to move away from a
+                .classpath file and use Gradle's dependency management features directly.)
+            </para>
+        </section>
+
+        <section id='sub:dependency_management_and_java'>
+            <title>Dependency management and Java</title>
+            <para>It is ironic that in a language known for its rich library of open source components that Java has no concept of libraries or versions. In Java, there is no standard way to tell the
+                JVM that you are using version 3.0.5 of Hibernate, and there is no standard way to say that
+                <literal>foo-1.0.jar</literal>
+                depends on <literal>bar-2.0.jar</literal>. This has led to external solutions often based on build tools. The most popular ones at the moment are Maven and Ivy. While Maven provides a
+                complete build system, Ivy focuses solely on dependency management.
+            </para>
+            <para>Both tools rely on descriptor XML files, which contain information about the dependencies of a particular jar. Both also use repositories where the actual jars are placed together
+                with their descriptor files, and both offer resolution for conflicting jar versions in one form or the other. Both have emerged as standards for solving dependency conflicts, and while
+                Gradle originally used Ivy under the hood for its dependency management. Gradle has replaced this direct dependency on Ivy with a native Gradle dependency resolution engine which
+                supports a range of approached to dependency resolution including both POM and Ivy descriptor files.
+            </para>
+        </section>
     </section>
+
     <section id='sec:dependency_management_overview'>
-        <title>Dependency Management Best Practices.</title>
-        <para>We have an opinion on what are dependency management best practices. As usual, Gradle does not force our opinion onto you, but supports any kind of pattern you want to use. Nonetheless
-            we would like to share our opinion.
+        <title>Dependency Management Best Practices</title>
+        <para>While Gradle has strong opinions on dependency management, the tool gives you a choice between two
+            options: follow recommended best practices or support any kind of pattern you can think of. This section
+            outlines the Gradle project's recommended best practices for managing dependencies.
         </para>
-        <para>We think good dependency management is very important for almost any project. Yet the kind of dependency
-            management you need depends on the complexity and the environment of your project. Is your project a
-            distribution or a library? Is it part of an enterprise environment, where it is integrated into other
-            projects builds or not? But all types of projects should follow the rules below:
+        <para>No matter what the language, proper dependency management is important for every project.
+            From a complex enterprise application written in Java depending on hundreds of open source
+            libraries to the simplest Clojure application depending on a handful of libraries, approaches to dependency
+            management vary widely and can depend on the target technology, the method of application deployment, and the
+            nature of the project. Projects bundled as reusable libraries may have different requirements than
+            enterprise applications integrated into much larger systems of software and infrastructure. Despite this wide variation of requirements,
+            the Gradle project recommends that all projects follow this set of core rules:
         </para>
         <section id='sub:versioning_the_jar_name'>
-            <title>Versioning the jar name</title>
-            <para>The version of the jar must be easy to recognize. Sometimes the version is in the Manifest file of
-                the jar, often not. And even if, it is rather painful to always look into the Manifest file to learn
-                about the version. Therefore we think that you should only use jars which have their version as part
-                of their file name. If you are using transitive dependency management you are forced to do this in any case.
-            </para>
-            <para>Why do we think this is important? Without a dependency management as described above, your are likely
-                to burn your fingers sooner or later. If it is unclear which version of a jar your are using, this can
-                introduce subtle bugs which are very hard to find. For example there might be a project which uses
-                Hibernate 3.0.4. There are some problems with Hibernate so a developer installs version 3.0.5 of
-                Hibernate on her machine. This did not solve the problem but she forgot to roll back Hibernate to 3.0.4.
-                Weeks later there is an exception on the integration machine which can't be reproduced on the developer
-                machine. Without a version in the jar name this problem might take a long time to debug. Version in the
-                jar names increases the expressiveness of your project and makes it easier to maintain.
+            <title>Put the Version in the Filename (Version the jar)</title>
+            <para>The version of a library must be easy to recognize in the filename. While the version of a jar is usually in the Manifest file, it isn't readily apparent when you are inspecting a
+                project. If someone asks you to look at a collection of 20 jar files, which would you prefer? A collection of files with names like <filename>commons-beanutils-1.3.jar</filename>
+                or a collection of files with names like <filename>spring.jar</filename>? If dependencies have file names with version numbers it is much easier to quickly identify the versions of
+                your dependencies.
+            </para>
+            <para>If versions are unclear you can introduce subtle bugs which are very hard to find. For example there might be a project which uses Hibernate 2.5. Think about a developer who decides
+                to install version 3.0.5 of Hibernate on her machine to fix a critical security bug but forgets to notify others in the team of this change. She may address the security bug
+                successfully, but she also may have introduced subtle bugs into a codebase that was using a now-deprecated feature from Hibernate. Weeks later there is an exception on the integration
+                machine which can't be reproduced on anyone's machine. Multiple developers then spend days on this issue only finally realising that the error would have easy to uncover if they knew
+                that Hibernate had been upgraded from 2.5 to 3.0.5.
+            </para>
+            <para>Versions in jar names increase the expressiveness of your project and make them easier to maintain. This practice also reduces the potential for error.
             </para>
         </section>
         <section id='sub:transitive_dependency_management'>
-            <title>Use some form of transitive dependency management</title>
-            <para>When we talk about transitive dependency management, we mean any technique that enables to distinguish
-                between what are the first level dependencies and what are the transitive ones. We will about different techniques for this later on.
-            </para>
-            <para>Why is transitive dependency management so important? If you don't know which dependencies are first
-                level dependencies and which ones are transitive you will soon lose control over your build. Even a non enterprise project
-                Gradle has already 100+ jars. An enterprise project using Spring, Hibernate, etc. easily ends up with
-                many more jars. There is no way to memorize where all these jars come from. If you want to get rid of a first
-                level dependency you can't be sure which other jars you should remove. Because a dependency of a
-                first level dependency might also be a first level dependency itself. Or it might be a transitive
-                dependency of another of your first level dependencies. Many first level dependencies are runtime
-                dependencies and the transitive dependencies are of course all runtime dependencies. So the compiler
-                won't help you much here. The end of the story is, as we have seen very often, no one dares to remove
-                any jar any longer. The project classpath is a complete mess and if a classpath problem arises, hell on
-                earth invites you for a ride. In one of our former projects, we found some ldap related jar in the
-                classpath, whose sheer presence, as we found out after much research, accelerated LDAP access. So
-                removing this jar would not have led to any errors at compile or runtime.
-            </para>
-            <para>Gradle offers you different ways to express what are first level and what are transitive dependencies.
-                Gradle allows you for example to store your jars in CVS or SVN without XML descriptor files and still
-                use transitive dependency management. Also, not all techniques for transitive dependency management deal with
-                the problem described above equally well.
+            <title>Manage transitive dependencies</title>
+            <para>Transitive dependency management is a technique that enables your project to depend on libraries which, in turn, depend on other libraries. This recursive pattern of transitive
+                dependencies results in a tree of dependencies including your project's first-level dependencies, second-level dependencies, and so on. If you don't model your dependencies as a
+                hierarchical tree of first-level and second-level dependencies it is very easy to quickly lose control over an assembled mess of unstructured dependencies. Consider the Gradle project
+                itself, while Gradle only has a few direct, first-level dependencies, when Gradle is compiled it needs more that one hundred dependencies on the classpath. On a far larger scale,
+                Enterprise projects using Spring, Hibernate, and other libraries, alongside hundreds or thousands of internal projects can have very large dependency trees.
+            </para>
+            <para>When these large dependency trees need to change, you'll often have to solve some dependency version conflicts. Say one open source library needs one version of a logging library and
+                a another uses an alternative version. Gradle and other build tools all have the ability to solve this dependency tree and resolve conflicts, but what differentiates Gradle is the
+                control it gives you over transitive dependencies and conflict resolution.
+            </para>
+            <para>While you could try to manage this problem manually, you will quickly find that this approach doesn't scale. If you want to get rid of a first level dependency you really can't be
+                sure which other jars you should remove. A dependency of a first level dependency might also be a first level dependency itself, or it might be a transitive dependency of yet another
+                first level dependency. If you try to manage transitive dependencies yourself, the end of the story is that your build becomes brittle: no one dares to change your dependencies because
+                the risk of breaking the build is too high. The project classpath becomes a complete mess, and, if a classpath problem arises, hell on earth invites you for a ride.
+            </para>
+            <note><emphasis>NOTE:</emphasis>In one project, we found a mystery, LDAP related jar in the classpath. No code referenced this jar and there was no connection to the project. No one could
+                figure out what the jar was for, until it was removed from the build and the application suffered massive performance problem whenever it attempted to authenticate to LDAP. This
+                mystery jar was a necessary transitive, fourth-level dependency that was easy to miss because no one had bothered to use managed transitive dependencies.
+            </note>
+            <para>Gradle offers you different ways to express first-level and transitive dependencies. With Gradle you can mix and match approaches; for example, you could store your jars in an SCM
+                without XML descriptor files and still use transitive dependency management.
             </para>
         </section>
         <section id='sub:version_conflicts'>
-            <title>Version conflicts</title>
-            <para>Conflicting versions of the same jar should be detected and either resolved or cause an exception. If you don't use
-                transitive dependency management, version conflicts are undetected and the mostly accidental fragile order of the classpath
-                will determine, what version of a dependency will win. For example adding a dependency with a particular version to a subproject
-                might change that order and then will led to all kind of surprising side effects. You might also want to learn where conflicting
-                versions are used as you might want to consolidate on a particular version of an dependency across your organization. With a good conflict
-                reporting that information can be used to communicate with the teams to solve this.
-            </para>
-            <para>It is common that different dependencies rely on different versions of
-                another dependency which leads to a version conflictm as The JVM unfortunately does not offer yet any easy way, to have different versions of
-                the same jar in the classpath (see <xref linkend='sub:dependency_management_and_java'/>).</para>
-            <para>Gradle offers following conflict resolution strategies:
-                <itemizedlist>
-                    <listitem><emphasis>Newest</emphasis> - used by default by Gradle - the newest version of the dependency is used.
-                    This strategy has been in Gradle since early days.
-                    </listitem>
-                    <listitem><emphasis>Fail</emphasis> - fail eagerly on version conflict.
-                        Useful if you need extra control and manage the conflicts manually.
-                        Introduced in <code>1.0-milestone-6</code>. See <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/> for reference on managing the conflict resolution strategies.
-                    </listitem>
-                    <listitem>We are working on making conflict resolution fully customizable.
-                    </listitem>
-                </itemizedlist>
-                Gradle provides means to resolve version conflicts:
-                <itemizedlist>
-                    <listitem>
-                        Configuring a first level dependency as <emphasis>forced</emphasis>.
-                        The feature has been in Gradle since early days.
-                        This approach is useful if the dependency incurring conflict is already a first level dependency.
-                        See examples in <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>
-                    </listitem>
-                    <listitem>
-                        Configuring any dependency (transitive or not) as <emphasis>forced</emphasis>.
-                        The feature was introduced in <code>1.0-milestone-7</code>.
-                        This approach is useful if the dependency incurring conflict is a transitive dependency.
-                        It also can be used to force versions of first level dependencies.
-                        See examples in <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>
-                    </listitem>
-                </itemizedlist>
-                To deal with problems due to version conflicts, reports with dependency graphs are also very helpful.
-                Such reports are another feature of dependency management.
+            <title>Resolve version conflicts</title>
+            <para>Conflicting versions of the same jar should be detected and either resolved or cause an exception. If you don't use transitive dependency management, version conflicts are undetected
+                and the often accidental order of the classpath will determine what version of a dependency will win. On a large project with many developers changing dependencies, successful builds
+                will be few and far between as the order of dependencies may directly affect whether a build succeeds or fails (or whether a bug appears or disappears in production).
             </para>
+            <para>If you haven't had to deal with the curse of conflicting versions of jars on a classpath, here's a small example of the fun that awaits you. Consider a large project with 30
+                submodules, adding a dependency with a particular version to a subproject changes the order of a classpath, swapping an old version of Spring 2.4 for a newer version Spring 2.5. While
+                the build may continue to work, developers are starting to notice all sorts of surprising (and surprisingly awful) bugs in production. Worse yet, this unintentional downgrade of Spring
+                introduced several security vulnerabilities into the system which now require a full security audit throughout the organization.
+            </para>
+            <para>In short, version conflicts are bad, manage your transitive dependencies to avoid them. You might also want to learn where conflicting versions are used and consolidate on a
+                particular version of a dependency across your organization. With a good conflict reporting tool like Gradle that information can be used to communicate with the entire organization
+                and standardise on a single version.
+                <emphasis>If you think version conflicts don't happen to you, think again.</emphasis>
+                It is very common for different first-level dependencies to rely on a range of different overlapping versions for other dependencies, and the JVM doesn't yet offer an easy way to have
+                different versions of the same jar in the classpath (see <xref linkend='sub:dependency_management_and_java'/>).
+            </para>
+            <para>Gradle offers following conflict resolution strategies:</para>
+            <itemizedlist>
+                <listitem>
+                    <emphasis>Newest</emphasis> - used by default by Gradle - the newest version of the dependency is used. This has been Gradle's approach since the beginning of the project, and
+                    while it isn't appropriate in every situation, this is why Gradle provides you with various options for resolving conflicts.
+                </listitem>
+                <listitem>
+                    <emphasis>Fail</emphasis> - fail eagerly on version conflict. Useful if you need extra control over dependencies and if you need to manage version conflicts manually. See
+                    <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/> for reference on managing the conflict resolution strategies.
+                </listitem>
+            </itemizedlist>
+            <para>While the strategies introduced above are usually enough to solve most conflicts, Gradle provides more fine-grained mechanisms to resolve version conflicts:</para>
+            <itemizedlist>
+                <listitem>
+                    Configuring a first level dependency as <emphasis>forced</emphasis>. This approach is useful if the dependency in conflict is already a first level dependency.
+                    See examples in <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>.
+                </listitem>
+                <listitem>
+                    Configuring any dependency (transitive or not) as <emphasis>forced</emphasis>. This approach is useful if the dependency in conflict is a transitive dependency.
+                    It also can be used to force versions of first level dependencies.
+                    See examples in <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>
+                </listitem>
+                <listitem>
+                    Dependency resolve rules are an incubating feature introduced in Gradle 1.4 which give you fine-grained control over the version selected for a particular dependency.
+                </listitem>
+            </itemizedlist>
+            <para>To deal with problems due to version conflicts, reports with dependency graphs are also very helpful. Such reports are another feature of dependency management.</para>
         </section>
         <section id='sub:dynamic_versions_and_changing_modules'>
-            <title>Dynamic Versions and Changing Modules</title>
-            <para>Sometimes, you always want to use the latest version of a particular dependency, or the latest in a range of versions.
-                You can easily do this using a <emphasis>dynamic version</emphasis>. A dynamic version can be either a version range (eg. <literal>2.+</literal>)
-                or it can be a placeholder for the latest version available (eg. <literal>latest.integration</literal>).
+            <title>Use Dynamic Versions and Changing Modules</title>
+            <para>There are many situation when you want to use the latest version of a particular dependency, or the latest in a range of versions. This can be a requirement during development, or
+                you may be developing a library that is designed to work with a range of dependency versions. You can easily depend on these constantly changing dependencies by using a
+                <emphasis>dynamic version</emphasis>. A dynamic version can be either a version range (e.g. <literal>2.+</literal>) or it can be a placeholder for the latest version available
+                (e.g. <literal>latest.integration</literal>).
             </para>
-            <para>Alternatively, sometimes the module you request can change over time, even for the same version.
-                An example of this type of <emphasis>changing module</emphasis> is a maven <literal>SNAPSHOT</literal> module,
-                which always points at the latest artifacts published.
+            <para>Alternatively, sometimes the module you request can change over time, even for the same version. An example of this type of <emphasis>changing module</emphasis>
+                is a Maven <literal>SNAPSHOT</literal> module, which always points at the latest artifact published. In other words, a standard Maven snapshot is a module that never stands still
+                so to speak, it is a "changing module".
             </para>
-            <para>
-                The main difference between a <emphasis>dynamic version</emphasis> and a <emphasis>changing module</emphasis> is
-                that when you resolve a <emphasis>dynamic version</emphasis>, you'll get the real, static version as the module name.
-                When you resolve a <emphasis>changing module</emphasis>, the artifacts are named using the version you requested,
-                but the underlying artifacts may change over time.
+            <para>The main difference between a <emphasis>dynamic version</emphasis> and a <emphasis>changing module</emphasis> is that when you resolve a <emphasis>dynamic version</emphasis>, you'll
+                get the real, static version as the module name. When you resolve a <emphasis>changing module</emphasis>, the artifacts are named using the version you requested, but the underlying
+                artifacts may change over time.
             </para>
-            <para>By default, Gradle caches dynamic versions and changing modules for 24 hours.
-                You can override the default cache modes using <link linkend="sec:cache_command_line_options">command line options</link>.
-                You can change the cache expiry times in your build using the <literal>resolution strategy</literal> (see <xref linkend='sec:controlling_caching'/>).
-            </para>
-        </section>
-        <section id='sub:dependency_management_and_java'>
-            <title>Dependency management and Java</title>
-            <para>Traditionally, Java has offered no support at all for dealing with libraries and versions. There are
-                no standard ways to say that
-                <literal>foo-1.0.jar</literal>
-                depends on a <literal>bar-2.0.jar</literal>. This has led to proprietary solutions. The most popular ones
-                are Maven and Ivy. Maven is a complete build system whereas Ivy focuses solely on dependency management.
-            </para>
-            <para>Both approaches rely on descriptor XML files, which contains information about the dependencies of a
-                particular jar. Both also use repositories where the actual jars are placed together with their
-                descriptor files. And both offer resolution for conflicting jar versions in one form or the other. Yet
-                we think the differences of both approaches are significant
-                in terms of flexibility and maintainability. Originally Gradle did use Ivy under the
-                hood for its dependency management. This has been replaced with a native Gradle dependency resolution engine. This resolution engine
-                supports both pom and ivy descriptor files.
+            <para>By default, Gradle caches dynamic versions and changing modules for 24 hours. You can override the default cache modes using <link linkend="sec:cache_command_line_options">command
+                line options</link>. You can change the cache expiry times in your build using the <literal>resolution strategy</literal> (see <xref linkend='sec:controlling_caching'/>).
             </para>
         </section>
     </section>
@@ -186,7 +218,8 @@
         <para>In Gradle dependencies are grouped into configurations. Configurations have a name, a number of other properties,
             and they can extend each other.
             Many Gradle plugin add pre-defined configurations to your project. The Java plugin, for example,
-            adds some configurations to represent the various classpaths it needs. see <xref linkend='sec:java_plugin_and_dependency_management'/>
+            adds some configurations to represent the various classpaths it needs. see
+            <xref linkend='sec:java_plugin_and_dependency_management'/>
             for details. Of course you can add your add custom configurations on top of that. There are many use cases
             for custom configurations. This is very handy for example for adding dependencies not needed for
             building or testing your software (e.g. additional JDBC drivers to be shipped with your distribution).
@@ -223,31 +256,43 @@
                 </tr>
             </thead>
             <tr>
-                <td><link linkend="sub:module_dependencies">External module dependency</link></td>
+                <td>
+                    <link linkend="sub:module_dependencies">External module dependency</link>
+                </td>
                 <td>A dependency on an external module in some repository.</td>
             </tr>
             <tr>
-                <td><link linkend="sub:project_dependencies">Project dependency</link></td>
+                <td>
+                    <link linkend="sub:project_dependencies">Project dependency</link>
+                </td>
                 <td>A dependency on another project in the same build.</td>
             </tr>
             <tr>
-                <td><link linkend="sub:file_dependencies">File dependency</link></td>
+                <td>
+                    <link linkend="sub:file_dependencies">File dependency</link>
+                </td>
                 <td>A dependency on a set of files on the local filesystem.</td>
             </tr>
             <tr>
-                <td><link linkend="sub:client_module_dependencies">Client module dependency</link></td>
+                <td>
+                    <link linkend="sub:client_module_dependencies">Client module dependency</link>
+                </td>
                 <td>A dependency on an external module, where the artifacts are located in some repository but the module meta-data
                     is specified by the local build. You use this kind of dependency when you want to override the meta-data for the module.
                 </td>
             </tr>
             <tr>
-                <td><link linkend="sub:api_dependencies">Gradle API dependency</link></td>
+                <td>
+                    <link linkend="sub:api_dependencies">Gradle API dependency</link>
+                </td>
                 <td>A dependency on the API of the current Gradle version.
                     You use this kind of dependency when you are developing custom Gradle plugins and task types.
                 </td>
             </tr>
             <tr>
-                <td><link linkend="sub:groovy_dependencies">Local Groovy dependency</link></td>
+                <td>
+                    <link linkend="sub:groovy_dependencies">Local Groovy dependency</link>
+                </td>
                 <td>A dependency on the Groovy version used by the current Gradle version.
                     You use this kind of dependency when you are developing custom Gradle plugins and task types.
                 </td>
@@ -261,32 +306,34 @@
             <sample id="moduleDependencies" dir="userguide/artifacts/externalDependencies" title="Module dependencies">
                 <sourcefile file="build.gradle" snippet="module-dependencies"/>
             </sample>
-            <para>Please see the <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>
+            <para>Please see the
+                <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>
                 for more examples and complete reference. Please read on to get thorough understanding of the Gradle's dependency management.
             </para>
             <para>Gradle provides different notations for module dependencies. There is a string notation and
                 a map notation. A module dependency has an API which allows for further configuration. Have a look at
-                <apilink class='org.gradle.api.artifacts.ExternalModuleDependency'/> to learn all about the API.
+                <apilink class='org.gradle.api.artifacts.ExternalModuleDependency'/>
+                to learn all about the API.
                 This API provides properties and configuration methods. Via the string notation you can define a subset
                 the properties. With the map notation you can define all properties. To have access to the complete API,
                 either with the map or with the string notation, you can assign a single dependency to a configuration
                 together with a closure.
             </para>
-            <para>If you declare a module dependency, Gradle looks for a corresponding module descriptor file (<literal>pom.xml</literal> or
-                <literal>ivy.xml</literal>) in the repositories. If such a module descriptor file exists, it is parsed and the artifacts of
-                this module (e.g. <literal>hibernate-3.0.5.jar</literal>) as well as its dependencies (e.g. cglib) are downloaded. If no such
-                module descriptor file exists, Gradle looks for a file called <literal>hibernate-3.0.5.jar</literal> to retrieve. In Maven
-                a module can only have one and only one artifact. In Gradle and Ivy a module can have multiple artifacts.
+            <para>If you declare a module dependency, Gradle looks for a corresponding module descriptor file (<filename>pom.xml</filename> or
+                <filename>ivy.xml</filename>) in the repositories. If such a module descriptor file exists, it is parsed and the artifacts of
+                this module (e.g.<filename>hibernate-3.0.5.jar</filename>) as well as its dependencies (e.g. cglib) are downloaded. If no such
+                module descriptor file exists, Gradle looks for a file called <filename>hibernate-3.0.5.jar</filename>
+                to retrieve. In Maven a module can only have one and only one artifact. In Gradle and Ivy a module can have multiple artifacts.
                 Each artifact can have a different set of dependencies.
             </para>
             <section id='ssub:multi_artifact_dependencies'>
                 <title>Depending on modules with multiple artifacts</title>
-                As mentioned earlier, a maven module has only one artifact. So, when your project depends on a maven module
-                it's obvious what artifact is the actual dependency.
-                With Gradle or Ivy the case is different. Ivy model of dependencies (<literal>ivy.xml</literal>) can declare multiple artifacts.
-                For more information, see Ivy reference for <literal>ivy.xml</literal>.
-                In Gradle, when you declare a dependency on an ivy module you actually declare dependency on the <literal>'default'</literal> configuration of that module.
-                So the actual list of artifacts (typically jars) your project depends on, are all artifacts that are attached to the <literal>default</literal> configuration of that module.
+                As mentioned earlier, a Maven module has only one artifact. So, when your project depends on a Maven module it's obvious what artifact is the actual dependency.
+                With Gradle or Ivy the case is different. Ivy model of dependencies (<filename>ivy.xml</filename>) can declare multiple artifacts.
+                For more information, see Ivy reference for<filename>ivy.xml</filename>.
+                In Gradle, when you declare a dependency on an ivy module you actually declare dependency on the '<literal>default</literal>' configuration of that module.
+                So the actual list of artifacts (typically jars) your project depends on, are all artifacts that are attached to the
+                <literal>default</literal> configuration of that module.
                 This is very important in following exemplary use cases:
                 <itemizedlist>
                     <listitem>The <literal>default</literal> configuration of some module contains some artifacts
@@ -304,22 +351,20 @@
             <section id='ssub:artifact_dependencies'>
                 <title>Artifact only notation</title>
                 <para>As said above, if no module descriptor file can be found, Gradle by default
-                    downloads a jar with the name of the module. But sometimes, even if the repository contains module descriptors,  you want to download only the artifact jar, without
+                    downloads a jar with the name of the module. But sometimes, even if the repository contains module descriptors, you want to download only the artifact jar, without
                     the dependencies.
                     <footnote>
-                        <para>Gradle supports partial multiproject builds (see <xref linkend='multi_project_builds'/>).
-                        </para>
+                        <para>Gradle supports partial multiproject builds (see <xref linkend='multi_project_builds'/>).</para>
                     </footnote>
-                    And sometimes you want to download a zip from a repository, that does not have module descriptors.
-                    Gradle provides an <emphasis>artifact only</emphasis> notation for those use cases - simply prefix the extension that you want to be downloaded with <literal>'@'</literal> sign:
+                    And sometimes you want to download a zip from a repository, that does not have module descriptors. Gradle provides an <emphasis>artifact only</emphasis>
+                    notation for those use cases - simply prefix the extension that you want to be downloaded with <literal>'@'</literal> sign:
                     <sample id="artifactOnly" dir="userguide/artifacts/externalDependencies" title="Artifact only notation">
                         <sourcefile file="build.gradle" snippet="artifact-only"/>
                     </sample>
-                    An artifact only notation creates a module dependency which downloads only the artifact file with
-                    the specified extension. Existing module descriptors are ignored.
+                    An artifact only notation creates a module dependency which downloads only the artifact file with the specified extension. Existing module descriptors are ignored.
                 </para>
             </section>
-            <section id='ssub:classifiers'>
+            <section id='sub:classifiers'>
                 <title>Classifiers</title>
                 <para>The Maven dependency management has the notion of classifiers.
                     <footnote>
@@ -327,16 +372,16 @@
                             <ulink url='http://www.sonatype.com/books/maven-book/reference/pom-relationships-sect-project-relationships.html'/>
                         </para>
                     </footnote>
-                    Gradle supports this. To retrieve classified dependencies from a maven repository you can write:
+                    Gradle supports this. To retrieve classified dependencies from a Maven repository you can write:
                 </para>
                 <sample id="classifier" dir="userguide/artifacts/excludesAndClassifiers" title="Dependency with classifier">
-                        <sourcefile file="build.gradle" snippet="classifier"/>
+                    <sourcefile file="build.gradle" snippet="classifier"/>
                 </sample>
-                <para>As you can see in the example, classifiers can be used together with setting
-                an explicit extension (artifact only notation).</para>
+                <para>As you can see in the example, classifiers can be used together with setting an explicit extension (artifact only notation).
+                </para>
             </section>
             <para>To use the external dependencies of a configuration:</para>
-            <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies"  title="Usage of external dependency of a configuration">
+            <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies" title="Usage of external dependency of a configuration">
                 <sourcefile file="build.gradle" snippet="use-configuration"/>
                 <output args="-q listJars"/>
             </sample>
@@ -344,9 +389,8 @@
 
         <section id='sub:client_module_dependencies'>
             <title>Client module dependencies</title>
-            <para>Client module dependencies enable you to declare <emphasis>transitive</emphasis>
-                dependencies directly in your build script. They are a replacement for a module descriptor XML file in
-                an external repository.
+            <para>Client module dependencies enable you to declare <emphasis>transitive</emphasis> dependencies directly in your build script. They are a replacement for a module descriptor
+                XML file in an external repository.
             </para>
             <sample id="client-modules" dir="userguide/artifacts/externalDependencies" title="Client module dependencies - transitive dependencies">
                 <sourcefile file="build.gradle" snippet="client-modules"/>
@@ -354,7 +398,8 @@
             <para>This declares a dependency of your project on Groovy. Groovy itself has dependencies. But Gradle does
                 not look for an XML descriptor to figure them out but gets the information from the build file. The
                 dependencies of a client module can be normal module dependencies or artifact dependencies or another
-                client module. Have also a look at the API documentation: <apilink class='org.gradle.api.artifacts.ClientModule'/>
+                client module. Have also a look at the API documentation:
+                <apilink class='org.gradle.api.artifacts.ClientModule'/>
             </para>
             <para>In the current release client modules have one limitation. Let's say your project is a library and
                 you want this library to be uploaded to your company's Maven or Ivy repository. Gradle uploads the
@@ -372,9 +417,10 @@
             <sample id="project-dependencies" dir="java/multiproject/api" title="Project dependencies">
                 <sourcefile file="build.gradle" snippet="project-dependencies"/>
             </sample>
-            <para>For more information see the API documentation for <apilink class="org.gradle.api.artifacts.ProjectDependency"/>
+            <para>For more information see the API documentation for
+                <apilink class="org.gradle.api.artifacts.ProjectDependency"/>
             </para>
-            <para>Multi-project builds are discussed in <xref linkend='multi_project_builds'/>.
+            <para>Multi-project builds are discussed in<xref linkend='multi_project_builds'/>.
             </para>
         </section>
 
@@ -385,7 +431,9 @@
                 repository. Or if you do not want to use any repositories at all for storing your dependencies.
             </para>
             <para>To add some files as a dependency for a configuration, you simply pass a
-                <link linkend="sec:file_collections">file collection</link> as a dependency:</para>
+                <link linkend="sec:file_collections">file collection</link>
+                as a dependency:
+            </para>
             <sample id="file-dependencies" dir="userguide/artifacts/externalDependencies" title="File dependencies">
                 <sourcefile file="build.gradle" snippet="file-dependencies"/>
             </sample>
@@ -405,9 +453,9 @@
 
         <section id="sub:api_dependencies">
             <title>Gradle API Dependency</title>
-            <para>You can declare a dependency on the API of the current version of Gradle by using the
-                <apilink class="org.gradle.api.artifacts.dsl.DependencyHandler" method="gradleApi"/> method. This is
-                useful when you are developing custom Gradle tasks or plugins.</para>
+            <para>You can declare a dependency on the API of the current version of Gradle by using the <apilink class="org.gradle.api.artifacts.dsl.DependencyHandler" method="gradleApi"/>
+                method. This is useful when you are developing custom Gradle tasks or plugins.
+            </para>
             <sample id="gradle-api-dependencies" dir="customPlugin/plugin" title="Gradle API dependencies">
                 <sourcefile file="build.gradle" snippet="gradle-api-dependencies"/>
             </sample>
@@ -415,9 +463,9 @@
 
         <section id="sub:groovy_dependencies">
             <title>Local Groovy Dependency</title>
-            <para>You can declare a dependency on the Groovy that is distributed with Gradle by using the
-                <apilink class="org.gradle.api.artifacts.dsl.DependencyHandler" method="localGroovy"/> method. This is
-                useful when you are developing custom Gradle tasks or plugins in Groovy.</para>
+            <para>You can declare a dependency on the Groovy that is distributed with Gradle by using the <apilink class="org.gradle.api.artifacts.dsl.DependencyHandler" method="localGroovy"/>
+                method. This is useful when you are developing custom Gradle tasks or plugins in Groovy.
+            </para>
             <sample id="local-groovy-dependencies" dir="customPlugin/plugin" title="Gradle's Groovy dependencies">
                 <sourcefile file="build.gradle" snippet="local-groovy-dependencies"/>
             </sample>
@@ -430,15 +478,12 @@
             <sample id="exclude-dependencies" dir="userguide/artifacts/excludesAndClassifiers" title="Excluding transitive dependencies">
                 <sourcefile file="build.gradle" snippet="exclude-dependencies"/>
             </sample>
-            <para>If you define
-                an exclude for a particular configuration, the excluded transitive dependency will be filtered for all
+            <para>If you define an exclude for a particular configuration, the excluded transitive dependency will be filtered for all
                 dependencies when resolving this configuration or any inheriting configuration.
                 If you want to exclude a transitive dependency from all your
                 configurations you can use the Groovy spread-dot operator to express this in a concise way, as shown in the example.
-                When defining an exclude, you can
-                specify either only the organization or only the module name or both.
-                Have also a look at the API documentation of <apilink class="org.gradle.api.artifacts.Dependency"/> and
-                <apilink class="org.gradle.api.artifacts.Configuration"/>.
+                When defining an exclude, you can specify either only the organization or only the module name or both.
+                Have also a look at the API documentation of <apilink class="org.gradle.api.artifacts.Dependency"/> and <apilink class="org.gradle.api.artifacts.Configuration"/>.
             </para>
             <para>
                 Not every transitive dependency can be excluded - some transitive dependencies might be essential
@@ -455,10 +500,8 @@
                     <listitem>The dependency is undesired due to licensing reasons.</listitem>
                     <listitem>The dependency is not available in any of remote repositories.</listitem>
                     <listitem>The dependency is not needed for runtime.</listitem>
-                    <listitem>The dependency has a version that conflicts with a desired version.
-                        For that use case please refer to <xref linkend='sub:version_conflicts'/>
-                        and the documentation on <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>
-                        for a potentially better solution to the problem.
+                    <listitem>The dependency has a version that conflicts with a desired version. For that use case please refer to <xref linkend='sub:version_conflicts'/>
+                        and the documentation on <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/> for a potentially better solution to the problem.
                     </listitem>
                 </itemizedlist>
                 Basically, in most of the cases excluding the transitive dependency should be done per configuration.
@@ -468,8 +511,7 @@
                 that unwanted transitive dependency.
             </para>
             <para>
-                Other examples of the dependency exclusions can be found in the reference for
-                <apilink class='org.gradle.api.artifacts.ModuleDependency'/> or
+                Other examples of the dependency exclusions can be found in the reference for <apilink class='org.gradle.api.artifacts.ModuleDependency'/> or
                 <apilink class='org.gradle.api.artifacts.dsl.DependencyHandler'/>.
             </para>
         </section>
@@ -493,15 +535,15 @@
         <section id="sec:dependency_configurations">
             <title>Dependency configurations</title>
             <para>In Gradle a dependency can have different configurations (as your project can have different configurations). If you
-            don't specify anything explicitly, Gradle uses the default configuration of the dependency. For dependencies
-            from a Maven repository, the default configuration is the only available one anyway. If you work with Ivy repositories and
-            want to declare a non-default configuration for your dependency you have to use the map notation and declare:
+                don't specify anything explicitly, Gradle uses the default configuration of the dependency. For dependencies
+                from a Maven repository, the default configuration is the only available one anyway. If you work with Ivy repositories and
+                want to declare a non-default configuration for your dependency you have to use the map notation and declare:
             </para>
             <sample id="dependencyConfigurations" dir="userguide/artifacts/externalDependencies" title="Dependency configurations">
                 <sourcefile file="build.gradle" snippet="dependency-configurations"/>
             </sample>
             <para>To do the same for project dependencies you need to declare:</para>
-            <sample id="dependencyConfigurationsProjects" dir="/java/multiproject/services/webservice"  title="Dependency configurations for project">
+            <sample id="dependencyConfigurationsProjects" dir="/java/multiproject/services/webservice" title="Dependency configurations for project">
                 <sourcefile file="build.gradle" snippet="dependency-configurations"/>
             </sample>
         </section>
@@ -542,8 +584,7 @@
             <output args="-q dependencies"/>
         </sample>
         <para><code>dependencies</code> returns only the dependencies belonging explicitly to the configuration.
-            <code>allDependencies</code> includes the dependencies from extended
-            configurations.
+            <code>allDependencies</code> includes the dependencies from extended configurations.
         </para>
         <para>To get the library files of the configuration dependencies you can do:
         </para>
@@ -557,32 +598,29 @@
             <sourcefile file="build.gradle" snippet="files"/>
             <output args="-q files"/>
         </sample>
-        <para>The <code>Configuration.files</code> method always retrieves all artifacts of the <emphasis>whole</emphasis> configuration. It
-        then filters the retrieved files by specified dependencies. As you can see in the example, transitive dependencies are included.
+        <para>The <code>Configuration.files</code> method always retrieves all artifacts of the <emphasis>whole</emphasis>
+            configuration. It then filters the retrieved files by specified dependencies. As you can see in the example, transitive dependencies are included.
         </para>
         <para>You can also copy a configuration. You can optionally specify that only a subset of dependencies from the original configuration
-            should be copied. The copying methods come in two flavors. The <code>copy</code> method copies only the dependencies belonging
-            explicitly to the configuration. The <code>copyRecursive</code> method copies all the dependencies, including the dependencies from extended
-            configurations.
+            should be copied. The copying methods come in two flavors. The <code>copy</code> method copies only the dependencies belonging explicitly to the configuration. The
+            <code>copyRecursive</code> method copies all the dependencies, including the dependencies from extended configurations.
         </para>
         <sample id="configurationHandlingCopy" dir="userguide/artifacts/configurationHandling" title="Configuration.copy">
             <sourcefile file="build.gradle" snippet="copy"/>
-             <output args="-q copy"/>
+            <output args="-q copy"/>
         </sample>
         <para>It is important to note that the returned files of the copied configuration
             are often but not always the same than the returned files of the dependency subset of the original configuration.
-            In case of version conflicts between
-            dependencies of the subset and dependencies not belonging to the subset the resolve result might be different.</para>
+            In case of version conflicts between dependencies of the subset and dependencies not belonging to the subset the resolve result might be different.
+        </para>
         <sample id="configurationHandlingCopyVsFiles" dir="userguide/artifacts/configurationHandling" title="Configuration.copy vs. Configuration.files">
             <sourcefile file="build.gradle" snippet="copyVsFiles"/>
-             <output args="-q copyVsFiles"/>
+            <output args="-q copyVsFiles"/>
         </sample>
-        <para>In the example above, <code>orca</code> has a dependency on <code>seal-1.0</code> whereas
-            <code>shark</code> has a dependency on <code>seal-2.0</code>. The original configuration has therefore a version
-            conflict which is resolved to the newer <code>seal-2.0</code> version. The <code>files</code> method therefore
-            returns <code>seal-2.0</code> as a transitive dependency of <code>orca</code>. The copied configuration only has <code>orca</code>
-            as a dependency and therefore there is no version conflict and <code>seal-1.0</code> is returned as a transitive
-            dependency.
+        <para>In the example above, <code>orca</code> has a dependency on <code>seal-1.0</code> whereas <code>shark</code> has a dependency on<code>seal-2.0</code>. The original configuration
+            has therefore a version conflict which is resolved to the newer <code>seal-2.0</code> version. The <code>files</code> method therefore returns <code>seal-2.0</code> as a
+            transitive dependency of<code>orca</code>. The copied configuration only has <code>orca</code> as a dependency and therefore there is no version conflict and <code>seal-1.0</code>
+            is returned as a transitive dependency.
         </para>
         <para>Once a configuration is resolved it is immutable. Changing its state or the state of one of its dependencies
             will cause an exception. You can always copy a resolved configuration. The copied configuration is in the unresolved
@@ -598,9 +636,10 @@
             Additionally Gradle provides various convenience method to add pre-configured repositories.
         </para>
         <para>You may configure any number of repositories, each of which is treated independently by Gradle. If Gradle finds a module descriptor in a
-        particular repository, it will attempt to download all of the artifacts for that module from <emphasis>the same repository</emphasis>.
-        Although module meta-data and module artifacts must be located in the same repository, it is possible to compose a single repository of multiple
-        URLs, giving multiple locations to search for meta-data files and jar files.</para>
+            particular repository, it will attempt to download all of the artifacts for that module from <emphasis>the same repository</emphasis>.
+            Although module meta-data and module artifacts must be located in the same repository, it is possible to compose a single repository of multiple
+            URLs, giving multiple locations to search for meta-data files and jar files.
+        </para>
 
         <para>There are several different types of repositories you can declare:</para>
         <table>
@@ -612,23 +651,33 @@
                 </tr>
             </thead>
             <tr>
-                <td><link linkend="sub:maven_central">Maven central repository</link></td>
+                <td>
+                    <link linkend="sub:maven_central">Maven central repository</link>
+                </td>
                 <td>A pre-configured repository that looks for dependencies in Maven Central.</td>
             </tr>
             <tr>
-                <td><link linkend="sub:maven_local">Maven local repository</link></td>
+                <td>
+                    <link linkend="sub:maven_local">Maven local repository</link>
+                </td>
                 <td>A pre-configured repository that looks for dependencies in the local Maven repository.</td>
             </tr>
             <tr>
-                <td><link linkend="sub:maven_repo">Maven repository</link></td>
+                <td>
+                    <link linkend="sub:maven_repo">Maven repository</link>
+                </td>
                 <td>A Maven repository. Can be located on the local filesystem or at some remote location.</td>
             </tr>
             <tr>
-                <td><link linkend="sec:ivy_repositories">Ivy repository</link></td>
+                <td>
+                    <link linkend="sec:ivy_repositories">Ivy repository</link>
+                </td>
                 <td>An Ivy repository. Can be located on the local filesystem or at some remote location.</td>
             </tr>
             <tr>
-                <td><link linkend="sec:flat_dir_resolver">Flat directory repository</link></td>
+                <td>
+                    <link linkend="sec:flat_dir_resolver">Flat directory repository</link>
+                </td>
                 <td>A simple repository on the local filesystem. Does not support any meta-data formats.</td>
             </tr>
         </table>
@@ -650,9 +699,11 @@
             <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>
+            <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'>
@@ -678,7 +729,8 @@
                 <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>
+                <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>
 
@@ -691,7 +743,8 @@
             </sample>
             <para>This adds repositories which look into one or more directories for finding dependencies. If you only
                 work with flat directory resolvers you don't need to set all attributes of a dependency.
-                See <xref linkend='para:dependencies_with_empty_attributes'/>
+                See
+                <xref linkend='para:dependencies_with_empty_attributes'/>
             </para>
         </section>
 
@@ -701,7 +754,10 @@
             <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository">
                 <sourcefile file="build.gradle" snippet="ivy-repo-with-maven-layout"/>
             </sample>
-            <para>See <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository"/> for details.</para>
+            <para>See
+                <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository"/>
+                for details.
+            </para>
             <section>
                 <title>Defining custom patterns for an Ivy repository</title>
                 <para>To define an Ivy repository with a non-standard layout, you can define a pattern layout for the repository:
@@ -713,26 +769,23 @@
             <section>
                 <title>Ivy repository with Maven compatible layout</title>
                 <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>.
+                    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>Defining different artifact and ivy file locations for an Ivy repository</title>
-                <para>To define an Ivy repository which fetches ivy files and artifacts from different locations, you can explicitly define complete URL patterns
-                      for artifacts and ivy files:
+                <title>Defining different artifact and Ivy file locations for an Ivy repository</title>
+                <para>To define an Ivy repository which fetches Ivy files and artifacts from different locations, you can use the pattern layout with separate patterns to use to locate
+                    the Ivy files and artifacts:
                 </para>
                 <sample id="ivyRepository" dir="userguide/artifacts/defineRepository" title="Ivy repository with custom patterns">
                     <sourcefile file="build.gradle" snippet="ivy-repo-with-custom-pattern"/>
                 </sample>
                 <para>
-                    Each <literal>ivyPattern</literal> or <literal>artifactPattern</literal> specified for a repository adds an <emphasis>
-                    additional</emphasis> pattern, on top of any url/layout based patterns defined.
-                    Values supplied as <literal>ivyPattern</literal> or <literal>artifactPattern</literal> should be fully qualified URLs as they are not resolved
-                    relative to the <literal>url</literal> parameter for the repository. Any unqualified patterns will be resolved as a file path, relative to the project base directory.
+                    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>
             </section>
             <section>
@@ -762,8 +815,7 @@
             </para>
             <itemizedlist>
                 <listitem>
-                    <para>There are many options for the protocol to communicate with the repository (e.g. filesystem,
-                        http, ssh, ...)
+                    <para>There are many options for the protocol to communicate with the repository (e.g. filesystem, http, ssh, ...)
                     </para>
                 </listitem>
                 <listitem>
@@ -771,15 +823,12 @@
                     </para>
                 </listitem>
             </itemizedlist>
-            <para>Let's say, you declare a dependency on the
-                <literal>junit:junit:3.8.2</literal> library.
+            <para>Let's say, you declare a dependency on the <literal>junit:junit:3.8.2</literal> library.
                 Now how does Gradle find it in the repositories? Somehow the dependency information has to be mapped to a
                 path. In contrast to Maven, where this path is fixed, with Gradle you can define a pattern that defines
                 what the path will look like. Here are some examples:
                 <footnote>
-                    <para>At
-                        <ulink url='http://ant.apache.org/ivy/history/latest-milestone/concept.html'/>
-                        you can learn more about ivy patterns.
+                    <para>At <ulink url='http://ant.apache.org/ivy/history/latest-milestone/concept.html'/> you can learn more about ivy patterns.
                     </para>
                 </footnote>
             </para>
@@ -787,7 +836,7 @@
 // Maven2 layout (if a repository is marked as Maven2 compatible, the organization (group) is split into subfolders according to the dots.)
 someroot/[organisation]/[module]/[revision]/[module]-[revision].[ext]
 
-// Typical layout for an ivy repository (the organization is not split into subfolder)
+// Typical layout for an Ivy repository (the organization is not split into subfolder)
 someroot/[organisation]/[module]/[revision]/[type]s/[artifact].[ext]
 
 // Simple layout (the organization is not used, no nested folders.)
@@ -807,127 +856,170 @@ someroot/[artifact]-[revision].[ext]
     <section id='sec:dependency_resolution'>
         <title>How dependency resolution works</title>
         <para>Gradle takes your dependency declarations and repository definitions and attempts to download all of your dependencies by a process called <emphasis>dependency resolution</emphasis>.
-        Below is a brief outline of how this process works.</para>
-            <itemizedlist>
-                <listitem>
-                    <para>
-                        Given a required dependency, Gradle first attempts to resolve the <emphasis>module</emphasis> for that dependency. Each repository is inspected in order, searching
-                        first for a <emphasis>module descriptor</emphasis> file (pom or ivy file) that indicates the presence of that module. If no module descriptor is found,
-                        Gradle will search for the presence of the primary <emphasis>module artifact</emphasis> file indicating that the module exists in the repository.
-                    </para>
+            Below is a brief outline of how this process works.
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>
+                    Given a required dependency, Gradle first attempts to resolve the <emphasis>module</emphasis> for that dependency. Each repository is inspected in order, searching
+                    first for a <emphasis>module descriptor</emphasis> file (POM or Ivy file) that indicates the presence of that module. If no module descriptor is found,
+                    Gradle will search for the presence of the primary <emphasis>module artifact</emphasis> file indicating that the module exists in the repository.
+                </para>
+                <itemizedlist>
+                    <listitem>
+                        <para>If the dependency is declared as a dynamic version (like <literal>1.+</literal>), Gradle will resolve this to the newest available static version (like
+                            <literal>1.2</literal>) in the repository. For Maven repositories, this is done using the <literal>maven-metadata.xml</literal>
+                            file, while for Ivy repositories this is done by directory listing.
+                        </para>
+                    </listitem>
+                    <listitem>
+                        <para>If the module descriptor is a POM file that has a parent POM declared, Gradle will recursively attempt to resolve each of the parent modules for the POM.
+                        </para>
+                    </listitem>
+                </itemizedlist>
+            </listitem>
+            <listitem>
+                <para>Once each repository has been inspected for the module, Gradle will choose the 'best' one to use. This is done using the following criteria:
                     <itemizedlist>
-                        <listitem>
-                            <para>If the dependency is declared as a dynamic version (like <literal>1.+</literal>), Gradle will resolve this to the newest available static version (like <literal>1.2</literal>)
-                                in the repository. For maven repositories, this is done using the <literal>maven-metadata.xml</literal> file, while for ivy repositories this is done by directory listing.</para>
-                        </listitem>
-                        <listitem>
-                            <para>If the module descriptor is a <literal>pom</literal> file that has a parent pom declared, Gradle will recursively attempt to resolve each of the parent modules for the pom.</para>
-                        </listitem>
+                        <listitem>For a dynamic version, a 'higher' static version is preferred over a 'lower' version.</listitem>
+                        <listitem>Modules declared by a module descriptor file (Ivy or POM file) are preferred over modules that have an artifact file only.</listitem>
+                        <listitem>Modules from earlier repositories are preferred over modules in later repositories.</listitem>
                     </itemizedlist>
-                </listitem>
-                <listitem>
-                    <para>Once each repository has been inspected for the module, Gradle will choose the 'best' one to use. This is done using the following criteria:
-                        <itemizedlist>
-                            <listitem>For a dynamic version, a 'higher' static version is preferred over a 'lower' version.</listitem>
-                            <listitem>Modules declared by a module descriptor file (ivy or pom file) are preferred over modules that have an artifact file only.</listitem>
-                            <listitem>Modules from earlier repositories are preferred over modules in later repositories.</listitem>
-                        </itemizedlist>
-                    </para>
-                    <para>When the dependency is declared by a static version and a module descriptor file is found in a repository, there is no need to continue searching later
-                    repositories and the remainder of the process is short-circuited.</para>
-                </listitem>
-                <listitem>
-                    <para>All of the artifacts for the module are then requested from the <emphasis>same repository</emphasis> that was chosen in the process above.</para>
-                </listitem>
-            </itemizedlist>
+                </para>
+                <para>When the dependency is declared by a static version and a module descriptor file is found in a repository, there is no need to continue searching later
+                    repositories and the remainder of the process is short-circuited.
+                </para>
+            </listitem>
+            <listitem>
+                <para>All of the artifacts for the module are then requested from the <emphasis>same repository</emphasis> that was chosen in the process above.
+                </para>
+            </listitem>
+        </itemizedlist>
     </section>
-    <section id='sec:dependency_metadata_manipulation'>
-        <title>Manipulating the dependency metadata during the resolution process</title>
-        <para>Gradle's dependency resolution engine will eventually offer a complete access to the
-            dependency tree during the resolution, allowing any kind of manipulation.
-            We want to get there incrementally, by solving one by one all the important use cases raised by our users.
-            Currently, it is possible to manipulate the dependency metadata in following ways:
+    <section>
+        <title>Fine-tuning the dependency resolution process</title>
+        <para>In most cases, Gradle's default dependency management will resolve the dependencies that you want in your build. In some cases, however, it can be necessary to tweak
+            dependency resolution to ensure that your build receives exactly the right dependencies.
         </para>
+        <para>There are a number of ways that you can influence how Gradle resolves dependencies.</para>
+
         <section id='sec:forcing_modules'>
-            <title>Forcing certain module versions</title>
-            <para>It is possible to configure dependency resolution to use a specific version for given dependency (transitive or not).
-                The dependency is identified by group and name.
-                This feature is useful when tackling version conflicts - for more information see <xref linkend='sub:version_conflicts'/>.
-                Another interesting use case that drives this feature is dealing with rogue metadata of transitive dependencies.
-                Imagine a transitive dependency that is pulled onto your classpath. This dependency has poor quality metadata
-                that leads to some problems at dependency resolution time.
-                However, there is a new version of this dependency available, a version with improved metadata information.
-                Using forced modules, you can force a better version of this dependency and hence avoid the problem.
-                For an example, see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>
+            <title>Forcing a particular module version</title>
+            <para>Forcing a module version tells Gradle to always use a specific version for given dependency (transitive or not), overriding any version specified in a
+                published module descriptor. This can be very useful when tackling version conflicts - for more information see <xref linkend='sub:version_conflicts'/>.
+            </para>
+            <para>
+                Force versions can also be used to deal with rogue metadata of transitive dependencies.
+                If a transitive dependency has poor quality metadata that leads to problems at dependency resolution time, you can force Gradle to use a newer, fixed version of this dependency.
+                For an example, see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>.
+                Note that 'dependency resolve rules' (outlined below) provide a more powerful mechanism for replacing a broken module dependency. See <xref linkend='sec:blacklisting_version'/>.
             </para>
         </section>
         <section id='sec:dependency_resolve_rules'>
-            <title>Dependency resolve rules</title>
-            <para>A dependency resolve rule is executed for each resolved dependency and offers ways
-                to manipulate the dependency metadata when the dependency is resolved.
-                This feature is incubating, not entire dependency metadata can be manipulated at this time.
-                At the moment, the dependency resolve rule can be used to manipulate the dependency version.</para>
+            <title>Using dependency resolve rules</title>
+            <para>A dependency resolve rule is executed for each resolved dependency, and offers a powerful api for manipulating a requested dependency prior to that dependency being resolved.
+                This feature is incubating, but currently offers the ability to change the group, name and/or version of a requested dependency,
+                allowing a dependency to be substituted with a completely different module during resolution.
+            </para>
+            <para>
+                Dependency resolve rules provide a very powerful way to control the dependency resolution process, and can be used to implement all sorts of advanced
+                patterns in dependency management. Some of these patterns are outlined below.
+                For more information and code samples see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>.
+            </para>
             <section id='sec:releasable_unit'>
                 <title>Modelling releaseable units</title>
-                <para>One of the use cases for dependency resolve rules was the requirement
-                    of resolving all dependencies from a single releasable unit with the same consistent version.
-                    Lets consider that the releasable unit is a set of libraries that are built, tested and published together.
-                    Lets imagine that the releasable unit are all libraries that have 'org.gradle' group and we want them to use a consistent version:
+                <para>Often an organisation publishes a set of libraries with a single version; where the libraries are built, tested and published together.
+                    These libraries form a 'releasable unit', designed and intended to be used as a whole. It does not make sense to use libraries from different releasable units together.
+                </para>
+                <para>
+                    But it is easy for transitive dependency resolution to violate this contract. For example:
+                    <itemizedlist>
+                        <listitem><literal>module-a</literal> depends on <literal>releasable-unit:part-one:1.0</literal></listitem>
+                        <listitem><literal>module-b</literal> depends on <literal>releasable-unit:part-two:1.1</literal></listitem>
+                    </itemizedlist>
+                    A build depending on both <literal>module-a</literal> and <literal>module-b</literal> will obtain different versions of libraries within the releasable unit.
+                </para>
+                <para>
+                    Dependency resolve rules give you the power to enforce releasable units in your build.
+                    Imagine a releasable unit defined by all libraries that have 'org.gradle' group. We can force all of these libraries to use a consistent version:
                     <sample id="releasable-unit" dir="userguide/artifacts/resolutionStrategy" title="Forcing consistent version for a group of libraries">
                         <sourcefile file="build.gradle" snippet="releasable-unit"/>
                     </sample>
-                    For more information and code samples see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>.
                 </para>
             </section>
             <section id='sec:custom_versioning_scheme'>
-                <title>Custom versioning scheme</title>
-                <para>Another use case that supports the dependency resolve rules is a custom versioning scheme.
-                    Imagine an environment where the versions of libraries that can be declared in gradle builds are maintained and audited externally.
-                    In the build script, the developer declares dependencies using group, name and a placeholder for version, for example a String value: 'default'.
-                    The 'default' version is resolved to a specific version via a dependency resolve rule, which looks up the version in some corporate catalog.
-                    The dependency resolve rule implementation can be neatly encapsulated in a suite of corporate plugins.
-                    Here is an example of such dependency resolve rule:
+                <title>Implement a custom versioning scheme</title>
+                <para>
+                    In some corporate environments, the list of module versions that can be declared in gradle builds is maintained and audited externally.
+                    Dependency resolve rules provide a neat implementation of this pattern:
+                    <itemizedlist>
+                        <listitem>In the build script, the developer declares dependencies with the module group and name, but uses a placeholder version, for example: '<literal>default</literal>'.</listitem>
+                        <listitem>The 'default' version is resolved to a specific version via a dependency resolve rule, which looks up the version in a corporate catalog of approved modules.</listitem>
+                    </itemizedlist>
+
+                    This rule implementation can be neatly encapsulated in a corporate plugin, and shared across all builds within the organisation.
                     <sample id="custom-versioning-scheme" dir="userguide/artifacts/resolutionStrategy" title="Using a custom versioning scheme">
                         <sourcefile file="build.gradle" snippet="custom-versioning-scheme"/>
                     </sample>
-                    For more information and code samples see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>.
                 </para>
             </section>
             <section id='sec:blacklisting_version'>
-                <title>Blacklisting a version with a replacement</title>
-                <para>One of the interesting scenarios for dependency resolve rules is blacklisting a certain version of a dependency
-                    and providing a replacement version. This is extremely useful if an unwanted transitive dependency is pulled in to the dependency graph.
-                    In this scenario the dependency resolve rule can be used to specify a different version of an unwanted dependency.
-                    Why a transitive dependency might be unwanted? For example, it was published with troublesome metadata
-                    that causes the dependency resolution to fail. More specifically, our unwanted transitive dependency
-                    declares dependency to a library that cannot be found in any of the public repositories.
-                    There are many other reasons why certain version of a dependency might be unwanted and a different version is preferred.
-                </para><para>
-                    In below example, we will demonstrate how to avoid a dependency to 'org.cool.software:cool-library:1.2'
-                    and prefer different version of 'cool-library'. Let's imagine, version 1.2.1 contains important fixes
-                    and hence we prefer 1.2.1 over 1.2.
-                    Bear in mind that in the example below, only version 1.2 is replaced with 1.2.1.
-                    If there are other versions that participate in a conflict resolution the final version may be different.
-                    For example the 'newest' conflict resolution strategy can select version 1.3 because this version was also pulled transitively.
+                <title>Blacklisting a particular version with a replacement</title>
+                <para>
+                    Dependency resolve rules provide a mechanism for blacklisting a particular version of a dependency and providing a replacement version.
+                    This can be useful if a certain dependency version is broken and should not be used,
+                    where a dependency resolve rule causes this version to be replaced with a known good version.
+                    One example of a broken module is one that declares a dependency on a library that cannot be found in any of the public repositories,
+                    but there are many other reasons why a particular module version is unwanted and a different version is preferred.
+                </para>
+                <para>
+                    In example below, imagine that version <literal>1.2.1</literal> contains important fixes and should always be used in preference to <literal>1.2</literal>.
+                    The rule provided will enforce just this: any time version <literal>1.2</literal> is encountered it will be replaced with <literal>1.2.1</literal>.
+                    Note that this is different from a forced version as described above, in that any other versions of this module would not be affected.
+                    This means that the 'newest' conflict resolution strategy would still select version <literal>1.3</literal> if this version was also pulled transitively.
                     <sample id="blacklisting_version" dir="userguide/artifacts/resolutionStrategy" title="Blacklisting a version with a replacement">
                         <sourcefile file="build.gradle" snippet="blacklisting_version"/>
                     </sample>
-                    For more information and code samples see <apilink class='org.gradle.api.artifacts.ResolutionStrategy'/>.
                 </para>
             </section>
+            <section id='sec:module_substitution'>
+                <title>Substituting a dependency module with a compatible replacement</title>
+                <para>At times a completely different module can serve as a replacement for a requested module dependency.
+                    Examples include using '<literal>groovy</literal>' in place of '<literal>groovy-all</literal>', or using '<literal>log4j-over-slf4j</literal>' instead of '<literal>log4j</literal>'.
+                    Starting with Gradle 1.5 you can make these substitutions using dependency resolve rules:
+                    <sample id="module_substitution" dir="userguide/artifacts/resolutionStrategy" title="Changing dependency group and/or name at the resolution">
+                        <sourcefile file="build.gradle" snippet="module_substitution"/>
+                    </sample>
+                </para>
+            </section>
+        </section>
+        <section id="ivy_dynamic_resolve_mode">
+            <title>Enabling Ivy dynamic resolve mode</title>
+            <para>
+                Gradle's Ivy repository implementations support the equivalent to Ivy's dynamic resolve mode. Normally, Gradle will use the <literal>rev</literal> attribute for each dependency
+                definition included in an <filename>ivy.xml</filename> file. In dynamic resolve mode, Gradle will instead prefer the <literal>revConstraint</literal> attribute over the
+                <literal>rev</literal> attribute for a given dependency definition. If the <literal>revConstraint</literal> attribute is not present, the <literal>rev</literal> attribute is used
+                instead.
+            </para>
+            <para>To enable dynamic resolve mode, you need to set the appropriate option on the repository definition. A couple of examples are shown below. Note that dynamic resolve mode is only
+                available for Gradle's Ivy repositories. It is not available for Maven repositories, or custom Ivy <classname>DependencyResolver</classname> implementations.</para>
+            <sample id="dynamicResolveMode" dir="userguide/artifacts/defineRepository" title="Enabling dynamic resolve mode">
+                <sourcefile file="build.gradle" snippet="ivy-repo-dynamic-mode"/>
+            </sample>
         </section>
     </section>
     <section id='sec:dependency_cache'>
         <title>The dependency cache</title>
         <para>Gradle contains a highly sophisticated dependency caching mechanism, which seeks to minimise the number of remote requests made in
-        dependency resolution, while striving to guarantee that the results of dependency resolution are correct and reproducible.
+            dependency resolution, while striving to guarantee that the results of dependency resolution are correct and reproducible.
         </para>
         <para>
-            The Gradle dependency cache consists of 2 key types of storage:</para>
+            The Gradle dependency cache consists of 2 key types of storage:
+        </para>
         <itemizedlist>
             <listitem>
-                <para>A file-based store of downloaded artifacts, including binaries like jars as well as raw downloaded meta-data like pom files and ivy files.
-                      The storage path for a downloaded artifact includes the SHA1 checksum, meaning that 2 artifacts with the same name but different content can easily be cached.
+                <para>A file-based store of downloaded artifacts, including binaries like jars as well as raw downloaded meta-data like POM files and Ivy files.
+                    The storage path for a downloaded artifact includes the SHA1 checksum, meaning that 2 artifacts with the same name but different content can easily be cached.
                 </para>
             </listitem>
             <listitem>
@@ -935,10 +1027,12 @@ someroot/[artifact]-[revision].[ext]
             </listitem>
         </itemizedlist>
         <para>Separating the storage of downloaded artifacts from the cache metadata permits us to do some very powerful things with our cache that would be difficult with a transparent,
-            file-only cache layout.</para>
+            file-only cache layout.
+        </para>
         <para>The Gradle cache does not allow the local cache to hide problems and creating mysterious and difficult to debug behavior
             that has been a challenge with many build tools. This new behavior is implemented in a bandwidth and storage efficient way.
-            In doing so, Gradle enables reliable and reproducible enterprise builds.</para>
+            In doing so, Gradle enables reliable and reproducible enterprise builds.
+        </para>
 
         <section id='sec:cache_features'>
             <title>Key features of the Gradle dependency cache</title>
@@ -948,11 +1042,14 @@ someroot/[artifact]-[revision].[ext]
                     Gradle keeps a record of various aspects of dependency resolution in binary format in the metadata cache.
                     The information stored in the metadata cache includes:
                     <itemizedlist>
-                        <listitem>The result of resolving a dynamic version (eg 1.+) to a concrete version (eg 1.2).</listitem>
+                        <listitem>The result of resolving a dynamic version (e.g. <literal>1.+</literal>) to a concrete version (e.g. <literal>1.2</literal>).</listitem>
                         <listitem>The resolved module metadata for a particular module, including module artifacts and module dependencies.</listitem>
                         <listitem>The resolved artifact metadata for a particular artifact, including a pointer to the downloaded artifact file.</listitem>
-                        <listitem>The <emphasis>absence</emphasis> of a particular module or artifact in a particular repository,
-                            eliminating repeated attempts to access a resource that does not exist. </listitem>
+                        <listitem>The
+                            <emphasis>absence</emphasis>
+                            of a particular module or artifact in a particular repository,
+                            eliminating repeated attempts to access a resource that does not exist.
+                        </listitem>
                     </itemizedlist>
                     Every entry in the metadata cache includes a record of the repository that provided the information as well as a timestamp that can be used for cache expiry.
                 </para>
@@ -961,19 +1058,24 @@ someroot/[artifact]-[revision].[ext]
                 <title>Repository caches are independent</title>
                 <para>As described above, for each repository there is a separate metadata cache. A repository is identified by its URL, type and layout.
                     If a module or artifact has not been previously resolved from <emphasis>this repository</emphasis>, Gradle will attempt to resolve the module
-                    against the repository. This will always involve a remote lookup on the repository, however in many cases no download will be required (see <xref linkend='sub:cache_artifact_reuse'/>, below).</para>
+                    against the repository. This will always involve a remote lookup on the repository, however in many cases no download will be required (see<xref
+                            linkend='sub:cache_artifact_reuse'/>, below).
+                </para>
                 <para>Dependency resolution will fail if the required artifacts are not available in any repository specified by the build,
                     regardless whether the local cache has retrieved this artifact from a different repository.
                     Repository independence allows builds to be isolated from each other in an advanced way that no build tool has done before.
-                    This is a key feature to create builds that are reliable and reproducible in any environment.</para>
+                    This is a key feature to create builds that are reliable and reproducible in any environment.
+                </para>
             </section>
             <section id='sub:cache_artifact_reuse'>
                 <title>Artifact reuse</title>
                 <para>Before downloading an artifact, Gradle tries to determine the checksum of the required artifact by downloading the sha file associated
                     with that artifact. If the checksum can be retrieved, an artifact is not downloaded if an artifact already exists with the same id and checksum.
-                    If the checksum cannot be retrieved from the remote server, the artifact will be downloaded (and ignored if it matches an existing artifact).</para>
+                    If the checksum cannot be retrieved from the remote server, the artifact will be downloaded (and ignored if it matches an existing artifact).
+                </para>
                 <para>As well as considering artifacts downloaded from a different repository, Gradle will also attempt to reuse artifacts found in the local Maven Repository.
-                    If a candidate artifact has been downloaded by Maven, Gradle will use this artifact if it can be verified to match the checksum declared by the remote server.</para>
+                    If a candidate artifact has been downloaded by Maven, Gradle will use this artifact if it can be verified to match the checksum declared by the remote server.
+                </para>
             </section>
             <section id='sub:cache_checksum_storage'>
                 <title>Checksum based storage</title>
@@ -996,24 +1098,31 @@ someroot/[artifact]-[revision].[ext]
             <title>Command line options to override caching</title>
             <section id='sub:cache_offline'>
                 <title>Offline</title>
-                <para>The <literal>--offline</literal> command line switch tells Gradle to always use dependency modules from the cache, regardless if they are due to be checked again.
+                <para>The <literal>--offline</literal>
+                    command line switch tells Gradle to always use dependency modules from the cache, regardless if they are due to be checked again.
                     When running with offline, Gradle will never attempt to access the network to perform dependency resolution.
-                    If required modules are not present in the dependency cache, build execution will fail.</para>
+                    If required modules are not present in the dependency cache, build execution will fail.
+                </para>
             </section>
             <section id='sub:cache_refresh'>
                 <title>Refresh</title>
                 <para>At times, the Gradle Dependency Cache can be out of sync with the actual state of the configured repositories. Perhaps a repository was initially misconfigured,
-                    or perhaps a "non-changing" module was published incorrectly. To refresh all dependencies in the dependency cache, use the <literal>--refresh-dependencies</literal>
-                    option on the command line.</para>
+                    or perhaps a "non-changing" module was published incorrectly. To refresh all dependencies in the dependency cache, use the
+                    <literal>--refresh-dependencies</literal> option on the command line.
+                </para>
                 <para>The <literal>--refresh-dependencies</literal> option tells Gradle to ignore all cached entries for resolved modules and artifacts.
                     A fresh resolve will be performed against all configured repositories, with dynamic versions recalculated, modules refreshed, and artifacts downloaded.
                     However, where possible Gradle will attempt to if the previously downloaded artifacts are valid before downloading again.
-                    This is done by comparing published SHA1 values in the repository with the SHA1 values for existing downloaded artifacts.</para>
+                    This is done by comparing published SHA1 values in the repository with the SHA1 values for existing downloaded artifacts.
+                </para>
             </section>
         </section>
         <section id='sec:controlling_caching'>
             <title>Fine-tuned control over dependency caching</title>
-            <para>You can fine-tune certain aspects of caching using the <literal>ResolutionStrategy</literal> for a configuration.</para>
+            <para>You can fine-tune certain aspects of caching using the
+                <literal>ResolutionStrategy</literal>
+                for a configuration.
+            </para>
             <para>By default, Gradle caches dynamic versions for 24 hours. To change how long Gradle will cache the resolved version for a dynamic version, use:
             </para>
             <sample id="dynamic-version-cache-control" dir="userguide/artifacts/resolutionStrategy" title="Dynamic version cache control">
@@ -1024,13 +1133,13 @@ someroot/[artifact]-[revision].[ext]
             <sample id="changing-module-cache-control" dir="userguide/artifacts/resolutionStrategy" title="Changing module cache control">
                 <sourcefile file="build.gradle" snippet="changing-module-cache-control"/>
             </sample>
-            <para>For more details, take a look at the API documentation for <apilink class="org.gradle.api.artifacts.ResolutionStrategy"/>.</para>
+            <para>For more details, take a look at the API documentation for<apilink class="org.gradle.api.artifacts.ResolutionStrategy"/>.
+            </para>
         </section>
     </section>
     <section id='sec:strategies_of_transitive_dependency_management'>
         <title>Strategies for transitive dependency management</title>
-        <para>Many projects rely on the <ulink url='http://repo1.maven.org/maven2'>Maven Central repository</ulink>. This is not
-            without problems.
+        <para>Many projects rely on the <ulink url='http://repo1.maven.org/maven2'>Maven Central repository</ulink>. This is not without problems.
         </para>
         <itemizedlist>
             <listitem>
@@ -1038,14 +1147,12 @@ someroot/[artifact]-[revision].[ext]
                 </para>
             </listitem>
             <listitem>
-                <para>The <literal>pom.xml</literal>'s of many projects have wrong information (as one example, the POM of
-                    <literal>commons-httpclient-3.0</literal> declares JUnit as a runtime dependency).
+                <para>The POM files of many projects have wrong information (as one example, the POM of <literal>commons-httpclient-3.0</literal>
+                    declares JUnit as a runtime dependency).
                 </para>
             </listitem>
             <listitem>
-                <para>For many projects there is not one right set of dependencies (as more or less imposed by the
-                    <literal>pom</literal>
-                    format).
+                <para>For many projects there is not one right set of dependencies (as more or less imposed by the POM format).
                 </para>
             </listitem>
         </itemizedlist>
@@ -1058,12 +1165,11 @@ someroot/[artifact]-[revision].[ext]
                 </para>
             </listitem>
             <listitem>
-                <para>You want to deal properly with wrong metadata in a Maven Central <literal>pom.xml</literal>.
+                <para>You want to deal properly with wrong metadata in a Maven Central POM file.
                 </para>
             </listitem>
             <listitem>
-                <para>You don't want to expose people who want to build your project, to the
-                    downtimes or sometimes very long response times of Maven Central.
+                <para>You don't want to expose people who want to build your project, to the downtimes or sometimes very long response times of Maven Central.
                 </para>
             </listitem>
         </itemizedlist>
@@ -1083,38 +1189,29 @@ someroot/[artifact]-[revision].[ext]
         </para>
         <para>This is a reason why some projects prefer to store their libraries in their version control system. This
             approach is fully supported by Gradle. The libraries can be stored in a flat directory without any XML module
-            descriptor files.  Yet Gradle offers complete transitive dependency management. You can use either client module
+            descriptor files. Yet Gradle offers complete transitive dependency management. You can use either client module
             dependencies to express the dependency relations, or artifact dependencies in case a first level dependency has no
             transitive dependencies. People can check out such a project from svn and have everything necessary to build it.
         </para>
         <para>If you are working with a distributed version control system like Git you probably don't want to
-        use the version control system to store libraries as people check out the whole history. But even here the flexibility
-        of Gradle can make your life easier. For example you can use a shared flat directory without XML descriptors and
-        yet you can have full transitive dependency management as described above.</para>
-        <para>You could also have a mixed strategy. If your main concern is bad metadata in the <literal>pom.xml</literal> and maintaining
-            custom XML descriptors,
-            <emphasis>Client Modules</emphasis>
-            offer an alternative. But you can of course still use Maven2 repo and your custom repository as a
-            repository for
-            <emphasis>jars only</emphasis>
-            and still enjoy
-            <emphasis>transitive</emphasis>
-            dependency management. Or you can only provide client modules for POMs with bad metadata. For the
+            use the version control system to store libraries as people check out the whole history. But even here the flexibility
+            of Gradle can make your life easier. For example you can use a shared flat directory without XML descriptors and
+            yet you can have full transitive dependency management as described above.
+        </para>
+        <para>You could also have a mixed strategy. If your main concern is bad metadata in the POM file and maintaining custom XML descriptors,
+            <emphasis>Client Modules</emphasis> offer an alternative. But you can of course still use Maven2 repo and your custom repository as a repository for
+            <emphasis>jars only</emphasis> and still enjoy <emphasis>transitive</emphasis> dependency management. Or you can only provide client modules for POMs with bad metadata. For the
             jars and the correct POMs you still use the remote repository.
         </para>
         <section id='sub:implicit_transitive_dependencies'>
             <title>Implicit transitive dependencies</title>
-            <para>There is another way to deal with transitive dependencies
-                <emphasis>without</emphasis>
-                XML descriptor files. You can do this with Gradle, but we don't recommend it. We mention it for the sake
-                of completeness and comparison with other build tools.
+            <para>There is another way to deal with transitive dependencies <emphasis>without</emphasis> XML descriptor files. You can do this with Gradle, but we don't recommend it.
+                We mention it for the sake of completeness and comparison with other build tools.
             </para>
             <para>The trick is to use only artifact dependencies and group them in lists. That way you have somehow
-                expressed, what are your first level dependencies and what are transitive dependencies (see
-                <xref linkend="para:notation_collections"/>).
+                expressed, what are your first level dependencies and what are transitive dependencies (see <xref linkend="para:notation_collections"/>).
                 But the draw-back is, that for the Gradle dependency management all dependencies are considered first level dependencies. The
-                dependency reports don't show your real dependency graph and the
-                <literal>compile</literal>
+                dependency reports don't show your real dependency graph and the <literal>compile</literal>
                 task uses all dependencies, not just the first level dependencies. All in all, your build is less
                 maintainable and reliable than it could be when using client modules. And you don't gain anything.
             </para>
diff --git a/subprojects/docs/src/docs/userguide/distributionPlugin.xml b/subprojects/docs/src/docs/userguide/distributionPlugin.xml
new file mode 100644
index 0000000..5d453b9
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/distributionPlugin.xml
@@ -0,0 +1,123 @@
+<!--
+  ~ Copyright 2012 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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='distribution_plugin'>
+    <title>The Distribution Plugin</title>
+    <note>
+        <para>
+            The distribution plugin is incubating (see <xref linkend="sec:incubating_state"/>).
+        </para>
+    </note>
+    <para>The distribution plugin extends the language plugins with common distribution related tasks.
+	It allows bundling a project including binaries, sources and documentation.
+	</para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the distribution plugin, include in your build script:</para>
+        <sample id="useDistributionPlugin" dir="userguide/distribution" title="Using the distribution plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+        <para>To define the name for the distribution you have to set the <literal>baseName</literal> property as shown below</para>
+        <sample id="configureDistributionName" dir="userguide/distribution" title="Configure the distribution name">
+            <sourcefile file="build.gradle" snippet="name-conf"/>
+        </sample>
+        <para>The plugin build a distribution for your project. You can run <userinput>gradle distZip</userinput> to create a
+            ZIP containing the distribution.  Given that the project name is myproject and version is 1.2, then running gradle customDistZip will produce a ZIP file called myproject-1.2.zip
+        </para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Distribution plugin adds the following tasks to the project.</para>
+        <table>
+            <title>Distribution plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+		<tr>
+            <td>
+                <literal>distZip</literal>
+            </td>
+            <td>
+                <literal>-</literal>
+            </td>
+            <td>
+                <apilink class="org.gradle.api.tasks.bundling.Zip"/>
+            </td>
+                <td>Creates a full distribution ZIP archive.</td>
+            </tr>
+        <tr>
+            <td>
+               <literal>distTar</literal>
+            </td>
+            <td>
+               <literal>-</literal>
+            </td>
+            <td>
+                <apilink class="org.gradle.api.tasks.bundling.Tar"/>
+            </td>
+                <td>Creates a full distribution TAR archive.</td>
+            </tr>
+        <tr>
+            <td>
+                <literal>installDist</literal>
+            </td>
+            <td>
+                <literal>-</literal>
+            </td>
+            <td>
+                <apilink class="org.gradle.api.tasks.Sync"/>
+            </td>
+            <td>Install distribution contents.</td>
+            </tr>
+
+        </table>
+    </section>
+
+    <section>
+        <title>Configure distributions</title>
+        <para>The distribution plugin allow to configure distributions to include custom files and to change distribution baseName.
+        </para>
+        <sample id="customDistribution" dir="userguide/distribution" title="Declare multiple distributions">
+            <sourcefile file="build.gradle" snippet="custom-distribution"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Multiple distributions</title>
+        <para>The distribution plugin allow to generate multiple distributions.
+
+        </para>
+        <sample id="multipleDistribution" dir="userguide/distribution" title="Declare multiple distributions">
+            <sourcefile file="build.gradle" snippet="declare-distribution"/>
+        </sample>
+        <para>This will following tasks to the project : customDistZip, customDistTar, installcustomDist. Given that the project name is myproject, then running gradle customDistZip will produce a ZIP file called myproject-custom-1.2.zip
+            and running customDistTar will produce myproject-custom-1.2.tar. Running installcustomDist will install distribution contents into buildDir/install/custom.</para>
+    </section>
+
+    <section>
+            <title>Extension properties</title>
+            <para>The distribution plugin add an extension to the project, which you can use to configure its behaviour. See <apilink class="org.gradle.api.Project"/>.
+            </para>
+    </section>
+
+</chapter>
diff --git a/subprojects/docs/src/docs/userguide/featureLifecycle.xml b/subprojects/docs/src/docs/userguide/featureLifecycle.xml
index 4cdea41..291919b 100644
--- a/subprojects/docs/src/docs/userguide/featureLifecycle.xml
+++ b/subprojects/docs/src/docs/userguide/featureLifecycle.xml
@@ -66,20 +66,19 @@
                 Internal features may evolve into public features.
             </para>
         </section>
-        <section>
+        <section id="sec:incubating_state">
             <title>Incubating</title>
             <para>
-                When new features are introduced to Gradle, they are usually introduced in an <firstterm>incubating</firstterm> state.
-                A feature in this state is intended to be used by Gradle users, but may change in future Gradle versions until it is no longer incubating.
-                Changes to incubating features for a Gradle release will be highlighted in the release notes for that release.
-                The incubation period for new features varies depending on the scope, complexity and nature of the feature.
-            </para>
-            <para>
-                Features are introduced in the incubating state to allow real world feedback to be incorporated into the feature before it is made public and
+                Features are introduced in the <firstterm>incubating</firstterm> state to allow real world feedback to be incorporated into the feature before it is made public and
                 locked down to provide backwards compatibility.
                 It also gives users who are willing to accept potential future changes early access to the feature so they can put it into use immediately.
             </para>
             <para>
+                A feature in an incubating state may change in future Gradle versions until it is no longer incubating.
+                Changes to incubating features for a Gradle release will be highlighted in the release notes for that release.
+                The incubation period for new features varies depending on the scope, complexity and nature of the feature.
+            </para>
+            <para>
                 Features in incubation are clearly indicated to be so. In the source code, all methods/properties/classes that are incubating are
                 annotated with <apilink class="org.gradle.api.Incubating"/>, which is also used to specially mark them in the DSL and API references. If an incubating
                 feature is discussed in this User Guide, it will be explicitly said to be in the incubating state.
diff --git a/subprojects/docs/src/docs/userguide/gradleDaemon.xml b/subprojects/docs/src/docs/userguide/gradleDaemon.xml
index 48306ba..696c2a7 100644
--- a/subprojects/docs/src/docs/userguide/gradleDaemon.xml
+++ b/subprojects/docs/src/docs/userguide/gradleDaemon.xml
@@ -55,7 +55,7 @@
         </para>
         <para>In future the daemon will offer more features:
             <itemizedlist>
-                <listitem>Snappy up-to-date checks: use native file system change notifications (eg via jdk7 nio.2)
+                <listitem>Snappy up-to-date checks: use native file system change notifications (e.g. via jdk7 nio.2)
                     to preemptively perform up-to-date analysis.</listitem>
                 <listitem>Even faster builds: preemptively evaluate projects, so that the model is ready
                     when the user next invokes Gradle.</listitem>
diff --git a/subprojects/docs/src/docs/userguide/groovyPlugin.xml b/subprojects/docs/src/docs/userguide/groovyPlugin.xml
index 05558e0..b33bb14 100644
--- a/subprojects/docs/src/docs/userguide/groovyPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/groovyPlugin.xml
@@ -222,7 +222,7 @@
         <para>
             Note: When using the <literal>groovy</literal> rather than the <literal>groovy-all</literal> artifact, automatic configuration of
             <literal>groovyClasspath</literal> will only work correctly if the project declares a repository that contains the <literal>groovy</literal>
-            artifact along with a descriptor (<literal>pom.xml</literal> or <literal>ivy.xml</literal>) listing its dependencies. Otherwise, only the
+            artifact along with a descriptor (<filename>pom.xml</filename> or <filename>ivy.xml</filename>) listing its dependencies. Otherwise, only the
             artifact itself will be added to <literal>groovyClasspath</literal>, which will likely result in a <literal>NoClassDefFoundError</literal>
             during compilation.
         </para>
diff --git a/subprojects/docs/src/docs/userguide/installation.xml b/subprojects/docs/src/docs/userguide/installation.xml
index 2521327..78cb916 100644
--- a/subprojects/docs/src/docs/userguide/installation.xml
+++ b/subprojects/docs/src/docs/userguide/installation.xml
@@ -56,14 +56,6 @@
         </para>
     </listitem>
 </itemizedlist>
-
-<note>
-<title>For Un*x users</title>
-
-<para>You need a GNU compatible tool to unzip Gradle, if you want the file permissions to be properly set. We mention this as
-some zip front ends for Mac OS X don't restore the file permissions properly.
-</para>
-</note>
 </section>
 
 <section>
diff --git a/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml b/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml
index 9750cf6..c695b8a 100644
--- a/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/javaLibraryDistributionPlugin.xml
@@ -2,7 +2,7 @@
     <title>The Java Library Distribution Plugin</title>
     <note>
         <para>
-            The Java library distribution plugin is incubating and should not be considered stable.
+            The Java library distribution plugin is incubating (see <xref linkend="sec:incubating_state"/>).
         </para>
     </note>
     <para>The Java library distribution plugin adds support for building a distribution ZIP for a Java library. The distribution contains the
@@ -15,14 +15,14 @@
         <sample id="usejavaLibraryPlugin" dir="userguide/javaLibraryDistribution" title="Using the java library distribution plugin">
             <sourcefile file="build.gradle" snippet="use-plugin"/>
         </sample>
-        <para>To define the name for the distribution you have to set the <literal>name</literal> property as shown below:
+        <para>To define the name for the distribution you have to set the <literal>baseName</literal> property as shown below:
         </para>
         <sample id="useApplicationPlugin" dir="userguide/javaLibraryDistribution" title="Configure the distribution name">
             <sourcefile file="build.gradle" snippet="name-conf"/>
         </sample>
 
         <para>The plugin build a distribution for your library. The distribution will package up the runtime dependencies of the library
-            All files stored in <filename>src/dist</filename> will be added to the root of the archive distribution. You can run
+            All files stored in <filename>src/main/dist</filename> will be added to the root of the archive distribution. You can run
             <userinput>gradle distZip</userinput> to create a ZIP containing the distribution.
         </para>
     </section>
@@ -55,20 +55,14 @@
         </table>
     </section>
 
-    <section>
-        <title>Extension properties</title>
-        <para>The Java library distribution plugin add an extension to the project, which you can use to configure its behaviour. See <apilink class="org.gradle.api.Project"/>.
-        </para>
-    </section>
-
     <section id="java_library_distribution_resources">
         <title>Including other resources in the distribution</title>
         <para>
             All of the files from the <filename>src/dist</filename> directory are copied. To include any static files in the distribution, simply arrange
-            them in the <filename>src/dist</filename> directory, or add it to the task.
+            them in the <filename>src/dist</filename> directory, or add it to the content of the distribution.
         </para>
         <sample id="includeTaskOutputInApplicationDistribution" dir="userguide/javaLibraryDistribution" title="Include files in the distribution">
-            <sourcefile file="build.gradle" snippet="custom-distZip"/>
+            <sourcefile file="build.gradle" snippet="custom-distribution"/>
         </sample>
     </section>
 
diff --git a/subprojects/docs/src/docs/userguide/javaPlugin.xml b/subprojects/docs/src/docs/userguide/javaPlugin.xml
index fbb563c..9b177d0 100644
--- a/subprojects/docs/src/docs/userguide/javaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/javaPlugin.xml
@@ -47,7 +47,7 @@
             compiled and executed using JUnit or TestNG.
         </para>
     </section>
-    
+
     <section>
         <title>Tasks</title>
         <para>The Java plugin adds a number of tasks to your project, as shown below.</para>
@@ -223,7 +223,7 @@
                 <td>Assembles the given source set's classes directory.</td>
             </tr>
         </table>
-        
+
         <para>The Java plugin also adds a number of tasks which form a lifecycle for the project:</para>
 
         <table>
@@ -654,7 +654,7 @@
                 <td>
                     <literal>sourceCompatibility</literal>
                 </td>
-                <td><apilink class="org.gradle.api.JavaVersion"/>. Can also set using a String or a Number, eg
+                <td><apilink class="org.gradle.api.JavaVersion"/>. Can also set using a String or a Number, e.g.
                     <literal>'1.5'</literal> or <literal>1.5</literal>.
                 </td>
                 <td>Value of the current used JVM</td>
@@ -664,7 +664,7 @@
                 <td>
                     <literal>targetCompatibility</literal>
                 </td>
-                <td><apilink class="org.gradle.api.JavaVersion"/>. Can also set using a String or Number, eg
+                <td><apilink class="org.gradle.api.JavaVersion"/>. Can also set using a String or Number, e.g.
                     <literal>'1.5'</literal> or <literal>1.5</literal>.
                 </td>
                 <td>
@@ -958,7 +958,7 @@
             <ulink url='http://download.oracle.com/javase/1.5.0/docs/tooldocs/windows/javadoc.html#referenceguide'>reference documentation</ulink>
             of the Javadoc executable.
             For a complete list of supported Javadoc options consult the API documentation of the following classes:
-            <apilink class="org.gradle.external.javadoc.CoreJavadocOptions"/> and <apilink class="org.gradle.external.javadoc.StandardJavadocDocletOptions"/>. 
+            <apilink class="org.gradle.external.javadoc.CoreJavadocOptions"/> and <apilink class="org.gradle.external.javadoc.StandardJavadocDocletOptions"/>.
         </para>
         <table>
             <title>Java plugin - Javadoc properties</title>
@@ -1091,11 +1091,11 @@
                 <td><literal><replaceable>sourceSet</replaceable>.output.classesDir</literal></td>
             </tr>
         </table>
-        
+
         <para>The compile task delegates to Ant's javac task. Setting <literal>options.useAnt</literal> to <literal>false</literal>
             activates Gradle's direct compiler integration, bypassing the Ant task. In a future Gradle release, this will become the default.
         </para>
-        
+
         <para>By default, the Java compiler runs in the Gradle process. Setting <literal>options.fork</literal> to <literal>true</literal>
             causes compilation to occur in a separate process. In the case of the Ant javac task, this means that a new process will be
             forked for each compile task, which can slow down compilation. Conversely, Gradle's direct compiler integration (see above) will
@@ -1123,7 +1123,7 @@
                 suspended and listening on port 5005. This makes it very easy to debug your tests.  You may also enable
                 this using a system property as specified below.
             </para>
-            <para>You can specify whether or not to execute your tests in parallel. Gradle provides parallel test execution 
+            <para>You can specify whether or not to execute your tests in parallel. Gradle provides parallel test execution
                 by running multiple test processes concurrently. Each test process executes only a single test at a time, so you
                 generally don't need to do anything special to your tests to take advantage of this.
                 The <literal>maxParallelForks</literal> property specifies the maximum number of test processes to run
@@ -1185,7 +1185,7 @@
             </para>
             <para>Setting a system property of <literal>taskName.debug</literal> will run the tests in debug mode,
                   suspended and listening on port 5005.  For example:
-                  <literal>gradle test -Dtest.single=ThisUniquelyNamedTest -Dtest.debug</literal>                
+                  <literal>gradle test -Dtest.single=ThisUniquelyNamedTest -Dtest.debug</literal>
             </para>
         </section>
 
@@ -1352,5 +1352,5 @@
         <para>How to upload your archives is described in <xref linkend="artifact_management"/>.
         </para>
     </section>
-    
+
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/mavenPlugin.xml b/subprojects/docs/src/docs/userguide/mavenPlugin.xml
index 02e441a..7148821 100644
--- a/subprojects/docs/src/docs/userguide/mavenPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/mavenPlugin.xml
@@ -212,7 +212,7 @@
                 </tr>
                 <tr>
                     <td>webdav</td>
-                    <td>org.apache.maven.wagon:wagon-webdav-jackrabbit:2.2</td>
+                    <td>org.apache.maven.wagon:wagon-webdav:1.0-beta-2</td>
                 </tr>
                 <tr>
                     <td>file</td>
diff --git a/subprojects/docs/src/docs/userguide/multiproject.xml b/subprojects/docs/src/docs/userguide/multiproject.xml
index 3a973cb..7ce1e20 100644
--- a/subprojects/docs/src/docs/userguide/multiproject.xml
+++ b/subprojects/docs/src/docs/userguide/multiproject.xml
@@ -45,25 +45,28 @@
                 <para>
                     Configuration on demand mode attempts to configure only projects that are relevant for requested tasks.
                     This way, the configuration time of a large multi-project build is greatly improved.
-                    In the long term, this mode may become the default mode, possibly the only mode for Gradle build execution.
+                    In the long term, this mode will become the default mode, possibly the only mode for Gradle build execution.
                     The configuration on demand feature is incubating so not every build is guaranteed to work correctly.
                     The feature should work very well for multi-project builds that have decoupled projects (<xref linkend="sec:decoupled_projects"/>).
                     In configuration on demand mode projects are configured as follows:
                     <itemizedlist>
-                        <listitem>Root project is always evaluated.
+                        <listitem>Root project is always configured.
                             This way the typical common configuration is supported (allprojects or subprojects script blocks).</listitem>
-                        <listitem>Project that lives in the directory where the build is executed is always evaluated.</listitem>
-                        <listitem>The standard project dependencies are supported.
+                        <listitem>Project in the directory where the build is executed is also configured, but only when Gradle is executed without any tasks.
+                            This way the default tasks behave correctly when projects are configured on demand.</listitem>
+                        <listitem>The standard project dependencies are supported and makes relevant projects configured.
                             If project A has a compile dependency on project B then building A causes configuration of both projects: A and B.</listitem>
-                        <listitem>Task requested via task path causes the relevant project configured.
-                            Building 'projectA:projectB:someTask' causes configuration of both: projectA and projectB.
+                        <listitem>The task dependencies declared via task path are supported and cause relevant projects configured.
+                            Example: someTask.dependsOn(":someOtherProject:someOtherTask")
+                        </listitem>
+                        <listitem>Task requested via task path from the command line (or Tooling API) causes the relevant project configured.
+                            Building 'projectA:projectB:someTask' causes configuration of projectB.
                         </listitem>
                     </itemizedlist>
                 </para>
                 <para>
-                    Down the road Gradle will offer first class command line support for configuration on demand mode.
-                    At the moment, to enable this feature please use incubating 'org.gradle.configuration.ondemand' system property.
-                    For reference on using system properties see <xref linkend="sec:gradle_properties_and_system_properties"/>.
+                    Eager to try out this new feature? To configure on demand with every build run see <xref linkend="sec:gradle_configuration_properties"/>.
+                    To configure on demand just for given build please see <xref linkend="gradle_command_line"/>.
                 </para>
             </section>
         </section>
@@ -594,6 +597,45 @@
             </para>
         </section>
     </section>
+    <section id="sec:parallel_execution">
+        <title>Parallel project execution</title>
+        <para>With more and more CPU cores available on developer desktops and CI servers,
+            it is important that Gradle is able to fully utilise these processing resources.
+            More specifically, the parallel execution attempts to:
+            <itemizedlist>
+                <listitem>Reduce total build time for a multi-project build where execution is IO bound or otherwise does not consume all available CPU resources.</listitem>
+                <listitem>Provide faster feedback for execution of small projects without awaiting completion of other projects.</listitem>
+            </itemizedlist>
+            Although Gradle already offers parallel test execution via <apilink class="org.gradle.api.tasks.testing.Test" method="setMaxParallelForks"/>
+            the feature described in this section is parallel execution at a project level.
+            Parallel execution is an incubating feature. Please use it and let us know how it works for you.
+        </para>
+        <para>
+            Parallel project execution allows the separate projects in a decoupled multi-project build to be executed in parallel
+            (see also: <xref linkend="sec:decoupled_projects"/>).
+            While parallel execution does not strictly require decoupling at configuration time,
+            the long-term goal is to provide a powerful set of features that will be available for fully decoupled projects.
+            Such features include:
+            <itemizedlist>
+                <listitem><xref linkend="sec:configuration_on_demand"/>.</listitem>
+                <listitem>Configuration of projects in parallel.</listitem>
+                <listitem>Re-use of configuration for unchanged projects.</listitem>
+                <listitem>Project-level up-to-date checks.</listitem>
+                <listitem>Using pre-built artifacts in the place of building dependent projects.</listitem>
+            </itemizedlist>
+        </para>
+        <para>
+            How does the parallel execution work? First, you need to tell Gradle to use the parallel mode.
+            You can use the command line argument (<xref linkend='gradle_command_line'/>) or configure your build environment (<xref linkend="sec:gradle_configuration_properties"/>).
+            Unless you provide specific number of parallel threads Gradle attempts to choose the right number based on available CPU cores.
+            Every parallel worker exclusively owns a given project while executing a task.
+            This means that 2 tasks from the same project are never executed in parallel.
+            Therefore only multi-project builds can take advantage of parallel execution.
+            Task dependencies are fully supported and parallel workers will start executing upstream tasks first.
+            Bear in mind that the alphabetical scheduling of decoupled tasks, known from the sequential execution, does not really work in parallel mode.
+            You need to make sure the task dependencies are declared correctly to avoid ordering issues.
+        </para>
+    </section>
     <section id="sec:decoupled_projects">
         <title>Decoupled Projects</title>
         <para>Gradle allows any project to access any other project during both the configuration and execution phases. While this provides a great deal of power
@@ -602,7 +644,7 @@
         </para>
         <para>Two projects are said to be <emphasis>decoupled</emphasis> if they do not directly access each other's project model. Decoupled projects may only interact in terms of
             declared dependencies: project dependencies (<xref linkend='sub:project_dependencies'/>) and/or task dependencies (<xref linkend='sec:task_dependencies'/>).
-            Any other form of project interaction (ie. by modifying another project object or by reading a value from another project object) causes the projects to
+            Any other form of project interaction (i.e. by modifying another project object or by reading a value from another project object) causes the projects to
             be coupled.
         </para>
         <para>
@@ -612,7 +654,7 @@
             define common configuration, but as far as Gradle is concerned this root project is still a fully-fledged project, and by using <literal>allprojects</literal>
             that project is effectively coupled to all other projects.
         </para>
-        <para>This means that using any form of shared build script logic or configuration injection (<literal>allprojects</literal>, <literal>subprojects</literal>, etc)
+        <para>This means that using any form of shared build script logic or configuration injection (<literal>allprojects</literal>, <literal>subprojects</literal>, etc.)
             will cause your projects to be coupled. As we extend the concept of project decoupling and provide features that take advantage of decoupled projects,
             we will also introduce new features to help you to solve common use cases (like configuration injection) without causing your projects to be coupled.
         </para>
diff --git a/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml b/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
index ecaae3c..ae993c9 100644
--- a/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
+++ b/subprojects/docs/src/docs/userguide/organizeBuildLogic.xml
@@ -197,7 +197,7 @@
         <sample id="buildLogic" dir="userguide/organizeBuildLogic" title="Ant optional dependencies">
             <sourcefile file="build.gradle"/>
         </sample>
-        <para>This is also nice example for the usage of client modules. The pom.xml in maven central for the
+        <para>This is also nice example for the usage of client modules. The POM file in Maven Central for the
         ant-commons-net task does not provide the right information for this use case.</para>
     </section>
     <section id='sec:philosophy'>
diff --git a/subprojects/docs/src/docs/userguide/overview.xml b/subprojects/docs/src/docs/userguide/overview.xml
index 066a346..bc254c1 100644
--- a/subprojects/docs/src/docs/userguide/overview.xml
+++ b/subprojects/docs/src/docs/userguide/overview.xml
@@ -89,7 +89,7 @@
                 <listitem>
                     <para>Different teams prefer different ways to manage their external dependencies.
                         Gradle provides convenient support for any strategy. From transitive dependency 
-                        management with remote maven and ivy repositories to jars or dirs on the local file system.</para>
+                        management with remote Maven and Ivy repositories to jars or dirs on the local file system.</para>
                 </listitem>
             </varlistentry>
             <varlistentry>
@@ -100,7 +100,7 @@
                         You can depend on them from Gradle, you can enhance them from Gradle, you can even declare dependencies on
                         Gradle tasks in your build.xml. The same integration is provided for properties, paths, etc ...</para>
                     <para>Gradle fully supports your existing Maven or Ivy repository infrastructure for publishing and retrieving
-                        dependencies. Gradle also provides a converter for turning a Maven pom.xml into a Gradle script.
+                        dependencies. Gradle also provides a converter for turning a Maven <filename>pom.xml</filename> into a Gradle script.
                         Runtime imports of Maven projects will come soon.</para>
                 </listitem>
             </varlistentry>
diff --git a/subprojects/docs/src/docs/userguide/publishingIvy.xml b/subprojects/docs/src/docs/userguide/publishingIvy.xml
index 8b62e88..a00837d 100644
--- a/subprojects/docs/src/docs/userguide/publishingIvy.xml
+++ b/subprojects/docs/src/docs/userguide/publishingIvy.xml
@@ -18,48 +18,52 @@
     <title>Ivy Publishing (new)</title>
     <note>
         <para>
-            This chapter describes the new <emphasis>incubating</emphasis> Ivy publishing support introduced in Gradle 1.3.
-            If you are looking for documentation on the “traditional” Ivy publishing support please see <xref linkend="artifact_management"/>.
+            This chapter describes the new <emphasis>incubating</emphasis> Ivy publishing support provided by the “<literal>ivy-publish</literal>”
+            plugin. Eventually this new publishing support will replace publishing via the <literal>Upload</literal> task.
+        </para>
+        <para>
+            If you are looking for documentation on 'traditional' Ivy publishing using the <literal>Upload</literal> task please see
+            <xref linkend="artifact_management"/>.
         </para>
     </note>
     <para>
         This chapter describes how to publish build artifacts in the <ulink url="http://ant.apache.org/ivy/">Apache Ivy</ulink> format, usually to a repository
-        for consumption by other builds or projects. What is published is one or more artifacts created by the build, and an Ivy <firstterm>module descriptor</firstterm>
+        for consumption by other builds or projects. What is published is one or more artifacts created by the build, and an
+        Ivy <firstterm>module descriptor</firstterm> (normally <filename>ivy.xml</filename>)
         that describes the artifacts and the dependencies of the artifacts, if any.
     </para>
     <para>
         A published Ivy module can be consumed by Gradle (see <xref linkend="dependency_management" />) and other tools that understand the Ivy format.
     </para>
-    <section>
+    <section id="publishing_ivy:plugin">
         <title>The “<literal>ivy-publish</literal>” Plugin</title>
         <para>
             The ability to publish in the Ivy format is provided by the “<literal>ivy-publish</literal>” plugin.
         </para>
-        <sample id="publishing_ivy:apply_plugin" dir="ivypublish-new" title="Applying the “ivy-publish” plugin">
+        <para>
+            The “<literal>publishing</literal>” plugin creates an extension on the project named “<literal>publishing</literal>” of type <apilink class="org.gradle.api.publish.PublishingExtension"/>.
+            This extension provides a container of named publications and a container of named repositories. The “<literal>ivy-publish</literal>” plugin works with
+            <apilink class="org.gradle.api.publish.ivy.IvyPublication"/> publications and <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository"/> repositories.
+        </para>
+        <sample id="publishing_ivy:apply-plugin-snippet" dir="ivy-publish/quickstart" title="Applying the “ivy-publish” plugin">
             <sourcefile file="build.gradle" snippet="use-plugin" />
         </sample>
         <para>
-            This plugin does the following:
+            Applying the “<literal>ivy-publish</literal>” plugin does the following:
         </para>
         <itemizedlist>
-            <listitem>Applies the “<literal>publishing</literal>” plugin</listitem>
-            <listitem>
-                Creates a publication in the <literal>publishing.publications</literal> container of type <apilink class="org.gradle.api.publish.ivy.IvyPublication" /> named “<literal>ivy</literal>”
-                (see <xref linkend="publishing_ivy:publications"/>)
+            <listitem>Applies the “<literal>publishing</literal>” plugin
             </listitem>
             <listitem>
-                Establish a rule to automatically create an <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" /> task for each Ivy publishing repository added
-                (see <xref linkend="publishing_ivy:repositories"/>)
+                Establishes a rule to automatically create a <apilink class="org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor"/>
+                task for each <apilink class="org.gradle.api.publish.ivy.IvyPublication"/> added (see <xref linkend="publishing_ivy:publications"/>).
             </listitem>
             <listitem>
-                Create a task named 'generateIvyDescriptor' of type <apilink class="org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor" />.
+                Establishes a rule to automatically create a <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository"/> task
+                for the combination of each <apilink class="org.gradle.api.publish.ivy.IvyPublication"/> added (see <xref linkend="publishing_ivy:publications"/>),
+                with each <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository"/> added (see <xref linkend="publishing_ivy:repositories"/>).
             </listitem>
         </itemizedlist>
-        <para>
-            The “<literal>publishing</literal>” plugin creates an extension on the project named “<literal>publishing</literal>” of type <apilink class="org.gradle.api.publish.PublishingExtension"/>.
-            This extension provides a container of named publications and a container of named repositories. The “<literal>ivy-publish</literal>” works with
-            <apilink class="org.gradle.api.publish.ivy.IvyPublication"/> publications and <apilink class="org.gradle.api.artifacts.repositories.IvyArtifactRepository" /> repositories.
-        </para>
     </section>
     <section id="publishing_ivy:publications">
         <title>Publications</title>
@@ -76,58 +80,113 @@
             <apilink class="org.gradle.api.publish.PublishingExtension" method="getPublications()" /> container. Each publication has a unique name within the project.
         </para>
         <para>
-            At this time, it is not possible to create arbitrary publication objects. When the “<literal>ivy-publish</literal>” plugin is applied it creates a
-            single publication named “<literal>ivy</literal>”. This publication will publish all of artifacts of all of the project's visible configurations,
-            and any configurations that they extend from.
-        </para>
-        <sample dir="ivypublish-new" id="publishing_ivy:build_to_publish" title="A build to publish">
-            <sourcefile file="build.gradle" snippet="input" />
-        </sample>
-        <para>
-            The “<literal>publishing.publications.ivy</literal>” publication that was added to the “<literal>publishing.publications</literal>” container of the project
-            will be configured to publish two artifacts:
-        </para>
-        <itemizedlist>
-            <listitem>The primary “jar” artifact automatically created by the “<literal>java</literal>” plugin (see <xref linkend="java_plugin"/>)</listitem>
-            <listitem>The source “jar” artifact that has been explicitly configured in this build</listitem>
-        </itemizedlist>
-        <para>
-            When this publication is published, the <firstterm>module descriptor</firstterm> (i.e. the <literal>ivy.xml</literal> file) that is produced will look like…
-        </para>
-        <tip>
-            <para>Note that the <literal>«PUBLICATION-TIME-STAMP»</literal> in this example Ivy module descriptor will be the timestamp of when the descriptor was generated.</para>
-        </tip>
-        <sample dir="ivypublish-new" id="publishing_ivy:output_ivy.xml" title="Example generated ivy.xml">
-            <sourcefile file="output-ivy.xml" snippet="content" />
-        </sample>
-        <para>
-            The attributes of the <literal><info></literal> tag identify the module. These values are derived from the following project properties:
-        </para>
-        <itemizedlist>
-            <listitem><literal>organisation</literal> - <apilink class="org.gradle.api.Project" method="getGroup()" /></listitem>
-            <listitem><literal>module</literal> - <apilink class="org.gradle.api.Project" method="getName()" /></listitem>
-            <listitem><literal>revision</literal> - <apilink class="org.gradle.api.Project" method="getVersion()" /></listitem>
-            <listitem><literal>status</literal> - <apilink class="org.gradle.api.Project" method="getStatus()" /></listitem>
-        </itemizedlist>
-        <para>
-            Note that you can set the value of these project properties in your build script, with the exception of <literal>name</literal>.
+            For the “<literal>ivy-publish</literal>” plugin to have any effect, a <apilink class="org.gradle.api.publish.ivy.IvyPublication" /> must be added to the set of publications.
+            This publication determines which artifacts are actually published as well as the details included in the associated Ivy module descriptor file.
+            A publication can be configured by adding components, customising artifacts, and by modifying the generated module descriptor file directly.
         </para>
         <section>
-            <title>Modifying the published module descriptor</title>
+            <title>Publishing a Software Component</title>
+            <para>
+                The simplest way to publish a Gradle project to an Ivy repository is to specify a <apilink class="org.gradle.api.component.SoftwareComponent"/> to publish.
+                The components presently available for publication are:
+            </para>
+            <table>
+                <title>Software Components</title>
+                <thead>
+                    <tr>
+                        <td>Name</td>
+                        <td>Provided By</td>
+                        <td>Artifacts</td>
+                        <td>Dependencies</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td><literal>java</literal></td>
+                    <td><link linkend="java_plugin">Java Plugin</link></td>
+                    <td>Generated jar file</td>
+                    <td>Dependencies from 'runtime' configuration</td>
+                </tr>
+                <tr>
+                    <td><literal>web</literal></td>
+                    <td><link linkend="war_plugin">War Plugin</link></td>
+                    <td>Generated war file</td>
+                    <td>No dependencies</td>
+                </tr>
+            </table>
+            <para>
+                 In the following example, artifacts and runtime dependencies are taken from the `java` component, which is added by the <literal>Java Plugin</literal>.
+             </para>
+            <sample dir="ivy-publish/quickstart" id="publishing_ivy:publish-component-snippet" title="Publishing a java module to Ivy">
+                <sourcefile file="build.gradle" snippet="publish-component" />
+            </sample>
+        </section>
+        <section>
+            <title>Publishing custom artifacts</title>
+            <para>
+                It is also possible to explicitly configure artifacts to be included in the publication. Artifacts are commonly supplied as raw files, or as instances of
+                <apilink class="org.gradle.api.tasks.bundling.AbstractArchiveTask"/> (e.g. Jar, Zip).
+            </para>
+            <para>
+                For each custom artifact, it is possible to specify the <literal>name</literal>, <literal>extension</literal>, <literal>type</literal>, <literal>classifier</literal>
+                and <literal>conf</literal> values to use for publication. Note that each artifacts must have a unique name/classifier/extension combination.
+            </para>
             <para>
-                Notice that the <literal>junit</literal> dependency that appears in the descriptor above is different to what was actually used in the project.
-                This is because of the descriptor modification that was declared.
+                Configure custom artifacts as follows:
             </para>
-            <sample dir="ivypublish-new" id="publishing_ivy:descriptor_mod" title="Modifying the Ivy descriptor">
-                <sourcefile file="build.gradle" snippet="descriptor-mod" />
+            <sample dir="ivy-publish/java-multi-project" id="publishing_ivy:publish-custom-artifact-snippet" title="Publishing additional artifact to Ivy">
+                <sourcefile file="build.gradle" snippet="publish-custom-artifact" />
             </sample>
             <para>
-                It is possible to modify any aspect of the created descriptor should you need to.
+                See <apilink class="org.gradle.api.publish.ivy.IvyPublication" /> for more detailed documentation on how artifacts can be customised.
+            </para>
+        </section>
+        <section>
+            <title>Identity values for the published project</title>
+            <para>
+                The generated Ivy module descriptor file contains an<literal><info></literal> tag that identifies the module.
+                The default identity values are derived from the following project properties:
+            </para>
+            <itemizedlist>
+                <listitem><literal>organisation</literal> - <apilink class="org.gradle.api.Project" method="getGroup()" /></listitem>
+                <listitem><literal>module</literal> - <apilink class="org.gradle.api.Project" method="getName()" /></listitem>
+                <listitem><literal>revision</literal> - <apilink class="org.gradle.api.Project" method="getVersion()" /></listitem>
+                <listitem><literal>status</literal> - <apilink class="org.gradle.api.Project" method="getStatus()" /></listitem>
+            </itemizedlist>
+            <tip>
+                Certain repositories are not able to handle all supported characters.
+                For example, the ':' character cannot be used as an identifier when publishing to a filesystem-backed repository on Windows.
+            </tip>
+            <para>
+                Note that you can set the value of these project properties in your build script, with the exception of <literal>name</literal>.
+            </para>
+            <para>
+                Gradle will handle any valid Unicode character for organisation, module and revision (as well as artifact name, extension and classifier).
+                The only values that are explicitly prohibited are '<literal>\</literal>', '<literal>/</literal>' and any ISO control character. The supplied values are validated early in publication.
+            </para>
+        </section>
+        <section>
+            <title>Modifying the generated module descriptor</title>
+            <para>
+                At times, the module descriptor file generated from the project information will need to be tweaked before publishing. The “<literal>ivy-publish</literal>”
+                plugin provides a hook to allow such modification.
+            </para>
+            <sample dir="ivy-publish/descriptor-customization" id="publishing_ivy:descriptor-customization-snippet" title="Customizing the module descriptor file">
+                <sourcefile file="build.gradle" snippet="customize-descriptor" />
+            </sample>
+            <para>
+                In this example we are adding a 'description' element to the generated Ivy dependency descriptor, but this this hook, allows you to modify any aspect
+                of the generated descriptor. For example, you could replace the version range for a dependency with the actual version used to produce the build.
+            </para>
+            <para>
+                See <apilink class="org.gradle.api.publish.ivy.IvyModuleDescriptor" method="withXml(org.gradle.api.Action)" /> for the relevant API reference documentation.
+            </para>
+            <para>
+                It is possible to modify virtually any aspect of the created descriptor should you need to.
                 This means that it is also possible to modify the descriptor in such a way that it is no longer a valid
                 Ivy module descriptor, so care must be taken when using this feature.
             </para>
             <para>
-                See <apilink class="org.gradle.api.publish.ivy.IvyModuleDescriptor" method="withXml(org.gradle.api.Action)" /> for the relevant API reference documentation on descriptor modification.
+                The identifier (organisation, module, revision) of the published module is an exception; these values cannot be modified in the descriptor using the `withXML` hook.
             </para>
         </section>
     </section>
@@ -137,14 +196,13 @@
             Publications are published to repositories. The repositories to publish to are defined by the <apilink class="org.gradle.api.publish.PublishingExtension" method="getRepositories()" />
             container.
         </para>
-        <sample dir="ivypublish-new" id="publishing_ivy:repositories" title="Declaring repositories to publish to">
+        <sample dir="ivy-publish/quickstart" id="publishing_ivy:sample_repositories" title="Declaring repositories to publish to">
             <sourcefile file="build.gradle" snippet="repositories" />
         </sample>
         <para>
-            The DSL used to declare repositories to publish to is the same DSL that is used to declare repositories to consume dependencies from,
-            <apilink class="org.gradle.api.artifacts.dsl.RepositoryHandler" />. However, in the context of Ivy publication only the repositories created
-            by the <literal>ivy()</literal> methods can be used as publication destinations. You cannot publish an <literal>IvyPublication</literal> to
-            a Maven repository for example.
+            The DSL used to declare repositories for publishing is the same DSL that is used to declare repositories for dependencies (<apilink class="org.gradle.api.artifacts.dsl.RepositoryHandler" />).
+            However, in the context of Ivy publication only the repositories created by the <literal>ivy()</literal> methods can be used as publication destinations.
+            You cannot publish an <literal>IvyPublication</literal> to a Maven repository for example.
         </para>
     </section>
     <section id="publishing_ivy:publishing">
@@ -155,64 +213,97 @@
             combination in the <literal>publishing.publications</literal> and <literal>publishing.repositories</literal> containers respectively.
         </para>
         <para>
-            In the example we have been working with so far, given that the publication that the “<literal>ivy-publish</literal>”
-            plugin creates is named “<literal>ivy</literal>” and that the default name for repositories created using the <literal>ivy()</literal> methods of the
-            <literal>publishing.repositories</literal> container is also “<literal>ivy</literal>”, a publish task will be created with the name
-            “<literal>publishIvyPublicationToIvyRepository</literal>”. The naming pattern is
-            “<literal>publish«<emphasis>NAME OF PUBLICATION</emphasis>»PublicationTo«<emphasis>NAME OF REPOSITORY</emphasis>»Repository</literal>”.
+            The created task is named using the pattern "<literal>publish«<emphasis>NAME OF PUBLICATION</emphasis>»PublicationTo«<emphasis>NAME OF REPOSITORY</emphasis>»Repository</literal>".
+            So in this example a single <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" /> task is be added, named '<literal>publishIvyJavaPublicationToIvyRepository</literal>'.
         </para>
+        <sample dir="ivy-publish/quickstart" id="publishingIvyPublishSingle" title="Choosing a particular publication to publish">
+            <sourcefile file="build.gradle"/>
+            <output args='publishIvyJavaPublicationToIvyRepository'/>
+        </sample>
+        <section>
+            <title>The “<literal>publish</literal>” lifecycle task</title>
+            <para>
+                The “<literal>publish</literal>” plugin (that the “<literal>ivy-publish</literal>” plugin implicitly applies) adds a lifecycle task
+                that can be used to publish all publications to all applicable repositories named “<literal>publish</literal>”.
+            </para>
+            <para>
+                In more concrete terms, executing this task will execute all <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" /> tasks in the project.
+                This is usually the most convenient way to perform a publish.
+            </para>
+            <sample dir="ivy-publish/quickstart" id="publishingIvyPublishLifecycle" title="Publishing all publications via the “publish” lifecycle task">
+                <output args='publish'/>
+            </sample>
+    </section>
+    </section>
+    <section id="publishing_ivy:descriptor">
+        <title>Generating the Ivy module descriptor file without publishing</title>
         <para>
-            Executing this task will build all of the artifacts to be published, and transfer them to the repository.
+            At times it is useful to generate the Ivy module descriptor file (normally <filename>ivy.xml</filename>) without publishing your module to an Ivy repository.
+            Since descriptor file generation is performed by a separate task, this is very easy to do.
         </para>
-        <sample dir="ivypublish-new" id="publishingIvyPublishSingle" title="Publishing via single publish task">
-            <output args='publishIvyPublicationToIvyRepository'/>
-        </sample>
-    <section>
-        <title>The “<literal>publish</literal>” lifecycle task</title>
         <para>
-            The “<literal>publish</literal>” plugin (that the “<literal>ivy-publish</literal>” plugin implicitly applies) adds a lifecycle task
-            that can be used to publish all publications to all applicable repositories named “<literal>publish</literal>”.
+            The “<literal>ivy-publish</literal>” plugin automatically wires in one <apilink class="org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor" /> task
+            for each registered <apilink class="org.gradle.api.publish.ivy.IvyPublication" />.
+            This task is given a name based on the name of the publication:  "<literal>generate«<emphasis>NAME OF PUBLICATION</emphasis>»IvyModuleDescriptor</literal>".
+            So in the above example where the publication is named "<literal>ivyJava</literal>", the task will be named "<literal>generateIvyJavaIvyModuleDescriptor</literal>".
         </para>
         <para>
-            In more concrete terms, executing this task will execute all <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" /> tasks in the project.
-            This is usually the most convenient way to perform a publish.
+            You can specify where the generated Ivy file will be located by setting the <literal>destination</literal> property on the generate task.
+            By default this file is generated to <literal>build/publications/«<emphasis>NAME OF PUBLICATION</emphasis>»/ivy.xml</literal>.
         </para>
-        <sample dir="ivypublish-new" id="publishingIvyPublishLifecycle" title="Publishing via “publish” lifecycle task">
-            <output args='publish'/>
+        <sample dir="ivy-publish/descriptor-customization" id="publishingIvyGenerateDescriptor" title="Generating the Ivy module descriptor file">
+            <sourcefile file="build.gradle" snippet="generate" />
+            <output args="generateIvyCustomIvyModuleDescriptor"/>
         </sample>
+        <note>
+            <para>
+                The “<literal>ivy-publish</literal>” plugin leverages some experimental support for late plugin configuration,
+                and the <literal>GenerateIvyDescriptor</literal> task will not be constructed until the publishing extension is configured.
+                The simplest way to ensure that the publishing plugin is configured when you attempt to access the <literal>GenerateIvyDescriptor</literal> task
+                is to place the access inside a <literal>publishing</literal> block, as the above example demonstrates.
+            </para>
+            <para>
+                The same applies to any attempt to access publication-specific tasks like <apilink class="org.gradle.api.publish.ivy.tasks.PublishToIvyRepository" />.
+                These tasks should be referenced from within a <literal>publishing</literal> block.
+            </para>
+        </note>
     </section>
-    </section>
-    <section id="publishing_ivy:descriptor">
-        <title>Generating the Ivy module descriptor file</title>
+    <section id="publishing_ivy:example">
+        <title>Complete example</title>
         <para>
-            Sometimes you want to generate the Ivy module descriptor file (normally <literal>ivy.xml</literal>) without publishing your module to an Ivy
-            repository. For this purpose, the <literal>ivy-publish</literal> plugin automatically wires in one <apilink class="org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor" /> task
-            for each registered <apilink class="org.gradle.api.publish.ivy.IvyPublication" />.
+            The following example demonstrates publishing with a multi-project build. Each project publishes a java component and a configured additional source artifact.
+            The descriptor file is customized to include the project description for each project.
         </para>
+        <sample dir="ivy-publish/java-multi-project" id="publishing_ivy:complete_example" title="Publishing a java module">
+            <sourcefile file="build.gradle" />
+        </sample>
         <para>
-            In the default case of a single <literal>IvyPublication</literal> named 'ivy', the <literal>ivy-publish</literal> plugin
-            will create a single task named <literal>generateIvyModuleDescriptor</literal>.
-            Where more than one publication is present, additional tasks will be created using the naming pattern
-            <literal>generate«<emphasis>NAME OF PUBLICATION</emphasis>»IvyModuleDescriptor</literal>. Thus if there is one publication named 'other' in addition
-            to the default publication, there will be 2 tasks created: <literal>generateIvyModuleDescriptor</literal> and <literal>generateOtherIvyModuleDescriptor</literal>.
+            The result is that the following artifacts will be published for each project:
         </para>
+        <itemizedlist>
+            <listitem>The Ivy module descriptor file: <filename>ivy-1.0.xml</filename>.</listitem>
+           <listitem>The primary “jar” artifact for the java component: <filename>project1-1.0.jar</filename>.</listitem>
+           <listitem>The source “jar” artifact that has been explicitly configured:<filename>project1-1.0-source.jar</filename>.</listitem>
+       </itemizedlist>
         <para>
-            You can specify where the generated Ivy file will be located by setting the <literal>destination</literal> property on the generate task.
-            By default this file is generated to <literal>build/publications/«<emphasis>NAME OF PUBLICATION</emphasis>»/ivy.xml</literal>.
+            When <literal>project1</literal> is published, the module descriptor (i.e. the <filename>ivy.xml</filename> file) that is produced will look like…
         </para>
-        <sample dir="ivypublish-new" id="generateIvyModuleDescriptor" title="Generating the Ivy module descriptor file">
-            <sourcefile file="build.gradle" snippet="generate" />
+        <tip>
+            <para>Note that the <literal>«PUBLICATION-TIME-STAMP»</literal> in this example Ivy module descriptor will be the timestamp of when the descriptor was generated.</para>
+        </tip>
+        <sample dir="ivy-publish/java-multi-project" id="publishing_ivy:output_ivy.xml" title="Example generated ivy.xml">
+            <sourcefile file="output-ivy.xml" snippet="content" />
         </sample>
     </section>
-    <section>
+    <section id="publishing_ivy:future">
         <title>Future features</title>
         <para>
-            The “<literal>ivy-publish</literal>” functionality as described above is incomplete, as the feature is still <firstterm>incubating</firstterm>.
+            The “<literal>ivy-publish</literal>” plugin functionality as described above is incomplete, as the feature is still <firstterm>incubating</firstterm>.
             Over the coming Gradle releases, the functionality will be expanded to include (but not limited to):
         </para>
         <itemizedlist>
             <listitem>Convenient customisation of module attributes (<literal>module</literal>, <literal>organisation</literal> etc.)</listitem>
-            <listitem>Fine grained control of which artifacts are published</listitem>
+            <listitem>Convenient customisation of dependencies reported in <literal>module descriptor</literal>.</listitem>
             <listitem>Multiple discreet publications per project</listitem>
         </itemizedlist>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/publishingMaven.xml b/subprojects/docs/src/docs/userguide/publishingMaven.xml
index 0718ca6..a1a9f07 100644
--- a/subprojects/docs/src/docs/userguide/publishingMaven.xml
+++ b/subprojects/docs/src/docs/userguide/publishingMaven.xml
@@ -18,101 +18,188 @@
     <title>Maven Publishing (new)</title>
     <note>
         <para>
-            This chapter describes the new <emphasis>incubating</emphasis> Maven publishing support introduced in Gradle 1.4.
-            If you are looking for documentation on the 'traditional' Maven publishing support please see <xref linkend="artifact_management"/>.
+            This chapter describes the new <emphasis>incubating</emphasis> Maven publishing support provided by the “<literal>maven-publish</literal>”
+            plugin. Eventually this new publishing support will replace publishing via the <literal>Upload</literal> task.
         </para>
         <para>
-            This chapter is a work in progress, and is not comprehensive or complete.
-            It is expected that you understand the concepts in <xref linkend="publishing_ivy"/> before reading this chapter.
+            If you are looking for documentation on 'traditional' Maven publishing using the <literal>Upload</literal> task please see
+            <xref linkend="artifact_management"/>.
         </para>
     </note>
     <para>
-        This chapter describes how to publish build artifacts to a <ulink url="http://maven.apache.org/">Apache Maven</ulink> Repository.
+        This chapter describes how to publish build artifacts to an <ulink url="http://maven.apache.org/">Apache Maven</ulink> Repository.
         A module published to a Maven repository can be consumed by Maven, Gradle (see <xref linkend="dependency_management" />) and other tools that understand the Maven repository format.
     </para>
-    <note>
+    <section>
+        <title>The “<literal>maven-publish</literal>” Plugin</title>
         <para>
-            It is currently only possible to publish '<literal>java</literal>' projects using the <literal>maven-publish</literal> plugin. If the <literal>java</literal> plugin is not
-            applied (either directly or indirectly) then no publication will be created.
+            The ability to publish in the Maven format is provided by the “<literal>maven-publish</literal>” plugin.
         </para>
-    </note>
-    <section>
-        <title>The <literal>maven-publish</literal> Plugin</title>
         <para>
-            The ability to publish in the Maven format is provided by the '<literal>maven-publish</literal>' plugin.
+            The “<literal>publishing</literal>” plugin creates an extension on the project named “<literal>publishing</literal>” of type <apilink class="org.gradle.api.publish.PublishingExtension"/>.
+            This extension provides a container of named publications and a container of named repositories. The “<literal>maven-publish</literal>” plugin works with
+            <apilink class="org.gradle.api.publish.maven.MavenPublication"/> publications and <apilink class="org.gradle.api.artifacts.repositories.MavenArtifactRepository" /> repositories.
         </para>
-        <sample id="publishing_maven:apply_plugin" dir="maven/publish-new" title="Applying the 'maven-publish' plugin">
+        <sample id="publishing_maven:apply_plugin" dir="maven-publish/quickstart" title="Applying the 'maven-publish' plugin">
             <sourcefile file="build.gradle" snippet="use-plugin" />
         </sample>
         <para>
-            This plugin does the following:
+            Applying the “<literal>maven-publish</literal>” plugin does the following:
         </para>
         <itemizedlist>
-            <listitem>Applies the <literal>publishing</literal> plugin</listitem>
+            <listitem>Applies the “<literal>publishing</literal>” plugin
+            </listitem>
+            <listitem>
+                Establishes a rule to automatically create a <apilink class="org.gradle.api.publish.maven.tasks.GenerateMavenPom"/>
+                task for each <apilink class="org.gradle.api.publish.maven.MavenPublication"/> added (see <xref linkend="publishing_maven:publications"/>).
+            </listitem>
             <listitem>
-                If the project has the <literal>java</literal> plugin applied:
-                <itemizedlist>
-                    <listitem>
-                        Creates a publication in the <literal>publishing.publications</literal> container
-                        of type <apilink class="org.gradle.api.publish.maven.MavenPublication" /> named “<literal>maven</literal>”
-                        (see <xref linkend="publishing_maven:publications"/>)
-                    </listitem>
-                    <listitem>
-                        Establish a rule to automatically create an <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository" /> task for each Maven publishing repository added
-                        (see <xref linkend="publishing_maven:repositories"/>)
-                    </listitem>
-                </itemizedlist>
+                Establishes a rule to automatically create a <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository"/> task
+                for the combination of each <apilink class="org.gradle.api.publish.maven.MavenPublication"/> added (see <xref linkend="publishing_maven:publications"/>),
+                with each <apilink class="org.gradle.api.artifacts.repositories.MavenArtifactRepository"/> added (see <xref linkend="publishing_maven:repositories"/>).
+            </listitem>
+            <listitem>
+                Establishes a rule to automatically create a <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenLocal"/> task
+                for each <apilink class="org.gradle.api.publish.maven.MavenPublication"/> added (see<xref linkend="publishing_maven:publications"/>).
             </listitem>
         </itemizedlist>
-        <para>
-            The “<literal>publishing</literal>” plugin creates an extension on the project named “<literal>publishing</literal>” of type <apilink class="org.gradle.api.publish.PublishingExtension"/>.
-            This extension provides a container of named publications and a container of named repositories. The “<literal>maven-publish</literal>” works with
-            <apilink class="org.gradle.api.publish.maven.MavenPublication"/> publications and <apilink class="org.gradle.api.artifacts.repositories.MavenArtifactRepository" /> repositories.
-        </para>
     </section>
     <section id="publishing_maven:publications">
         <title>Publications</title>
+        <note>
+            <para>
+                If you are not familiar with project artifacts and configurations, you should read the <xref linkend="artifact_management" />
+                that introduces these concepts. This chapter also describes “publishing artifacts” using a different mechanism than what is
+                described in this chapter. The publishing functionality described here will eventually supersede that functionality.
+            </para>
+        </note>
         <para>
-            When the “<literal>maven-publish</literal>” plugin is applied together with the “<literal>java</literal>”plugin, it creates a single publication named “<literal>maven</literal>”.
-            This publication will publish the artifacts of the project and the associated POM file.
-        </para>
-        <para>
-            The attributes of the generated <literal>POM</literal> file will contain identity values derived from the following project properties:
+            Publication objects describe the structure/configuration of a publication to be created. Publications are published to repositories via tasks, and the
+            configuration of the publication object determines exactly what is published. All of the publications of a project are defined in the
+            <apilink class="org.gradle.api.publish.PublishingExtension" method="getPublications()" /> container. Each publication has a unique name within the project.
         </para>
-        <itemizedlist>
-            <listitem><literal>groupId</literal> - <apilink class="org.gradle.api.Project" method="getGroup()" /></listitem>
-            <listitem><literal>artifactId</literal> - <apilink class="org.gradle.api.Project" method="getName()" /></listitem>
-            <listitem><literal>version</literal> - <apilink class="org.gradle.api.Project" method="getVersion()" /></listitem>
-        </itemizedlist>
         <para>
-            Note that you can set the value of these project properties in your build script, with the exception of <literal>name</literal>.
+            For the “<literal>maven-publish</literal>” plugin to have any effect, a <apilink class="org.gradle.api.publish.maven.MavenPublication" /> must be added to the set of publications.
+            This publication determines which artifacts are actually published as well as the details included in the associated POM file.
+            A publication can be configured by adding components, customising artifacts, and by modifying the generated POM file directly.
         </para>
         <section>
-            <title>Modifying the published module descriptor</title>
+            <title>Publishing a Software Component</title>
+            <para>
+                The simplest way to publish a Gradle project to a Maven repository is to specify a <apilink class="org.gradle.api.component.SoftwareComponent"/> to publish.
+                The components presently available for publication are:
+            </para>
+            <table>
+                <title>Software Components</title>
+                <thead>
+                    <tr>
+                        <td>Name</td>
+                        <td>Provided By</td>
+                        <td>Artifacts</td>
+                        <td>Dependencies</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td><literal>java</literal></td>
+                    <td><xref linkend="java_plugin"/></td>
+                    <td>Generated jar file</td>
+                    <td>Dependencies from 'runtime' configuration</td>
+                </tr>
+                <tr>
+                    <td><literal>web</literal></td>
+                    <td><xref linkend="war_plugin"/></td>
+                    <td>Generated war file</td>
+                    <td>No dependencies</td>
+                </tr>
+            </table>
+            <para>
+                 In the following example, artifacts and runtime dependencies are taken from the `java` component, which is added by the <literal>Java Plugin</literal>.
+             </para>
+            <sample dir="maven-publish/quickstart" id="publishing_maven:publish-component" title="Adding a MavenPublication for a java component">
+                <sourcefile file="build.gradle" snippet="publish-component" />
+            </sample>
+        </section>
+        <section>
+            <title>Publishing custom artifacts</title>
             <para>
-                At times, the POM file generated from the project information will need to be tweaked before publishing. The <literal>maven-publish</literal>
+                It is also possible to explicitly configure artifacts to be included in the publication. Artifacts are commonly supplied as raw files, or as instances of
+                <apilink class="org.gradle.api.tasks.bundling.AbstractArchiveTask"/> (e.g. Jar, Zip).
+            </para>
+            <para>
+                For each custom artifact, it is possible to specify the <literal>extension</literal> and <literal>classifier</literal> values to use for publication. Note that
+                only one of the published artifacts can have an empty classifier, and all other artifacts must have a unique classifier/extension combination.
+            </para>
+            <para>
+                Configure custom artifacts as follows:
+            </para>
+            <sample dir="maven-publish/javaProject" id="publishing_maven:publish-custom-artifact" title="Adding additional artifact to a MavenPublication">
+                <sourcefile file="build.gradle" snippet="publish-custom-artifact" />
+            </sample>
+            <para>
+                See <apilink class="org.gradle.api.publish.maven.MavenPublication" /> for more detailed documentation on how artifacts can be customised.
+            </para>
+        </section>
+
+        <section>
+            <title>Identity values in the generated POM</title>
+            <para>
+                The attributes of the generated <literal>POM</literal> file will contain identity values derived from the following project properties:
+            </para>
+            <itemizedlist>
+                <listitem><literal>groupId</literal> - <apilink class="org.gradle.api.Project" method="getGroup()" /></listitem>
+                <listitem><literal>artifactId</literal> - <apilink class="org.gradle.api.Project" method="getName()" /></listitem>
+                <listitem><literal>version</literal> - <apilink class="org.gradle.api.Project" method="getVersion()" /></listitem>
+            </itemizedlist>
+            <tip>
+                Certain repositories will not be able to handle all supported characters.
+                For example, the ':' character cannot be used as an identifier when publishing to a filesystem-backed repository on Windows.
+            </tip>
+            <para>
+                Note that you can set the value of these project properties in your build script, with the exception of <literal>name</literal>.
+            </para>
+            <para>
+                Maven restricts 'groupId' and 'artifactId' to a limited character set (<literal>[A-Za-z0-9_\\-.]+</literal>) and Gradle enforces this restriction.
+                For 'version' (as well as artifact 'extension' and 'classifier'), Gradle will handle any valid Unicode character.
+            </para>
+            <para>
+                The only Unicode values that are explicitly prohibited are '<literal>\</literal>', '<literal>/</literal>' and any ISO control character.
+                Supplied values are validated early in publication.
+            </para>
+        </section>
+        <section>
+            <title>Modifying the generated POM</title>
+            <para>
+                At times, the POM file generated from the project information will need to be tweaked before publishing. The “<literal>maven-publish</literal>”
                 plugin provides a hook to allow such modification.
             </para>
-            <sample dir="maven/publish-new" id="publishing_maven:pom_modification" title="Modifying the POM file">
+            <sample dir="maven-publish/pomCustomization" id="publishing_maven:pom_modification" title="Modifying the POM file">
                 <sourcefile file="build.gradle" snippet="pom-modification" />
             </sample>
             <para>
-                In this example we are adding a 'description' element for the generated POM. With this hook, you can modify any aspect of the pom.
+                In this example we are adding a 'description' element for the generated POM. With this hook, you can modify any aspect of the POM.
                 For example, you could replace the version range for a dependency with the actual version used to produce the build.
-                This allows the pom file to describe how the module should be consumed, rather than be a description of how the module was built.
             </para>
             <para>
                 See <apilink class="org.gradle.api.publish.maven.MavenPom" method="withXml(org.gradle.api.Action)" /> for the relevant API reference documentation.
             </para>
+            <para>
+                It is possible to modify virtually any aspect of the created POM should you need to.
+                This means that it is also possible to modify the POM in such a way that it is no longer a valid
+                Maven Pom, so care must be taken when using this feature.
+            </para>
+            <para>
+                The identifier (groupId, artifactId, version) of the published module is an exception; these values cannot be modified in the POM using the `withXML` hook.
+            </para>
         </section>
     </section>
+
     <section id="publishing_maven:repositories">
         <title>Repositories</title>
         <para>
             Publications are published to repositories. The repositories to publish to are defined by the <apilink class="org.gradle.api.publish.PublishingExtension" method="getRepositories()" />
             container.
         </para>
-        <sample dir="maven/publish-new" id="publishing_maven:repositories" title="Declaring repositories to publish to">
+        <sample dir="maven-publish/quickstart" id="publishing_maven:repositories" title="Declaring repositories to publish to">
             <sourcefile file="build.gradle" snippet="repositories" />
         </sample>
         <para>
@@ -128,12 +215,15 @@
             task for each <apilink class="org.gradle.api.publish.maven.MavenPublication" /> and <apilink class="org.gradle.api.artifacts.repositories.MavenArtifactRepository" />
             combination in the <literal>publishing.publications</literal> and <literal>publishing.repositories</literal> containers respectively.
         </para>
-        <sample dir="maven/publish-new" id="publishingMavenPublishMinimal" title="A build to publish">
-            <sourcefile file="build.gradle" snippet="minimal-build" />
+        <para>
+            The created task is named using the pattern "<literal>publish«<emphasis>NAME OF PUBLICATION</emphasis>»PublicationTo«<emphasis>NAME OF REPOSITORY</emphasis>»Repository</literal>".
+        </para>
+        <sample dir="maven-publish/quickstart" id="publishingMavenPublishMinimal" title="Publishing a project to a Maven repository">
+            <sourcefile file="build.gradle"/>
             <output args="publish"/>
         </sample>
         <para>
-            So in this example a single <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository" /> task is be added, named '<literal>publishMavenPublicationToMavenRepository</literal>'.
+            So in this example a single <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository" /> task is be added, named '<literal>publishMavenJavaPublicationToMavenRepository</literal>'.
             This task is wired into the <literal>publish</literal> lifecycle task.
             Executing <literal>gradle publish</literal> builds the POM file and all of the artifacts to be published, and transfers them to the repository.
         </para>
@@ -142,19 +232,54 @@
         <title>Publishing to Maven Local</title>
         <para>
             For integration with a local Maven installation, it is sometimes useful to publish the module into the local .m2 repository. In Maven parlance, this is
-            referred to as 'installing' the module. The <literal>maven-publish</literal> plugin makes this easy to do by automatically creating a
+            referred to as 'installing' the module. The “<literal>maven-publish</literal>” plugin makes this easy to do by automatically creating a
             <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenLocal" /> task for each <apilink class="org.gradle.api.publish.maven.MavenPublication" />
             in the <literal>publishing.publications</literal> container. Each of these tasks is wired into the <literal>publishToMavenLocal</literal> lifecycle task.
             You do not need to have `mavenLocal` in your `publishing.repositories` section.
         </para>
-        <sample dir="maven/publish-new" id="publishingMavenPublishLocal" title="Publish a project to the Maven local repository">
-            <sourcefile file="build.gradle" snippet="minimal-build" />
+        <para>
+            The created task is named using the pattern "<literal>publish«<emphasis>NAME OF PUBLICATION</emphasis>»PublicationToMavenLocal</literal>".
+        </para>
+        <sample dir="maven-publish/quickstart" id="publishingMavenPublishLocal" title="Publish a project to the Maven local repository">
             <output args="publishToMavenLocal"/>
         </sample>
         <para>
-            So in this example a single <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenLocal" /> task is be added, named '<literal>publishMavenPublicationToMavenLocal</literal>'.
-            This task is wired into the <literal>publish</literal> lifecycle task.
+            So in this example you can see that a single <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenLocal" /> task is be added,
+            named '<literal>publishMavenJavaPublicationToMavenLocal</literal>'. This task is wired into the <literal>publishToMavenLocal</literal> lifecycle task.
             Executing <literal>gradle publishToMavenLocal</literal> builds the POM file and all of the artifacts to be published, and 'installs' them into the local Maven repository.
         </para>
     </section>
+    <section id="publishing_maven:generate-pom">
+        <title>Generating the POM file without publishing</title>
+        <para>
+            At times it is useful to generate a Maven POM file for a module without actually publishing. Since POM generation is performed by a separate task, it is very easy
+            to do so.
+        </para>
+        <para>
+            The task for generating the POM file is of type <apilink class="org.gradle.api.publish.maven.tasks.GenerateMavenPom"/>, and it is given a name based on the name
+            of the publication: "<literal>generatePomFileFor«<emphasis>NAME OF PUBLICATION</emphasis>»Publication</literal>". So in the below example where the publication is named
+            "<literal>mavenCustom</literal>",
+            the task will be named "<literal>generatePomFileForMavenCustomPublication</literal>".
+        </para>
+        <sample dir="maven-publish/pomCustomization" id="publishingMavenGeneratePom" title="Generate a POM file without publishing">
+            <sourcefile file="build.gradle" snippet="generate" />
+            <output args="generatePomFileForMavenCustomPublication"/>
+        </sample>
+        <para>
+            All details of the publishing model are still considered in POM generation, including <literal>components</literal>`, custom <literal>artifacts</literal>,
+            and any modifications made via <literal>pom.withXml</literal>.
+        </para>
+        <note>
+            <para>
+                The “<literal>maven-publish</literal>” plugin leverages some experimental support for late plugin configuration,
+                and any <literal>GenerateMavenPom</literal> tasks will not be constructed until the publishing extension is configured.
+                The simplest way to ensure that the publishing plugin is configured when you attempt to access the <literal>GenerateMavenPom</literal> task
+                is to place the access inside a <literal>publishing</literal> block, as the above example demonstrates.
+            </para>
+            <para>
+                The same applies to any attempt to access publication-specific tasks like <apilink class="org.gradle.api.publish.maven.tasks.PublishToMavenRepository"/>.
+                These tasks should be referenced from within a <literal>publishing</literal> block.
+            </para>
+        </note>
+    </section>
 </chapter>
diff --git a/subprojects/docs/src/docs/userguide/scalaPlugin.xml b/subprojects/docs/src/docs/userguide/scalaPlugin.xml
index 588d8a5..6f84347 100644
--- a/subprojects/docs/src/docs/userguide/scalaPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/scalaPlugin.xml
@@ -372,4 +372,20 @@
             invocations, reusing the same Scala compiler. This is expected to yield another significant speedup for Scala compilation.
         </para>
     </section>
+
+    <section>
+        <title>Eclipse Integration</title>
+        <para>
+            When the Eclipse plugin encounters a Scala project, it adds additional configuration to make the project work with Scala IDE out of the box.
+            Specifically, the plugin adds a Scala nature and dependency container.
+        </para>
+    </section>
+
+    <section>
+        <title>IntelliJ IDEA Integration</title>
+        <para>
+            When the IDEA plugin encounters a Scala project, it adds additional configuration to make the project work with IDEA out of the box. Specifically,
+            the plugin adds a Scala facet and a Scala compiler library that matches the Scala version on the project's class path.
+        </para>
+    </section>
 </chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/signingPlugin.xml b/subprojects/docs/src/docs/userguide/signingPlugin.xml
index 4f56ddd..f0cccc4 100644
--- a/subprojects/docs/src/docs/userguide/signingPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/signingPlugin.xml
@@ -173,7 +173,7 @@ gradle.taskGraph.whenReady { taskGraph ->
             <sourcefile file="build.gradle" snippet="sign-pom"/>
         </sample>
         <para>
-            When signing is not required and the pom cannot be signed due to insufficient configuration (i.e. no credentials for signing) then the
+            When signing is not required and the POM cannot be signed due to insufficient configuration (i.e. no credentials for signing) then the
             <literal>signPom()</literal> method will silently do nothing.
         </para>
     </section>
diff --git a/subprojects/docs/src/docs/userguide/sonarPlugin.xml b/subprojects/docs/src/docs/userguide/sonarPlugin.xml
index ce430bc..755e62e 100644
--- a/subprojects/docs/src/docs/userguide/sonarPlugin.xml
+++ b/subprojects/docs/src/docs/userguide/sonarPlugin.xml
@@ -16,10 +16,18 @@
 <chapter id="sonar_plugin">
     <title>The Sonar Plugin</title>
 
+    <note>
+        <para>
+            You may wish to use the new <link linkend="sonar_runner_plugin">Sonar Runner Plugin</link> instead of this plugin.
+            In particular, only the Sonar Runner plugin supports Sonar 3.4 and higher.
+        </para>
+    </note>
+
     <para>The Sonar plugin provides integration with <ulink url="http://www.sonarsource.org">Sonar</ulink>,
         a web-based platform for monitoring code quality. The plugin adds a <literal>sonarAnalyze</literal> task
-        that analyzes the project to which the plugin is applied and its subprojects. The results are stored in
-        the Sonar database. The plugin requires Sonar 2.9 or higher.
+        that analyzes the project to which the plugin is applied, as well as its subprojects. The results are stored in
+        the Sonar database. The plugin is based on the <ulink url="http://docs.codehaus.org/display/SONAR/Analyzing+with+Sonar+Runner">Sonar Runner</ulink>
+        and requires Sonar 2.11 or higher.
     </para>
 
     <para>
@@ -35,11 +43,14 @@
         <sample id="quickstart" dir="sonar/quickstart" title="Applying the Sonar plugin">
             <sourcefile file="build.gradle" snippet="apply-plugin"/>
         </sample>
-        <para>Unless Sonar is run locally and with default settings, it is also necessary to configure
+        <para>Unless Sonar is run locally and with default settings, it is necessary to configure
             connection settings for the Sonar server and database.</para>
         <sample id="quickstart" dir="sonar/quickstart" title="Configuring Sonar connection settings">
             <sourcefile file="build.gradle" snippet="connection-settings"/>
         </sample>
+        <para>
+            Alternatively, some or all connection settings can be set from the command line (see <xref linkend="sec:sonar_command_line_parameters" />).
+        </para>
         <para>Project settings determine how the project is going to be analyzed. The default configuration
             works well for analyzing standard Java projects and can be customized in many ways.</para>
         <sample id="quickstart" dir="sonar/quickstart" title="Configuring Sonar project settings">
@@ -56,8 +67,8 @@
     <section>
         <title>Analyzing Multi-Project Builds</title>
         <para>The Sonar plugin is capable of analyzing a whole project hierarchy at once. This yields a hierarchical view in the
-            Sonar web interface with aggregated metrics and the ability to drill down into subprojects. It is also faster and
-            less likely to run into out-of-memory problems than analyzing each project separately.
+            Sonar web interface with aggregated metrics and the ability to drill down into subprojects. It is also faster than
+            analyzing each project separately.
         </para>
         <para>
             To analyze a project hierarchy, the Sonar plugin needs to be applied to the top-most project of the hierarchy.
@@ -106,6 +117,22 @@
     </section>
 
     <section>
+        <title>Analyzing languages other than Java</title>
+        <para>
+            To analyze code written in a language other than Java, install the corresponding
+            <ulink url="http://www.sonarsource.com/products/plugins/languages/">Sonar plugin</ulink>, and set
+            <literal>sonar.project.language</literal> accordingly:
+        </para>
+        <sample id="advanced" dir="sonar/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. You can, however, set a different language for each project in a
+            multi-project build.
+        </para>
+    </section>
+
+    <section>
         <title>Setting Custom Sonar Properties</title>
         <para>
             Eventually, most configuration is passed to the Sonar code analyzer in the form of key-value pairs known as Sonar properties.
@@ -126,9 +153,58 @@
         </sample>
 
         <para>
-            The Sonar documentation provides a complete list of Sonar properties. (Note that most of these properties are already covered
-            by the plugin's object model.) For configuring third-party Sonar plugins, consult their documentation.
+            A list of available Sonar properties can be found in the <ulink url="http://docs.codehaus.org/display/SONAR/Analysis+Parameters">Sonar documentation</ulink>.
+            Note that for most of these properties, the Sonar plugin's object model has an equivalent property, and it isn't necessary to use a <code>withGlobalProperties</code>
+            or <code>withProjectProperties</code> hook. For configuring a third-party Sonar plugin, consult the plugin's documentation.
+        </para>
+    </section>
+
+    <section id="sec:sonar_command_line_parameters">
+        <title>Configuring Sonar Settings from the Command Line</title>
+        <para>
+            The following properties can alternatively be set from the command line, as task parameters of the <literal>sonarAnalyze</literal> task.
+            A task parameter will override any corresponding value set in the build script.
         </para>
+        <ul>
+            <li>
+                <literal>server.url</literal>
+            </li>
+            <li>
+                <literal>database.url</literal>
+            </li>
+            <li>
+                <literal>database.driverClassName</literal>
+            </li>
+            <li>
+                <literal>database.username</literal>
+            </li>
+            <li>
+                <literal>database.password</literal>
+            </li>
+            <li>
+                <literal>showSql</literal>
+            </li>
+            <li>
+                <literal>showSqlResults</literal>
+            </li>
+            <li>
+                <literal>verbose</literal>
+            </li>
+            <li>
+                <literal>forceAnalysis</literal>
+            </li>
+        </ul>
+        <para>
+            Here is a complete example:
+        </para>
+        <code>
+            gradle sonarAnalyze --server.url=http://sonar.mycompany.com --database.password=myPassword --verbose
+        </code>
+        <para>If you need to set other properties from the command line, you can use system properties to do so:</para>
+        <sample id="advanced" dir="sonar/advanced" title="Implementing custom command line properties">
+            <sourcefile file="build.gradle" snippet="custom-command-line-properties"/>
+        </sample>
+        <para>However, keep in mind that it is usually best to keep configuration in the build script and under source control.</para>
     </section>
 
     <section>
diff --git a/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml b/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml
new file mode 100644
index 0000000..b2a9100
--- /dev/null
+++ b/subprojects/docs/src/docs/userguide/sonarRunnerPlugin.xml
@@ -0,0 +1,320 @@
+<!--
+  ~ Copyright 2011 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="sonar_runner_plugin">
+    <title>The Sonar Runner Plugin</title>
+    <note>
+        <para>
+            The Sonar Runner plugin is <link linkend="sec:incubating_state">incubating</link>.
+        </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+Sonar+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>
+    <variablelist>
+        <varlistentry>
+            <term>Automatic provisioning of Sonar 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 download, setup, and maintain a Sonar 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>
+            </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>
+            </listitem>
+        </varlistentry>
+    </variablelist>
+
+    <section>
+        <title>Plugin Status and Compatibility</title>
+        <para>
+            The Sonar Runner plugin is the successor to the <link linkend="sonar_plugin">Sonar Plugin</link>. It is currently
+            <link linkend="sec:incubating_state">incubating</link>. The plugin is based on Sonar Runner 2.0, which makes it compatible
+            with Sonar 2.11 and higher. Unlike the Sonar plugin, the Sonar Runner plugin works fine with Sonar 3.4 and higher.
+        </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">
+            <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.
+            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.
+        </para>
+        <para>
+            Before executing the <literal>sonarRunner</literal> task, all tasks producing output to be analysed by Sonar 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.
+        </para>
+    </section>
+
+    <section>
+        <title>Configuring the Sonar Runner</title>
+        <para>The Sonar Runner plugin adds a <apilink class="org.gradle.api.sonar.runner.SonarRunner" /> extension to the project,
+            which allows 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.
+        </para>
+        <sample id="quickstart" dir="sonarRunner/quickstart" title="Configuring Sonar connection settings">
+            <sourcefile file="build.gradle" snippet="connection-settings"/>
+        </sample>
+        <para>
+            For a complete list of standard Sonar properties, consult the<ulink url="http://docs.codehaus.org/display/SONAR/Analysis+Parameters">Sonar documentation</ulink>.
+            If you happen to use additional Sonar plugins, consult their documentation.
+        </para>
+        <para>
+            Alternatively, Sonar properties can be set from the command line. See <xref linkend="sec:sonar_command_line_parameters" /> for more information.
+        </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 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.
+        </para>
+        <table>
+            <title>Gradle defaults for standard Sonar properties</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Gradle default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>sonar.projectKey</td>
+                <td>"$project.group:$project.name" (for root project of analysed hierarchy; left to Sonar Runner otherwise)</td>
+            </tr>
+            <tr>
+                <td>sonar.projectName</td>
+                <td>project.name</td>
+            </tr>
+            <tr>
+                <td>sonar.projectDescription</td>
+                <td>project.description</td>
+            </tr>
+            <tr>
+                <td>sonar.projectVersion</td>
+                <td>project.version</td>
+            </tr>
+            <tr>
+                <td>sonar.projectBaseDir</td>
+                <td>project.projectDir</td>
+            </tr>
+            <tr>
+                <td>sonar.working.directory</td>
+                <td>"$project.buildDir/sonar"</td>
+            </tr>
+            <tr>
+                <td>sonar.dynamicAnalysis</td>
+                <td>"reuseReports"</td>
+            </tr>
+        </table>
+        <table>
+            <title>Additional defaults when <literal>java-base</literal> plugin is applied</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Gradle default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>sonar.java.source</td>
+                <td>project.sourceCompatibility</td>
+            </tr>
+            <tr>
+                <td>sonar.java.target</td>
+                <td>project.targetCompatibility</td>
+            </tr>
+        </table>
+        <table>
+            <title>Additional defaults when <literal>java</literal> plugin is applied</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Gradle default</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>sonar.sources</td>
+                <td>sourceSets.main.allSource.srcDirs (filtered to only include existing directories)</td>
+            </tr>
+            <tr>
+                <td>sonar.tests</td>
+                <td>sourceSets.test.allSource.srcDirs (filtered to only include existing directories)</td>
+            </tr>
+            <tr>
+                <td>sonar.binaries</td>
+                <td>sourceSets.main.runtimeClasspath (filtered to only include directories)</td>
+            </tr>
+            <tr>
+                <td>sonar.libraries</td>
+                <td>sourceSets.main.runtimeClasspath (filtering to only include files; <literal>rt.jar</literal> added if necessary)</td>
+            </tr>
+            <tr>
+                <td>sonar.surefire.reportsPath</td>
+                <td>test.testResultsDir (if the directory exists)</td>
+            </tr>
+        </table>
+    </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
+            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.
+            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.
+        </para>
+        <sample id="multiProject" dir="sonarRunner/multiProject" title="Global configuration settings">
+            <sourcefile file="build.gradle" snippet="global-configuration-settings"/>
+        </sample>
+        <para>
+            Configuration shared between subprojects can be configured in a <literal>subprojects</literal> block.
+        </para>
+        <sample id="multiProject" dir="sonarRunner/multiProject" title="Shared configuration settings">
+            <sourcefile file="build.gradle" snippet="shared-configuration-settings"/>
+        </sample>
+        <para>
+            Project-specific information is configured in the <literal>sonarRunner</literal> block
+            of the corresponding project.
+        </para>
+        <sample id="multiProject" dir="sonarRunner/multiProject" title="Individual configuration settings">
+            <sourcefile file="build.gradle" snippet="individual-configuration-settings"/>
+        </sample>
+        <para>
+            The skip Sonar analysis for a particular subproject, set <literal>sonarRunner.skipProject</literal>.
+        </para>
+        <sample id="multiProject" dir="sonarRunner/multiProject" title="Skipping analysis of a project">
+            <sourcefile file="build.gradle" snippet="skip-project"/>
+        </sample>
+    </section>
+
+    <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
+             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>
+        <sample id="advanced" dir="sonarRunner/advanced" title="Analyzing custom source sets">
+            <sourcefile file="build.gradle" snippet="source-sets"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Analyzing languages other than Java</title>
+        <para>
+            To analyze code written in a language other than Java, install the corresponding
+            <ulink url="http://www.sonarsource.com/products/plugins/languages/">Sonar plugin</ulink>, and 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>
+        <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 to set new properties or override existing ones. Furthermore, all properties that have
+            been configured up to this point, including all properties preconfigured by Gradle, are available via the <literal>properties</literal>
+            accessor.
+        <para>
+        </para>
+            Entries in the <literal>properties</literal> map can be read and written with the usual Groovy syntax. To facilitate their manipulation,
+            values still have their "idiomatic" type (<classname>File</classname>, <classname>List</classname>, etc.). After the sonarProperties block
+            has been evaluated, values are converted to Strings as follows: Collection values are (recursively) converted to comma-separated Strings,
+            and all other values are converted by calling their <literal>toString()</literal> method.
+        </para>
+        <para>
+            Because the <literal>sonarProperties</literal> block is evaluated lazily, properties of Gradle's object model can be safely referenced
+            from within the block, without having to fear that they have not yet been set.
+        </para>
+    </section>
+
+    <section id="sec:sonar_command_line_parameters">
+        <title>Setting Sonar 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.
+            This can be useful when dealing with sensitive information (e.g. credentials), environment information, or for ad-hoc configuration.
+        </para>
+        <programlisting>
+            gradle sonarRunner -Dsonar.host.url=http://sonar.mycompany.com -Dsonar.jdbc.password=myPassword -Dsonar.verbose=true
+        </programlisting>
+        <note>
+            <para>
+                While certainly useful at times, we do recommend to keep the bulk of the configuration in a (versioned) build script, readily
+                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
+            analyzing a project hierarchy, values set via system properties apply to the root project of the analyzed hierarchy.
+        </para>
+    </section>
+
+    <section>
+        <title>Executing Sonar Runner in a separate process</title>
+        <para>
+            Depending on project size, the Sonar Runner may require a lot of memory. For this and other (mainly isolation) reasons,
+            it is desirable to execute the Sonar Runner in a separate process. This feature will be provided once Sonar Runner 2.1
+            has been released and adopted by the Sonar Runner plugin. Until then, the Sonar Runner is executed in the
+            main Gradle process. See <xref linkend="sec:gradle_configuration_properties"/> for how to manage memory settings for that process.
+        </para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Sonar Runner plugin adds the following tasks to the project.</para>
+        <table>
+            <title>Sonar Runner plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td><literal>sonarRunner</literal></td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.sonar.runner.SonarRunner"/></td>
+                <td>Analyzes a project hierarchy and stores the results in the Sonar database.</td>
+            </tr>
+        </table>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/docs/src/docs/userguide/standardPlugins.xml b/subprojects/docs/src/docs/userguide/standardPlugins.xml
index 6fd77eb..53ac590 100644
--- a/subprojects/docs/src/docs/userguide/standardPlugins.xml
+++ b/subprojects/docs/src/docs/userguide/standardPlugins.xml
@@ -340,6 +340,19 @@
                     </para>
                 </td>
             </tr>
+            <tr>
+                <td>
+                    <link linkend="buildDashboard_plugin">
+                        <literal>build-dashboard</literal>
+                    </link>
+                </td>
+                <td>reporting-base</td>
+                <td>-</td>
+                <td>
+                    <para>Generates build dashboard report.
+                    </para>
+                </td>
+            </tr>
         </table>
     </section>
     <section>
@@ -418,7 +431,7 @@
                 <td>
                     <para>Does the same as the eclipse plugin plus generates eclipse WTP (Web Tools Platform) configuration files.
                         After importing to eclipse your war/ear projects should be configured to work with WTP.
-                        See also<xref linkend="tutorial_java_projects"/>.
+                        See also <xref linkend="tutorial_java_projects"/>.
                     </para>
                 </td>
             </tr>
@@ -528,7 +541,37 @@
                 <td>
                     <para>Provides integration with the
                         <ulink url="http://www.sonarsource.org">Sonar</ulink>
-                        code quality platform.
+                        code quality platform. Superceeded by the <link linkend='sonar_runner_plugin'><literal>sonar-runner</literal></link> plugin.
+                    </para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Incubating software development plugins</title>
+        <para>These plugins provide help with your software development process.</para>
+        <table>
+            <title>Software development plugins</title>
+            <thead>
+                <tr>
+                    <td>Plugin Id</td>
+                    <td>Automatically applies</td>
+                    <td>Works with</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <link linkend='sonar_runner_plugin'>
+                        <literal>sonar-runner</literal>
+                    </link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Provides integration with the
+                        <ulink url="http://www.sonarsource.org">Sonar</ulink>
+                        code quality platform. Superceeds the <link linkend='sonar_plugin'><literal>sonar</literal></link> plugin.
                     </para>
                 </td>
             </tr>
diff --git a/subprojects/docs/src/docs/userguide/userguide.xml b/subprojects/docs/src/docs/userguide/userguide.xml
index 4b664b3..46a99e3 100755
--- a/subprojects/docs/src/docs/userguide/userguide.xml
+++ b/subprojects/docs/src/docs/userguide/userguide.xml
@@ -61,6 +61,7 @@
     <xi:include href='jdependPlugin.xml'/>
     <xi:include href='pmdPlugin.xml'/>
     <xi:include href='sonarPlugin.xml'/>
+    <xi:include href='sonarRunnerPlugin.xml'/>
     <xi:include href='osgi.xml'/>
     <xi:include href='eclipsePlugin.xml'/>
     <xi:include href='ideaPlugin.xml'/>
@@ -68,9 +69,11 @@
     <xi:include href='projectReports.xml'/>
     <xi:include href='announcePlugin.xml'/>
     <xi:include href='buildAnnouncementsPlugin.xml'/>
+    <xi:include href='distributionPlugin.xml'/>
     <xi:include href='applicationPlugin.xml'/>
     <xi:include href='javaLibraryDistributionPlugin.xml'/>
     <xi:include href='bootstrapPlugin.xml'/>
+    <xi:include href='buildDashboardPlugin.xml'/>
 	<xi:include href='depMngmt.xml'/>
     <xi:include href='artifactMngmt.xml'/>
     <xi:include href='mavenPlugin.xml'/>
diff --git a/subprojects/docs/src/docs/userguide/workingWithFiles.xml b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
index 10d6b23..6e7ef94 100644
--- a/subprojects/docs/src/docs/userguide/workingWithFiles.xml
+++ b/subprojects/docs/src/docs/userguide/workingWithFiles.xml
@@ -57,7 +57,7 @@
             <apilink class="org.gradle.api.Project" method="files(java.lang.Object...)"/> method. You can pass this method any number of
             objects, which are then converted into a set of <classname>File</classname> objects. The
             <literal>files()</literal> method accepts any type of object as its parameters. These are evaluated relative
-            to the project directory, as for the <literal>file()</literal> method, described in <xref linkend="sec:locating_files"/>.
+            to the project directory, as per the <literal>file()</literal> method, described in <xref linkend="sec:locating_files"/>.
             You can also pass collections, iterables, maps and arrays to the <literal>files()</literal> method. These are flattened
             and the contents converted to <classname>File</classname> instances.
         </para>
diff --git a/subprojects/docs/src/samples/buildDashboard/build.gradle b/subprojects/docs/src/samples/buildDashboard/build.gradle
new file mode 100755
index 0000000..f57e409
--- /dev/null
+++ b/subprojects/docs/src/samples/buildDashboard/build.gradle
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// START SNIPPET use-build-dashboard-plugin
+apply plugin: 'build-dashboard'
+// END SNIPPET use-build-dashboard-plugin
+apply plugin: 'codenarc'
+apply plugin: 'groovy'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy localGroovy()
+    testCompile 'junit:junit:4.11'
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/buildDashboard/config/codenarc/codenarc.xml b/subprojects/docs/src/samples/buildDashboard/config/codenarc/codenarc.xml
new file mode 100755
index 0000000..9842ff3
--- /dev/null
+++ b/subprojects/docs/src/samples/buildDashboard/config/codenarc/codenarc.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ Copyright 2010 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<ruleset xmlns="http://codenarc.org/ruleset/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://codenarc.org/ruleset/1.0 http://codenarc.org/ruleset-schema.xsd"
+         xsi:noNamespaceSchemaLocation="http://codenarc.org/ruleset-schema.xsd">
+    <ruleset-ref path='rulesets/naming.xml'/>
+</ruleset>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/buildDashboard/readme.xml b/subprojects/docs/src/samples/buildDashboard/readme.xml
new file mode 100755
index 0000000..1522dd9
--- /dev/null
+++ b/subprojects/docs/src/samples/buildDashboard/readme.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ Copyright 2013 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<sample>
+    <para>A project which uses the build-dashboard plugin</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/buildDashboard/src/main/groovy/org/gradle/sample/GroovyPerson.groovy b/subprojects/docs/src/samples/buildDashboard/src/main/groovy/org/gradle/sample/GroovyPerson.groovy
new file mode 100755
index 0000000..9e6f3e6
--- /dev/null
+++ b/subprojects/docs/src/samples/buildDashboard/src/main/groovy/org/gradle/sample/GroovyPerson.groovy
@@ -0,0 +1,5 @@
+package org.gradle.sample
+
+class GroovyPerson {
+    def String name
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/buildDashboard/src/test/groovy/org/gradle/sample/PersonTest.groovy b/subprojects/docs/src/samples/buildDashboard/src/test/groovy/org/gradle/sample/PersonTest.groovy
new file mode 100755
index 0000000..3d91d49
--- /dev/null
+++ b/subprojects/docs/src/samples/buildDashboard/src/test/groovy/org/gradle/sample/PersonTest.groovy
@@ -0,0 +1,13 @@
+package org.gradle.sample
+
+import org.junit.*
+import static org.junit.Assert.*
+
+class PersonTest {
+    @Test
+    def void canCreateAPerson() {
+        Person person = new Person()
+        person.name = 'Barry'
+        assertEquals('Barry', person.name)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/dependencies/build.gradle b/subprojects/docs/src/samples/dependencies/build.gradle
deleted file mode 100644
index ee23c00..0000000
--- a/subprojects/docs/src/samples/dependencies/build.gradle
+++ /dev/null
@@ -1,178 +0,0 @@
-import org.apache.ivy.plugins.resolver.FileSystemResolver
-
-group = 'sealife'
-
-project(':atlantic') {
-    apply plugin: 'java'
-    configurations {
-        other
-    }
-    dependencies {
-        other "sea.fish:herring:1.0", files("$rootDir/lib/selfResolving1.jar")
-    }
-}
-
-project(':northSea') {
-    apply plugin: 'java'
-    configurations {
-        other
-    }
-    dependencies {
-        other "sea.fish:squid:1.0", files("$rootDir/lib/selfResolving2.jar"), project(path: ':atlantic', configuration: 'other')
-    }
-}
-
-repositories {
-    add(new FileSystemResolver()) {
-        name = "repo"
-        addArtifactPattern("$rootDir/repo/[organization]/[module](-[classifier])-[revision].[ext]")
-        addIvyPattern("$rootDir/repo/[organization]/ivy-[module](-[classifier])-[revision].xml")
-        checkmodified = true
-    }
-}
-
-configurations {
-    oneDepWithNoTransitives
-    oneDepWithTransitives
-    twoDepsWithNoTransitives
-    twoDepsWithTransitives
-    twoDepsWithOneAsTransitive
-    twoDepsWithVersionConflicts
-    twoDepsWithDifferentDependencyConfigurations
-    depWithMultipleConfigurations
-    extended
-    extending.extendsFrom extended
-    extendingWithDifferentConfiguration.extendsFrom extended
-    selfResolving
-    mixed.extendsFrom selfResolving, twoDepsWithTransitives
-    subprojectAtlantic
-    subprojectNorthSea
-    classifier
-    extendingClassifier.extendsFrom classifier
-    customArtifacts
-}
-
-dependencies {
-    oneDepWithNoTransitives "sea.fish:herring:1.0"
-    oneDepWithTransitives "sea.fish:tuna:1.0"
-    twoDepsWithNoTransitives "sea.fish:herring:1.0", "sea.mammals:seal:1.0"
-    twoDepsWithTransitives "sea.mammals:orca:1.0", "sea.fish:tuna:1.0"
-    twoDepsWithOneAsTransitive "sea.fish:shark:1.0", "sea.fish:tuna:1.0"
-    twoDepsWithVersionConflicts "sea.fish:shark:1.0", "sea.mammals:orca:1.0"
-    twoDepsWithDifferentDependencyConfigurations "sea.fish:shark:1.0", "sea.fish:billfish:1.0"
-    depWithMultipleConfigurations group: 'sea.fish', name: 'tuna', version: '1.0', configuration: 'default'
-    depWithMultipleConfigurations group: 'sea.fish', name: 'tuna', version: '1.0', configuration: 'specialWaters'
-    extended "sea.fish:tuna:1.0"
-    extending "sea.mammals:orca:1.0"
-    extendingWithDifferentConfiguration group: 'sea.fish', name: 'tuna', version: '1.0', configuration: 'specialWaters'
-    extendingWithDifferentConfiguration "sea.mammals:seal:1.0"
-    selfResolving files("$rootDir/lib/selfResolving1.jar"), files("$projectDir/someDir")
-    subprojectAtlantic project(path: ':atlantic', configuration: 'other')
-    subprojectNorthSea project(path: ':northSea', configuration: 'other')
-    classifier "sea.fish:herring:1.0"
-    classifier('sea.mammals:dolphin:1.0') {
-	    artifact {
-	        name = 'dolphin'
-	        type = 'jar'
-	        extension = 'jar'
-	        classifier = 'oceanic'
-        }
-        artifact {
-	        name = 'dolphin'
-	        type = 'jar'
-	        extension = 'jar'
-	        classifier = 'river'
-        }
-	}
-    // A resolve should return both, tuna and custom-tuna. A files filtering on shark or tuna either or.
-    // But Ivy does not return the correct resolve report. In fact if you produce an equivalent ivy.xml, only custom-tuna is returned, not tuna-1.0.jar.
-    // Therefore we don't test the resolve of customArtifacts
-    customArtifacts "sea.fish:shark:1.0"
-    customArtifacts("sea.fish:tuna:1.0") {
-        artifact {
-	        name = 'custom-tuna'
-	        type = 'jar'
-	        extension = 'jar'
-            url = "file:///$rootDir/lib/selfResolving1.jar"
-        }
-    }
-
-}
-
-task test(dependsOn: [configurations.subprojectAtlantic, configurations.subprojectNorthSea]) << {
-    assertCorrectFilesForCompleteConfigurations()
-    assertFilesForDependencySubsets()
-    assertFilesForConfigurationCopies()
-}
-
-def assertCorrectFilesForCompleteConfigurations() {
-    ext.expectedResults = [
-            oneDepWithNoTransitives: ['herring-1.0.jar'] as Set,
-            oneDepWithTransitives: ['tuna-1.0.jar', 'herring-1.0.jar'] as Set,
-            twoDepsWithNoTransitives: ['seal-1.0.jar', 'herring-1.0.jar'] as Set,
-            twoDepsWithTransitives: ['tuna-1.0.jar', 'orca-1.0.jar', 'seal-1.0.jar', 'herring-1.0.jar'] as Set,
-            twoDepsWithOneAsTransitive: ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'] as Set,
-            twoDepsWithVersionConflicts: ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'orca-1.0.jar', 'herring-1.0.jar'] as Set,
-            twoDepsWithDifferentDependencyConfigurations: ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'billfish-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar'] as Set,
-            depWithMultipleConfigurations: ['tuna-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar'] as Set,
-            extendingWithDifferentConfiguration: ['tuna-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar', 'seal-1.0.jar'] as Set,
-            extending: ['tuna-1.0.jar', 'herring-1.0.jar', 'seal-1.0.jar', 'orca-1.0.jar'] as Set,
-            selfResolving: ['selfResolving1.jar', 'someDir'] as Set,
-            mixed: ['tuna-1.0.jar', 'orca-1.0.jar', 'seal-1.0.jar', 'herring-1.0.jar', 'selfResolving1.jar', 'someDir'] as Set,
-            subprojectAtlantic: ['selfResolving1.jar', 'herring-1.0.jar'] as Set,
-            subprojectNorthSea: ['selfResolving1.jar', 'herring-1.0.jar', 'selfResolving2.jar', 'squid-1.0.jar'] as Set,
-            classifier: ['dolphin-oceanic-1.0.jar', 'dolphin-river-1.0.jar', "herring-1.0.jar"] as Set,
-            extendingClassifier: ['dolphin-oceanic-1.0.jar', 'dolphin-river-1.0.jar', "herring-1.0.jar"] as Set
-    ]
-    expectedResults.each { configurationName, expectedFileNames ->
-        Set resolvedFileNames = configurations[configurationName].files.collect { it.name }
-        assert expectedFileNames == resolvedFileNames
-    }
-}
-
-def assertFilesForDependencySubsets() {
-    assertSubsetFiles(configurations.oneDepWithNoTransitives, { dep -> dep.name == 'herring' }, ['herring-1.0.jar'])
-    assertSubsetFiles(configurations.oneDepWithTransitives, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.twoDepsWithNoTransitives, { dep -> dep.name == 'herring' }, ['herring-1.0.jar'])
-    assertSubsetFiles(configurations.twoDepsWithTransitives, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.twoDepsWithOneAsTransitive, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.twoDepsWithOneAsTransitive, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.twoDepsWithVersionConflicts, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.twoDepsWithVersionConflicts, { dep -> dep.name == 'orca' }, ['orca-1.0.jar', 'seal-2.0.jar'])
-    assertSubsetFiles(configurations.twoDepsWithDifferentDependencyConfigurations, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.twoDepsWithDifferentDependencyConfigurations, { dep -> dep.name == 'billfish' }, ['tuna-1.0.jar', 'billfish-1.0.jar', 'squid-1.0.jar'])
-    assertSubsetFiles(configurations.depWithMultipleConfigurations, { dep -> dep.configuration == 'default' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.depWithMultipleConfigurations, { dep -> dep.configuration == 'specialWaters' }, ['tuna-1.0.jar', 'squid-1.0.jar'])
-    assertSubsetFiles(configurations.extendingWithDifferentConfiguration, { dep -> dep.name != 'seal' }, ['tuna-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar'])
-    assertSubsetFiles(configurations.extendingWithDifferentConfiguration, { dep -> dep.name == 'tuna' && dep.configuration == 'default' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.extending, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.selfResolving, { dep -> dep.source.singleFile.name == 'someDir' }, ['someDir'])
-    assertSubsetFiles(configurations.mixed, { dep ->
-        dep instanceof org.gradle.api.artifacts.SelfResolvingDependency || dep.name == 'tuna' }, ['someDir', 'selfResolving1.jar', 'tuna-1.0.jar', 'herring-1.0.jar'])
-    assertSubsetFiles(configurations.classifier, { dep -> dep.name == 'dolphin' }, ['dolphin-oceanic-1.0.jar', 'dolphin-river-1.0.jar'])
-    assertSubsetFiles(configurations.extendingClassifier, { dep -> dep.name == 'dolphin' }, ['dolphin-oceanic-1.0.jar', 'dolphin-river-1.0.jar'])
-}
-
-def assertSubsetFiles(configuration, spec, expectedFileNames) {
-    Set resolvedFileNames = configuration.files(spec).collect { it.name }
-    assert expectedFileNames as Set == resolvedFileNames
-}
-
-def assertFilesForConfigurationCopies() {
-    assertConfigurationCopyFiles(configurations.oneDepWithNoTransitives, { dep -> true }, ['herring-1.0.jar'])
-    assertConfigurationCopyFiles(configurations.oneDepWithTransitives, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
-    assertConfigurationCopyFiles(configurations.twoDepsWithVersionConflicts, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
-    assertConfigurationCopyFiles(configurations.twoDepsWithVersionConflicts, { dep -> dep.name == 'orca' }, ['orca-1.0.jar', 'seal-1.0.jar'])
-    assertConfigurationCopyFiles(configurations.extending, { dep -> true }, ['seal-1.0.jar', 'orca-1.0.jar'])
-    assertConfigurationCopyFiles(configurations.extending, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'], true)
-    assertConfigurationCopyFiles(configurations.selfResolving, { dep -> true }, ['selfResolving1.jar', 'someDir'])
-    assertConfigurationCopyFiles(configurations.selfResolving, { dep -> dep.source.singleFile.name == 'someDir' }, ['someDir'])
-    assertConfigurationCopyFiles(configurations.mixed, { dep ->
-        dep instanceof org.gradle.api.artifacts.SelfResolvingDependency || dep.name == 'tuna' }, ['someDir', 'selfResolving1.jar', 'tuna-1.0.jar', 'herring-1.0.jar'], true)
-}
-
-def assertConfigurationCopyFiles(configuration, spec, expectedFileNames, recursive = false) {
-    String method = recursive ? 'copyRecursive' : 'copy'
-    Set resolvedFileNames = configuration."$method"(spec).collect { it.name }
-    assert expectedFileNames as Set == resolvedFileNames
-}
diff --git a/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-billfish-1.0.xml b/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-billfish-1.0.xml
deleted file mode 100644
index 7dd15d3..0000000
--- a/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-billfish-1.0.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<ivy-module version="1.0">
-    <info
-        organisation="sea.fish"
-        module="billfish"
-        revision="1.0"/>
-    <configurations>
-        <conf name="default"/>
-    </configurations>
-    <dependencies>
-        <dependency org="sea.fish" name="tuna" rev="1.0" conf="default->specialWaters"/>
-    </dependencies>
-</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-shark-1.0.xml b/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-shark-1.0.xml
deleted file mode 100644
index 1bfcd8d..0000000
--- a/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-shark-1.0.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<ivy-module version="1.0">
-    <info
-        organisation="sea.fish"
-        module="shark"
-        revision="1.0"/>
-    <configurations>
-        <conf name="default" extends="runtime"/>
-		<conf name="runtime"/>
-    </configurations>
-    <dependencies>
-        <dependency org="sea.mammals" name="seal" rev="2.0" conf="runtime->unknown(*)"/>
-        <dependency org="sea.fish" name="tuna" rev="1.0" conf="runtime->default"/>
-    </dependencies>
-</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-tuna-1.0.xml b/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-tuna-1.0.xml
deleted file mode 100644
index 8dfc0ee..0000000
--- a/subprojects/docs/src/samples/dependencies/repo/sea.fish/ivy-tuna-1.0.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<ivy-module version="1.0">
-    <info
-        organisation="sea.fish"
-        module="tuna"
-        revision="1.0"/>
-    <configurations>
-        <conf name="default"/>
-        <conf name="specialWaters"/>
-    </configurations>
-    <dependencies>
-        <dependency org="sea.fish" name="herring" rev="1.0" conf="default->default"/>
-        <dependency org="sea.fish" name="squid" rev="1.0" conf="specialWaters->default"/>
-    </dependencies>
-</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/dependencies/repo/sea.mammals/ivy-orca-1.0.xml b/subprojects/docs/src/samples/dependencies/repo/sea.mammals/ivy-orca-1.0.xml
deleted file mode 100644
index 7f0f277..0000000
--- a/subprojects/docs/src/samples/dependencies/repo/sea.mammals/ivy-orca-1.0.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<ivy-module version="1.0">
-    <info
-        organisation="sea.mammals"
-        module="orca"
-        revision="1.0"/>
-    <configurations>
-        <conf name="default"/>
-    </configurations>
-    <dependencies>
-        <dependency org="sea.mammals" name="seal" rev="1.0" conf="default"/>
-    </dependencies>
-</ivy-module>
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/dependencies/settings.gradle b/subprojects/docs/src/samples/dependencies/settings.gradle
deleted file mode 100644
index 76ebcde..0000000
--- a/subprojects/docs/src/samples/dependencies/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include 'atlantic', 'northSea'
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ivy-publish/descriptor-customization/build.gradle b/subprojects/docs/src/samples/ivy-publish/descriptor-customization/build.gradle
new file mode 100644
index 0000000..ef79270
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/descriptor-customization/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'ivy-publish'
+
+group = 'org.gradle.sample'
+version = '1.0'
+
+publishing {
+// START SNIPPET customize-descriptor
+    publications {
+        ivyCustom(IvyPublication) {
+            descriptor.withXml {
+                asNode().info[0].appendNode('description', 'A demonstration of ivy descriptor customization')
+            }
+        }
+    }
+// END SNIPPET customize-descriptor
+    repositories {
+        ivy {
+            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+        }
+    }
+}
+// START SNIPPET generate
+publishing {
+    generateIvyCustomIvyModuleDescriptor {
+        destination = file("$buildDir/generated-ivy.xml")
+    }
+}
+// END SNIPPET generate
+
+
diff --git a/subprojects/docs/src/samples/ivy-publish/java-multi-project/build.gradle b/subprojects/docs/src/samples/ivy-publish/java-multi-project/build.gradle
new file mode 100644
index 0000000..0d833d9
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/java-multi-project/build.gradle
@@ -0,0 +1,61 @@
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'ivy-publish'
+
+    version = '1.0'
+    group = 'org.gradle.sample'
+
+    repositories {
+        mavenCentral()
+    }
+    // START SNIPPET publish-custom-artifact
+    task sourceJar(type: Jar) {
+        from sourceSets.main.java
+        classifier "source"
+    }
+    // END SNIPPET publish-custom-artifact
+}
+
+project(":project1") {
+    description = "The first project"
+
+    dependencies {
+       compile 'junit:junit:4.11', project(':project2')
+    }
+}
+
+project(":project2") {
+    description = "The second project"
+
+    dependencies {
+       compile 'commons-collections:commons-collections:3.1'
+    }
+}
+
+subprojects {
+// START SNIPPET publish-custom-artifact
+    publishing {
+// END SNIPPET publish-custom-artifact
+        repositories {
+            ivy {
+                url "file://${rootProject.buildDir}/repo" // change to point to your repo, e.g. http://my.org/repo
+            }
+        }
+// START SNIPPET publish-custom-artifact
+        publications {
+            ivy(IvyPublication) {
+                from components.java
+                artifact(sourceJar) {
+                    type "source"
+                    conf "runtime"
+                }
+// END SNIPPET publish-custom-artifact
+                descriptor.withXml {
+                    asNode().info[0].appendNode('description', description)
+                }
+// START SNIPPET publish-custom-artifact
+            }
+        }
+    }
+// END SNIPPET publish-custom-artifact
+}
diff --git a/subprojects/docs/src/samples/ivy-publish/java-multi-project/output-ivy.xml b/subprojects/docs/src/samples/ivy-publish/java-multi-project/output-ivy.xml
new file mode 100644
index 0000000..3d2c271
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/java-multi-project/output-ivy.xml
@@ -0,0 +1,21 @@
+<!-- This file is an example of the Ivy module descriptor that this build will produce -->
+<!-- START SNIPPET content -->
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0">
+  <info organisation="org.gradle.sample" module="project1" revision="1.0" status="integration" publication="«PUBLICATION-TIME-STAMP»">
+    <description>The first project</description>
+  </info>
+  <configurations>
+    <conf name="default" visibility="public" extends="runtime"/>
+    <conf name="runtime" visibility="public"/>
+  </configurations>
+  <publications>
+    <artifact name="project1" type="jar" ext="jar" conf="runtime"/>
+    <artifact name="project1" type="source" ext="jar" conf="runtime" m:classifier="source" xmlns:m="http://ant.apache.org/ivy/maven"/>
+  </publications>
+  <dependencies>
+    <dependency org="junit" name="junit" rev="4.11" conf="runtime->default"/>
+    <dependency org="org.gradle.sample" name="project2" rev="1.0" conf="runtime->default"/>
+  </dependencies>
+</ivy-module>
+<!-- END SNIPPET content -->
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ivy-publish/java-multi-project/settings.gradle b/subprojects/docs/src/samples/ivy-publish/java-multi-project/settings.gradle
new file mode 100644
index 0000000..ca04044
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/java-multi-project/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'ivy-publish-java'
+include 'project1', 'project2'
diff --git a/subprojects/docs/src/samples/ivy-publish/quickstart/build.gradle b/subprojects/docs/src/samples/ivy-publish/quickstart/build.gradle
new file mode 100644
index 0000000..0d62680
--- /dev/null
+++ b/subprojects/docs/src/samples/ivy-publish/quickstart/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'java'
+// START SNIPPET use-plugin
+apply plugin: 'ivy-publish'
+// END SNIPPET use-plugin
+
+group = 'org.gradle.sample'
+version = '1.0'
+
+publishing {
+// START SNIPPET publish-component
+    publications {
+        ivyJava(IvyPublication) {
+            from components.java
+        }
+    }
+// END SNIPPET publish-component
+// START SNIPPET repositories
+    repositories {
+        ivy {
+            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+        }
+    }
+// END SNIPPET repositories
+}
+
diff --git a/subprojects/docs/src/samples/ivypublish-new/build.gradle b/subprojects/docs/src/samples/ivypublish-new/build.gradle
deleted file mode 100644
index 68c0d3a..0000000
--- a/subprojects/docs/src/samples/ivypublish-new/build.gradle
+++ /dev/null
@@ -1,65 +0,0 @@
-// START SNIPPET input
-apply plugin: 'java'
-// START SNIPPET use-plugin
-apply plugin: 'ivy-publish'
-// END SNIPPET use-plugin
-
-version = '1.0'
-group = 'org.gradle.test'
-
-dependencies {
-   compile 'junit:junit:4.11', project(':subproject')
-}
-
-repositories {
-    mavenCentral()
-}
-
-task sourceJar(type: Jar) {
-    baseName = 'ivypublishSource'
-    from sourceSets.main.java
-    classifier = 'src'
-}
-
-artifacts {
-    archives sourceJar
-}
-
-// END SNIPPET input
-// START SNIPPET repositories
-// START SNIPPET input
-// START SNIPPET descriptor-mod
-publishing {
-// END SNIPPET descriptor-mod
-// END SNIPPET input
-    repositories {
-        ivy {
-            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
-            credentials {
-                username 'user1'
-                password 'secret'
-            }
-        }
-    }
-// END SNIPPET repositories
-// START SNIPPET input
-// START SNIPPET descriptor-mod
-    publications {
-        ivy {
-            descriptor {
-                withXml {
-                    asNode().dependencies.dependency.find { it. at org == "junit" }. at rev = "4.10"
-                }
-            }
-        }
-    }
-// START SNIPPET repositories
-}
-// END SNIPPET repositories
-// END SNIPPET descriptor-mod
-// END SNIPPET input
-// START SNIPPET generate
-generateIvyModuleDescriptor {
-    destination = file('generated-ivy.xml')
-}
-// END SNIPPET generate
diff --git a/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml b/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml
deleted file mode 100644
index 89b2c6e..0000000
--- a/subprojects/docs/src/samples/ivypublish-new/output-ivy.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<!-- This file is an example of the Ivy module descriptor that this build will produce -->
-<!-- START SNIPPET content -->
-<?xml version="1.0" encoding="UTF-8"?>
-<ivy-module version="2.0">
-  <info organisation="org.gradle.test" module="ivypublish" revision="1.0" status="integration" publication="«PUBLICATION-TIME-STAMP»"/>
-  <configurations>
-    <conf name="archives" visibility="public" description="Configuration for archive artifacts."/>
-    <conf name="compile" visibility="private" description="Classpath for compiling the main sources."/>
-    <conf name="default" visibility="public" description="Configuration for default artifacts." extends="runtime"/>
-    <conf name="runtime" visibility="private" description="Classpath for running the compiled main classes." extends="compile"/>
-  </configurations>
-  <publications>
-    <artifact name="ivypublish" type="jar" ext="jar" conf="archives,runtime"/>
-    <artifact name="ivypublishSource" type="jar" ext="jar" conf="archives" m:classifier="src" xmlns:m="http://ant.apache.org/ivy/maven"/>
-  </publications>
-  <dependencies>
-    <dependency org="junit" name="junit" rev="4.10" conf="compile->default"/>
-    <dependency org="ivypublish" name="subproject" rev="unspecified" conf="compile->default"/>
-  </dependencies>
-</ivy-module>
-<!-- END SNIPPET content -->
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ivypublish-new/settings.gradle b/subprojects/docs/src/samples/ivypublish-new/settings.gradle
deleted file mode 100644
index af72e93..0000000
--- a/subprojects/docs/src/samples/ivypublish-new/settings.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-rootProject.name = 'ivypublish'
-include 'subproject'
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/ivypublish-new/src/main/java/org/gradle/SomeClass.java b/subprojects/docs/src/samples/ivypublish-new/src/main/java/org/gradle/SomeClass.java
deleted file mode 100644
index 4360a7e..0000000
--- a/subprojects/docs/src/samples/ivypublish-new/src/main/java/org/gradle/SomeClass.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package org.gradle;
-
-public class SomeClass {
-}
diff --git a/subprojects/docs/src/samples/maven-publish/javaProject/build.gradle b/subprojects/docs/src/samples/maven-publish/javaProject/build.gradle
new file mode 100644
index 0000000..d4bc19b
--- /dev/null
+++ b/subprojects/docs/src/samples/maven-publish/javaProject/build.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'java'
+apply plugin: 'maven-publish'
+
+group = 'org.gradle.sample'
+version = '1.0'
+
+dependencies {
+   compile 'commons-collections:commons-collections:3.0'
+}
+
+repositories {
+    mavenCentral()
+}
+
+// START SNIPPET publish-custom-artifact
+task sourceJar(type: Jar) {
+    from sourceSets.main.allJava
+}
+
+publishing {
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
+
+            artifact sourceJar {
+                classifier "sources"
+            }
+        }
+    }
+// END SNIPPET publish-custom-artifact
+    repositories {
+        maven {
+            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+        }
+    }
+// START SNIPPET publish-custom-artifact
+}
+// END SNIPPET publish-custom-artifact
+
+
diff --git a/subprojects/docs/src/samples/ivypublish-new/subproject/build.gradle b/subprojects/docs/src/samples/maven-publish/javaProject/subproject/build.gradle
similarity index 100%
rename from subprojects/docs/src/samples/ivypublish-new/subproject/build.gradle
rename to subprojects/docs/src/samples/maven-publish/javaProject/subproject/build.gradle
diff --git a/subprojects/docs/src/samples/ivypublish-new/subproject/src/main/java/org/gradle/shared/Person.java b/subprojects/docs/src/samples/maven-publish/javaProject/subproject/src/main/java/org/gradle/shared/Person.java
similarity index 100%
rename from subprojects/docs/src/samples/ivypublish-new/subproject/src/main/java/org/gradle/shared/Person.java
rename to subprojects/docs/src/samples/maven-publish/javaProject/subproject/src/main/java/org/gradle/shared/Person.java
diff --git a/subprojects/docs/src/samples/maven-publish/pomCustomization/build.gradle b/subprojects/docs/src/samples/maven-publish/pomCustomization/build.gradle
new file mode 100644
index 0000000..b0fc28b
--- /dev/null
+++ b/subprojects/docs/src/samples/maven-publish/pomCustomization/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'maven-publish'
+
+group = 'org.gradle.sample'
+version = '1.0'
+
+publishing {
+// START SNIPPET pom-modification
+    publications {
+        mavenCustom(MavenPublication) {
+            pom.withXml {
+                asNode().appendNode('description', 'A demonstration of maven pom customisation')
+            }
+        }
+    }
+// END SNIPPET pom-modification
+    repositories {
+        maven {
+            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+        }
+    }
+}
+// START SNIPPET generate
+publishing {
+    generatePomFileForMavenCustomPublication {
+        destination = file("$buildDir/generated-pom.xml")
+    }
+}
+// END SNIPPET generate
+
+
diff --git a/subprojects/docs/src/samples/maven-publish/quickstart/build.gradle b/subprojects/docs/src/samples/maven-publish/quickstart/build.gradle
new file mode 100644
index 0000000..853a142
--- /dev/null
+++ b/subprojects/docs/src/samples/maven-publish/quickstart/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'java'
+// START SNIPPET use-plugin
+apply plugin: 'maven-publish'
+// END SNIPPET use-plugin
+
+group = 'org.gradle.sample'
+version = '1.0'
+
+publishing {
+// START SNIPPET publish-component
+    publications {
+        mavenJava(MavenPublication) {
+            from components.java
+        }
+    }
+// END SNIPPET publish-component
+// START SNIPPET repositories
+    repositories {
+        maven {
+            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
+        }
+    }
+// END SNIPPET repositories
+}
+
diff --git a/subprojects/docs/src/samples/maven/publish-new/src/main/java/org/MyClass.java b/subprojects/docs/src/samples/maven-publish/quickstart/src/main/java/org/MyClass.java
similarity index 100%
rename from subprojects/docs/src/samples/maven/publish-new/src/main/java/org/MyClass.java
rename to subprojects/docs/src/samples/maven-publish/quickstart/src/main/java/org/MyClass.java
diff --git a/subprojects/docs/src/samples/maven/publish-new/build.gradle b/subprojects/docs/src/samples/maven/publish-new/build.gradle
deleted file mode 100644
index b0288bc..0000000
--- a/subprojects/docs/src/samples/maven/publish-new/build.gradle
+++ /dev/null
@@ -1,39 +0,0 @@
-// START SNIPPET minimal-build
-apply plugin: 'java'
-// START SNIPPET use-plugin
-apply plugin: 'maven-publish'
-// END SNIPPET use-plugin
-
-group = 'org.gradle.sample'
-version = '1.0'
-
-dependencies {
-   compile 'commons-collections:commons-collections:3.0'
-}
-
-repositories {
-    mavenCentral()
-}
-
-publishing {
-// START SNIPPET repositories
-    repositories {
-        maven {
-            url "file://$buildDir/repo" // change to point to your repo, e.g. http://my.org/repo
-        }
-    }
-// END SNIPPET repositories
-// END SNIPPET minimal-build
-// START SNIPPET pom-modification
-    publications {
-        maven {
-            pom.withXml {
-                asNode().appendNode('description', 'A demonstration of maven pom customisation')
-            }
-        }
-    }
-// END SNIPPET pom-modification
-// START SNIPPET minimal-build
-}
-// END SNIPPET minimal-build
-
diff --git a/subprojects/docs/src/samples/sonar/advanced/build.gradle b/subprojects/docs/src/samples/sonar/advanced/build.gradle
index 898fd2a..7509af2 100644
--- a/subprojects/docs/src/samples/sonar/advanced/build.gradle
+++ b/subprojects/docs/src/samples/sonar/advanced/build.gradle
@@ -13,14 +13,30 @@ sonar.project {
 }
 // END SNIPPET source-sets
 
+// START SNIPPET languages
+sonar.project {
+    language = "grvy" // set language to Groovy
+}
+// END SNIPPET languages
+
 // START SNIPPET global-properties
 sonar.withGlobalProperties { props ->
     props["some.global.property"] = "some value"
+    // non-String values are automatically converted to Strings
+    props["other.global.property"] = ["foo", "bar", "baz"]
 }
 // END SNIPPET global-properties
 
 // START SNIPPET project-properties
 sonar.project.withProjectProperties { props ->
     props["some.project.property"] = "some value"
+    // non-String values are automatically converted to Strings
+    props["other.project.property"] = ["foo", "bar", "baz"]
+}
+// END SNIPPET project-properties
+
+// START SNIPPET custom-command-line-properties
+sonar.project {
+    language = System.getProperty("sonar.language", "java")
 }
-// END SNIPPET project-properties
\ No newline at end of file
+// END SNIPPET custom-command-line-properties
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/sonarRunner/advanced/build.gradle b/subprojects/docs/src/samples/sonarRunner/advanced/build.gradle
new file mode 100644
index 0000000..fb6c347
--- /dev/null
+++ b/subprojects/docs/src/samples/sonarRunner/advanced/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: "java"
+apply plugin: "sonar-runner"
+
+sourceSets {
+    custom
+    integTest
+}
+
+// START SNIPPET source-sets
+sonarRunner {
+    sonarProperties {
+        properties["sonar.sources"] += sourceSets.custom.allSource.srcDirs
+        properties["sonar.tests"] += sourceSets.integTest.allSource.srcDirs
+    }
+}
+// END SNIPPET source-sets
+
+// START SNIPPET languages
+sonarRunner {
+    sonarProperties {
+        property "sonar.language", "grvy" // set language to Groovy
+    }
+}
+// END SNIPPET languages
diff --git a/subprojects/docs/src/samples/sonarRunner/multiProject/build.gradle b/subprojects/docs/src/samples/sonarRunner/multiProject/build.gradle
new file mode 100644
index 0000000..f79cf7c
--- /dev/null
+++ b/subprojects/docs/src/samples/sonarRunner/multiProject/build.gradle
@@ -0,0 +1,43 @@
+apply plugin: "sonar-runner"
+
+// START SNIPPET global-configuration-settings
+sonarRunner {
+    sonarProperties {
+        property "sonar.host.url", "http://my.server.com"
+        property "sonar.jdbc.url", "jdbc:mysql://my.server.com/sonar"
+        property "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver"
+        property "sonar.username", "Fred Flintstone"
+        property "sonar.password", "very clever"
+    }
+}
+// END SNIPPET global-configuration-settings
+
+
+// START SNIPPET shared-configuration-settings
+subprojects {
+    sonarRunner {
+        sonarProperties {
+            property "sonar.sourceEncoding", "UTF-8"
+        }
+    }
+}
+// END SNIPPET shared-configuration-settings
+
+// START SNIPPET individual-configuration-settings
+project(":project1") {
+    sonarRunner {
+        sonarProperties {
+            property "sonar.language", "grvy"
+        }
+    }
+}
+// END SNIPPET individual-configuration-settings
+
+// START SNIPPET skip-project
+project(":project2") {
+    sonarRunner {
+        skipProject = true
+    }
+}
+// END SNIPPET skip-project
+
diff --git a/subprojects/docs/src/samples/sonarRunner/multiProject/settings.gradle b/subprojects/docs/src/samples/sonarRunner/multiProject/settings.gradle
new file mode 100644
index 0000000..e07a6ab
--- /dev/null
+++ b/subprojects/docs/src/samples/sonarRunner/multiProject/settings.gradle
@@ -0,0 +1 @@
+include "project1", "project2"
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/sonarRunner/quickstart/build.gradle b/subprojects/docs/src/samples/sonarRunner/quickstart/build.gradle
new file mode 100644
index 0000000..c52cf83
--- /dev/null
+++ b/subprojects/docs/src/samples/sonarRunner/quickstart/build.gradle
@@ -0,0 +1,26 @@
+apply plugin: "java"
+
+// START SNIPPET apply-plugin
+apply plugin: "sonar-runner"
+// END SNIPPET apply-plugin
+
+// START SNIPPET connection-settings
+sonarRunner {
+    sonarProperties {
+        property "sonar.host.url", "http://my.server.com"
+        property "sonar.jdbc.url", "jdbc:mysql://my.server.com/sonar"
+        property "sonar.jdbc.driverClassName", "com.mysql.jdbc.Driver"
+        property "sonar.username", "Fred Flintstone"
+        property "sonar.password", "very clever"
+    }
+}
+// END SNIPPET connection-settings
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2"
+    testCompile "junit:junit:4.+"
+}
diff --git a/subprojects/docs/src/samples/sonarRunner/quickstart/src/main/java/org/gradle/Person.java b/subprojects/docs/src/samples/sonarRunner/quickstart/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..8b69988
--- /dev/null
+++ b/subprojects/docs/src/samples/sonarRunner/quickstart/src/main/java/org/gradle/Person.java
@@ -0,0 +1,16 @@
+package org.gradle;
+
+import org.apache.commons.collections.list.GrowthList;
+
+public class Person {
+    private final String name;
+
+    public Person(String name) {
+        this.name = name;
+        new GrowthList();
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/docs/src/samples/sonarRunner/quickstart/src/test/java/org/gradle/PersonTest.java b/subprojects/docs/src/samples/sonarRunner/quickstart/src/test/java/org/gradle/PersonTest.java
new file mode 100644
index 0000000..29fb813
--- /dev/null
+++ b/subprojects/docs/src/samples/sonarRunner/quickstart/src/test/java/org/gradle/PersonTest.java
@@ -0,0 +1,12 @@
+package org.gradle;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PersonTest {
+    @Test
+    public void canConstructAPersonWithAName() {
+        Person person = new Person("Larry");
+        assertEquals("Larry", person.getName());
+    }
+}
diff --git a/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
index 55b068a..a52abdc 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/defineRepository/build.gradle
@@ -1,3 +1,5 @@
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository
+
 //START SNIPPET maven-central
 repositories {
     mavenCentral()
@@ -94,9 +96,8 @@ repositories {
 repositories {
     ivy {
         url "http://repo.mycompany.com/repo"
-        layout 'pattern', {
-            artifact "[module]/[revision]/[artifact].[ext]"
-            ivy "[module]/[revision]/ivy.xml"
+        layout "pattern", {
+            artifact "[module]/[revision]/[type]/[artifact].[ext]"
         }
     }
 }
@@ -106,9 +107,8 @@ repositories {
 repositories {
     ivy {
         url "http://repo.mycompany.com/repo"
-        layout 'pattern', {
-            artifact "[organisation]/[module]/[revision]/[artifact].[ext]"
-            ivy "[organisation]/[module]/[revision]/ivy.xml"
+        layout "pattern", {
+            artifact "[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
             m2compatible = true
         }
     }
@@ -118,9 +118,12 @@ repositories {
 //START SNIPPET ivy-repo-with-custom-pattern
 repositories {
     ivy {
-        artifactPattern "http://repo.mycompany.com/3rd-party-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-        artifactPattern "http://repo.mycompany.com/company-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
-        ivyPattern "http://repo.mycompany.com/ivy-files/[organisation]/[module]/[revision]/ivy.xml"
+        url "http://repo.mycompany.com/repo"
+        layout "pattern", {
+            artifact "3rd-party-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+            artifact "company-artifacts/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
+            ivy "ivy-files/[organisation]/[module]/[revision]/ivy.xml"
+        }
     }
 }
 //END SNIPPET ivy-repo-with-custom-pattern
@@ -128,15 +131,30 @@ repositories {
 //START SNIPPET authenticated-ivy-repo
 repositories {
     ivy {
+        url 'http://repo.mycompany.com'
         credentials {
             username 'user'
             password 'password'
         }
-        artifactPattern "http://repo.mycompany.com/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
     }
 }
 //END SNIPPET authenticated-ivy-repo
 
+//START SNIPPET ivy-repo-dynamic-mode
+// Can enable dynamic resolve mode when you define the repository
+repositories {
+    ivy {
+        url "http://repo.mycompany.com/repo"
+        resolve.dynamicMode = true
+    }
+}
+
+// Can use a rule instead to enable (or disable) dynamic resolve mode for all repositories
+repositories.withType(IvyArtifactRepository) {
+    resolve.dynamicMode = true
+}
+//END SNIPPET ivy-repo-dynamic-mode
+
 task lookup << {
     //START SNIPPET lookup-resolver
     println repositories.localRepository.name
diff --git a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
index 0c76d42..ef8e7c0 100644
--- a/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
+++ b/subprojects/docs/src/samples/userguide/artifacts/resolutionStrategy/build.gradle
@@ -32,16 +32,32 @@ configurations.all {
 
 def findDefaultVersionInCatalog(String group, String name) {
     //some custom logic that resolves the default version into a specific version
+    "1.0"
 }
 //END SNIPPET custom-versioning-scheme
 
 //START SNIPPET blacklisting_version
 configurations.all {
     resolutionStrategy.eachDependency { DependencyResolveDetails details ->
-        if (details.requested.group == 'org.cool.software' && details.requested.name == 'cool-library' && details.requested.version == '1.2') {
+        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'
         }
     }
 }
-//END SNIPPET blacklisting_version
\ No newline at end of file
+//END SNIPPET blacklisting_version
+
+//START SNIPPET module_substitution
+configurations.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.name == 'log4j') {
+            //prefer 'log4j-over-slf4j' over 'log4j', with fixed version:
+            details.useTarget "org.slf4j:log4j-over-slf4j:1.7.2"
+        }
+    }
+}
+//END SNIPPET module_substitution
\ No newline at end of file
diff --git a/subprojects/docs/src/samples/userguide/distribution/build.gradle b/subprojects/docs/src/samples/userguide/distribution/build.gradle
new file mode 100755
index 0000000..18ab403
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/distribution/build.gradle
@@ -0,0 +1,51 @@
+// START SNIPPET use-plugin
+apply plugin: 'distribution'
+// END SNIPPET use-plugin
+
+version = '1.0.0'
+
+// START SNIPPET name-conf
+distributions {
+    main {
+        baseName = 'my-name'
+    }
+}
+// END SNIPPET name-conf
+
+// START SNIPPET custom-distZip
+apply plugin: 'distribution'
+
+distributions {
+    custom
+}
+
+customDistZip {
+    from "custom/custom.txt"
+}
+// END SNIPPET custom-distZip
+
+// START SNIPPET custom-distribution
+apply plugin: 'distribution'
+
+distributions {
+    main {
+        baseName = 'someName'
+        contents {
+            from { 'src/dist' }
+        }
+    }
+}
+// END SNIPPET custom-distribution
+
+// START SNIPPET declare-distribution
+apply plugin: 'distribution'
+
+version = '1.2'
+distributions {
+    custom {
+        contents {
+            from { 'src/dist' }
+        }
+    }
+}
+// END SNIPPET declare-distribution
diff --git a/subprojects/docs/src/samples/userguide/distribution/custom/custom.txt b/subprojects/docs/src/samples/userguide/distribution/custom/custom.txt
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/docs/src/samples/userguide/distribution/readme.xml b/subprojects/docs/src/samples/userguide/distribution/readme.xml
new file mode 100755
index 0000000..5750291
--- /dev/null
+++ b/subprojects/docs/src/samples/userguide/distribution/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>A project which uses the distribution plugin</para>
+</sample>
diff --git a/subprojects/docs/src/samples/userguide/javaLibraryDistribution/build.gradle b/subprojects/docs/src/samples/userguide/javaLibraryDistribution/build.gradle
index 8f2179e..99ad1a0 100755
--- a/subprojects/docs/src/samples/userguide/javaLibraryDistribution/build.gradle
+++ b/subprojects/docs/src/samples/userguide/javaLibraryDistribution/build.gradle
@@ -5,19 +5,23 @@ apply plugin: 'java-library-distribution'
 version = '1.0.0'
 
 // START SNIPPET name-conf
-distribution {
-    name = 'my-name'
+distributions {
+    main{
+        baseName = 'my-name'
+    }
 }
 // END SNIPPET name-conf
 
-// START SNIPPET custom-distZip
-distZip {
-    from('oneFile') //Copy to the root of the distribution
-    from('secondFile') {
-        into('dist') // Copy to the dist directory
+// START SNIPPET custom-distribution
+distributions {
+    main {
+        baseName = 'my-name'
+        contents {
+            from { 'src/dist' }
+        }
     }
 }
-// END SNIPPET custom-distZip
+// END SNIPPET custom-distribution
 
 repositories {
     mavenCentral()
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingIvyGenerateDescriptor.out b/subprojects/docs/src/samples/userguideOutput/publishingIvyGenerateDescriptor.out
new file mode 100644
index 0000000..a7c07c3
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/publishingIvyGenerateDescriptor.out
@@ -0,0 +1,5 @@
+:generateIvyCustomIvyModuleDescriptor
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out
index 5679f05..9bd41b5 100644
--- a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out
+++ b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishLifecycle.out
@@ -1,14 +1,9 @@
-:generateIvyModuleDescriptor
-:subproject:compileJava
-:subproject:processResources UP-TO-DATE
-:subproject:classes
-:subproject:jar
-:compileJava
+:generateIvyJavaIvyModuleDescriptor
+:compileJava UP-TO-DATE
 :processResources UP-TO-DATE
-:classes
+:classes UP-TO-DATE
 :jar
-:sourceJar
-:publishIvyPublicationToIvyRepository
+:publishIvyJavaPublicationToIvyRepository
 :publish
 
 BUILD SUCCESSFUL
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out
index aa9bd03..74218e8 100644
--- a/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out
+++ b/subprojects/docs/src/samples/userguideOutput/publishingIvyPublishSingle.out
@@ -1,14 +1,9 @@
-:generateIvyModuleDescriptor
-:subproject:compileJava
-:subproject:processResources UP-TO-DATE
-:subproject:classes
-:subproject:jar
-:compileJava
+:generateIvyJavaIvyModuleDescriptor
+:compileJava UP-TO-DATE
 :processResources UP-TO-DATE
-:classes
+:classes UP-TO-DATE
 :jar
-:sourceJar
-:publishIvyPublicationToIvyRepository
+:publishIvyJavaPublicationToIvyRepository
 
 BUILD SUCCESSFUL
 
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingMavenGeneratePom.out b/subprojects/docs/src/samples/userguideOutput/publishingMavenGeneratePom.out
new file mode 100644
index 0000000..b075e20
--- /dev/null
+++ b/subprojects/docs/src/samples/userguideOutput/publishingMavenGeneratePom.out
@@ -0,0 +1,5 @@
+:generatePomFileForMavenCustomPublication
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishLocal.out b/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishLocal.out
index 1f0686a..6dbcc28 100644
--- a/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishLocal.out
+++ b/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishLocal.out
@@ -1,8 +1,9 @@
+:generatePomFileForMavenJavaPublication
 :compileJava
 :processResources UP-TO-DATE
 :classes
 :jar
-:publishMavenPublicationToMavenLocal
+:publishMavenJavaPublicationToMavenLocal
 :publishToMavenLocal
 
 BUILD SUCCESSFUL
diff --git a/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishMinimal.out b/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishMinimal.out
index 7b3f6f0..5862f69 100644
--- a/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishMinimal.out
+++ b/subprojects/docs/src/samples/userguideOutput/publishingMavenPublishMinimal.out
@@ -1,8 +1,9 @@
+:generatePomFileForMavenJavaPublication
 :compileJava
 :processResources UP-TO-DATE
 :classes
 :jar
-:publishMavenPublicationToMavenRepository
+:publishMavenJavaPublicationToMavenRepository
 :publish
 
 BUILD SUCCESSFUL
diff --git a/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy
index 9b313bb..eae6162 100644
--- a/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy
+++ b/subprojects/docs/src/test/groovy/org/gradle/docs/releasenotes/FunctionalReleaseNotesTest.groovy
@@ -42,7 +42,7 @@ class FunctionalReleaseNotesTest extends GebReportingSpec {
             connection.requestMethod = "HEAD"
             connection.connect()
             connection.responseCode == 200
-        } catch (ignored) {
+        } catch (IOException ignore) {
             false
         }
     }
diff --git a/subprojects/docs/src/transforms/release-notes.gradle b/subprojects/docs/src/transforms/release-notes.gradle
new file mode 100644
index 0000000..3a23dda
--- /dev/null
+++ b/subprojects/docs/src/transforms/release-notes.gradle
@@ -0,0 +1,216 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath('com.uwyn:jhighlight:1.0') {
+            exclude module: "servlet-api"
+        }
+    }
+}
+
+import org.jsoup.nodes.Element
+import org.jsoup.select.Elements
+import com.uwyn.jhighlight.renderer.XhtmlRendererFactory
+
+ext {
+    baseStyleFile = project.file("$project.cssFiles.dir/base.css")
+    releaseNotesStyleFile = project.file("$project.cssFiles.dir/release-notes.css")
+    scriptFile = project.file("src/docs/release/content/script.js")
+}
+
+inputs.files([project.cssFiles, baseStyleFile, releaseNotesStyleFile, project.configurations.jquery, scriptFile])
+
+transformDocument {
+    outputSettings().indentAmount(2).prettyPrint(true)
+
+    prependChild(new org.jsoup.nodes.DocumentType("html", "", "", ""))
+
+    head().
+            append("<meta charset='utf-8'>").
+            append("<title>Gradle @version@ Release Notes</title>")
+
+    head().append("<style>p{}</style>").children().last().childNode(0).attr("data", baseStyleFile.text + releaseNotesStyleFile.text)
+
+    head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", project.configurations.jquery.singleFile.text)
+    head().append("<script type='text/javascript'>1;</script>").children().last().childNode(0).attr("data", scriptFile.text)
+}
+
+// wrap each h2 section in section.topic
+transformDocument {
+    def heading = body().select("h2").first()
+    def inSection = [heading]
+    Element next = heading.nextElementSibling()
+    while (true) {
+        if (next == null || next.tagName() == "h2") {
+            def section = heading.before("<section class='topic'/>").previousElementSibling()
+            Elements inSectionElements = new Elements(inSection)
+            section.html(inSectionElements.outerHtml())
+            inSectionElements.remove()
+
+            if (next == null) {
+                break
+            } else {
+                inSection = [next]
+                heading = next
+            }
+        } else {
+            inSection << next
+        }
+
+        next = next.nextElementSibling()
+    }
+}
+
+transformDocument {
+    def incubatingMarker = " (i)"
+    for (heading in body().select(".topic").select("h3")) {
+        if (heading.text().endsWith(incubatingMarker)) {
+            heading.text(heading.text() - incubatingMarker).addClass("incubating")
+            heading.after("<a class='incubating-marker' href='userguide/feature_lifecycle.html' title='“incubating” features are not yet guaranteed to be backwards compatible<br />(click for more information)'>incubating feature</a>")
+        }
+    }
+}
+
+// wrap all content after the first element after a h3 (up to the next same level heading)
+// in a section.major-detail block
+transformDocument {
+    for (heading in body().select(".topic").select("h3")) {
+        def detail = []
+
+        Element next = heading.nextElementSibling()
+        while (next != null && next.tagName() != "h4") {
+            next = next.nextElementSibling()
+        }
+
+        while (true) {
+            if (next == null || next.tagName() ==~ /h[123]/) {
+                break
+            }
+            detail << next
+            next = next.nextElementSibling()
+        }
+
+        if (detail) {
+            def section = detail.first().before("<section class='major-detail'/>").previousElementSibling()
+            Elements detailElements = new Elements(detail)
+            section.html(detailElements.outerHtml())
+            detailElements.remove()
+        }
+    }
+}
+
+// wrap all content after a h4 until the next heading in a section.minor-detail
+transformDocument {
+    for (heading in body().select("h4")) {
+        def detail = []
+        Element next = heading.nextElementSibling()
+        while (true) {
+            if (next == null || next.tagName() ==~ /h[1234]/) {
+                break
+            }
+            detail << next
+            next = next.nextElementSibling()
+        }
+
+        if (detail) {
+            def section = detail.first().before("<section class='minor-detail'/>").previousElementSibling()
+            Elements detailElements = new Elements(detail)
+            section.html(detailElements.outerHtml())
+            detailElements.remove()
+        }
+    }
+}
+
+// add anchors for all of the headings
+transformDocument {
+    for (heading in body().select("h2,h3")) {
+        def anchorName = heading.text().toLowerCase().replaceAll(' ', '-')
+        heading.attr("id", anchorName)
+    }
+}
+
+// Add the TOC
+transformDocument {
+    def tocSection = body().select("section.topic").first().before("<section class='table-of-contents'/>").previousElementSibling()
+    tocSection.append("<h2>Table Of Contents</h2>")
+    def toc = tocSection.append("<ul class='toc'/>").children().last()
+
+    for (topic in body().select(".topic")) {
+        def topicHeading = topic.select("h2").first()
+        def name = topicHeading.text()
+        def anchor = topicHeading.attr("id")
+
+        toc.append("<li><a/></li>").children().last().select("a").first().text(name).attr("href", "#$anchor")
+
+        def subs = topic.select("h3")
+        if (subs) {
+            def sublist = toc.children().last().append("<ul class='toc-sub'/>").children().last()
+            subs.each {
+                def subName = it.text()
+                def subAnchorName = it.attr("id")
+                sublist.append("<li><a/></li>").children().last().select("a").first().text(subName).attr("href", "#$subAnchorName")
+            }
+        }
+
+    }
+}
+
+// Add the heading
+transformDocument {
+    body().prepend("<h3 class='releaseinfo'>Version @version@</h3>")
+    body().prepend("<h1>Gradle Release Notes</h1>")
+}
+
+// Add the footer
+transformDocument {
+    def footer = body().append("<section class='footer'/>").children().last()
+    footer.html("Gradle @version@ Release Notes<br />")
+}
+
+// Syntax highlighting
+transformDocument {
+    body().select("code").each { code ->
+        def parent = code.parent()
+        if (parent.tagName() == "pre") {
+            def text = code.text()
+            def input = new ByteArrayInputStream(code.text().getBytes("utf-8"))
+            def renderer = XhtmlRendererFactory.getRenderer("groovy")
+            def out = new ByteArrayOutputStream()
+            renderer.highlight("test", input, out, "utf-8", true)
+            code.html(new String(out.toByteArray(), "utf-8"))
+            code.select("br").remove()
+            code.childNodes().findAll { it.nodeName().equals("#comment") }*.remove()
+            code.html(code.html().trim())
+            parent.addClass("code")
+        }
+    }
+}
+
+// Terminal styling
+transformDocument {
+    body().select("tt").each { tt ->
+        def parent = tt.parent()
+        if (parent.tagName() == "pre") {
+            tt.select("br").remove()
+            tt.childNodes().findAll { it.nodeName().equals("#comment") }*.remove()
+            tt.html(tt.html().trim())
+            parent.addClass("tt")
+        }
+    }
+}
+
+// Wrap the page in a text container to get the margins
+transformDocument {
+    def bodyContent = body().children().remove()
+    body().prepend("<div class='text-container'/>")
+    body().children()[0].html(bodyContent.outerHtml())
+}
+
+// Turn Gradle issue numbers into issue links
+transformDocument {
+    def rewritten = body().html().replaceAll(~/GRADLE-\d+/) {
+        "<a href='http://issues.gradle.org/browse/${it}'>${it}</a>"
+    }
+    body().html(rewritten)
+}
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java
index 26a9013..adbff5d 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/DeploymentDescriptor.java
@@ -190,7 +190,7 @@ public interface DeploymentDescriptor {
 
     /**
      * Reads the deployment descriptor from a file. The paths are resolved as defined by
-     * {@link org.gradle.api.Project#files(Object...)}
+     * {@link org.gradle.api.Project#file(Object)}
      * 
      * @param path
      *            The path of the file to read the deployment descriptor from
@@ -209,7 +209,7 @@ public interface DeploymentDescriptor {
 
     /**
      * Writes the deployment descriptor into a file. The paths are resolved as defined by
-     * {@link org.gradle.api.Project#files(Object...)}
+     * {@link org.gradle.api.Project#file(Object)}
      * 
      * @param path
      *            The path of the file to write the deployment descriptor into.
diff --git a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy
index 825e78f..7c17ee1 100644
--- a/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy
+++ b/subprojects/ear/src/main/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptor.groovy
@@ -20,10 +20,8 @@ import org.gradle.api.Action
 import org.gradle.api.UncheckedIOException
 import org.gradle.api.XmlProvider
 import org.gradle.api.internal.DomNode
-import org.gradle.api.internal.ErroringAction
-import org.gradle.api.internal.IoActions
-import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ear.descriptor.DeploymentDescriptor
 import org.gradle.plugins.ear.descriptor.EarModule
 import org.gradle.plugins.ear.descriptor.EarSecurityRole
@@ -185,12 +183,7 @@ class DefaultDeploymentDescriptor implements DeploymentDescriptor {
     }
 
     public DefaultDeploymentDescriptor writeTo(Object path) {
-        IoActions.writeFile(fileResolver.resolve(path), new ErroringAction<Writer>() {
-            @Override
-            void doExecute(Writer writer) {
-                writeTo(writer);
-            }
-        })
+        transformer.transform(toXmlNode(), fileResolver.resolve(path))
         return this;
     }
 
diff --git a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy
index 59ed353..66c024a 100644
--- a/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy
+++ b/subprojects/ear/src/test/groovy/org/gradle/plugins/ear/descriptor/internal/DefaultDeploymentDescriptorTest.groovy
@@ -16,24 +16,30 @@
 
 package org.gradle.plugins.ear.descriptor.internal
 
-import javax.xml.parsers.DocumentBuilderFactory
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
 import spock.lang.Specification
+
+import javax.xml.parsers.DocumentBuilderFactory
+
 import static org.gradle.util.TextUtil.toPlatformLineSeparators
 
 /**
  * @author: Szczepan Faber, created at: 6/3/11
  */
 class DefaultDeploymentDescriptorTest extends Specification {
-
-    def out = new StringWriter()
-    def descriptor = new DefaultDeploymentDescriptor(null)
+    def descriptor = new DefaultDeploymentDescriptor({ it } as FileResolver)
+    @Rule TestNameTestDirectoryProvider tmpDir
 
     def "writes default descriptor"() {
+        def file = tmpDir.file("out.xml")
+
         when:
-        descriptor.writeTo(out)
+        descriptor.writeTo(file)
 
         then:
-        def root = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(out.toString().getBytes("utf-8"))).documentElement
+        def root = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(file.bytes)).documentElement
         root.nodeName == 'application'
         root.getAttribute("xmlns") == "http://java.sun.com/xml/ns/javaee"
         root.getAttribute("xmlns:xsi") == "http://www.w3.org/2001/XMLSchema-instance"
@@ -43,6 +49,7 @@ class DefaultDeploymentDescriptorTest extends Specification {
     }
 
     def "writes version 1.3 default descriptor"() {
+        def out = new StringWriter()
         descriptor.version = '1.3'
 
         when:
@@ -56,6 +63,7 @@ class DefaultDeploymentDescriptorTest extends Specification {
     }
 
     def "writes customized descriptor"() {
+        def out = new StringWriter()
         descriptor.fileName = "myApp.xml"
         descriptor.version = "1.3"
         descriptor.applicationName = "myapp"
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 2595317..5d66bad 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
@@ -17,7 +17,6 @@ package org.gradle.plugins.ide.eclipse
 
 import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
-import org.gradle.test.fixtures.file.TestFile
 
 class AbstractEclipseIntegrationTest extends AbstractIdeIntegrationTest {
     protected ExecutionResult runEclipseTask(settingsScript = "rootProject.name = 'root'", buildScript) {
@@ -71,6 +70,6 @@ class AbstractEclipseIntegrationTest extends AbstractIdeIntegrationTest {
     }
 
     protected EclipseClasspathFixture getClasspath() {
-        return new EclipseClasspathFixture(testDirectory, new TestFile(executer.gradleUserHomeDir))
+        return new EclipseClasspathFixture(testDirectory, 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 23cd77f..a4f758c 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
@@ -26,7 +26,7 @@ import spock.lang.Issue
 class EclipseClasspathIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     String content
 
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
index 744bb40..acad7b3 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathRemoteResolutionIntegrationTest.groovy
@@ -25,7 +25,7 @@ import org.junit.Test
 class EclipseClasspathRemoteResolutionIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Rule public final HttpServer server = new HttpServer()
-    @Rule public final TestResources testResources = new TestResources()
+    @Rule public final TestResources testResources = new TestResources(testDirectoryProvider)
     final def repo = new MavenHttpRepository(server, "/repo", mavenRepo)
 
     @Before
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy
index a93a937..d9e0292 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseClasspathResolveIntegrationTest.groovy
@@ -23,7 +23,7 @@ import org.junit.Rule
 
 class EclipseClasspathResolveIntegrationTest extends AbstractDependencyResolutionTest {
 
-    @Rule ProgressLoggingFixture progressLogging
+    @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
 
     def "does not download source and javadoc artifacts from HTTP Maven repository until required"() {
         given:
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 4e56d1c..420119f 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
@@ -25,7 +25,7 @@ class EclipseIntegrationTest extends AbstractEclipseIntegrationTest {
     private static String nonAscii = "\\u7777\\u8888\\u9999"
 
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Test
     void canCreateAndDeleteMetaData() {
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
index bb31da6..00d4240 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseMultiModuleIntegrationTest.groovy
@@ -16,16 +16,16 @@
 package org.gradle.plugins.ide.eclipse
 
 import org.gradle.integtests.fixtures.TestResources
+import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 
 /**
  * @author Szczepan Faber, @date 01.03.11
  */
 class EclipseMultiModuleIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Test
     void dealsWithDuplicatedModuleNames() {
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
index 341d2e7..970eaff 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseProjectIntegrationTest.groovy
@@ -25,7 +25,7 @@ import org.junit.Test
 class EclipseProjectIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     String content
 
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
index 737693b..d031ac9 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/eclipse/EclipseWtpModelIntegrationTest.groovy
@@ -18,7 +18,6 @@ package org.gradle.plugins.ide.eclipse
 
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.plugins.ide.eclipse.model.AbstractClasspathEntry
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import spock.lang.Issue
@@ -29,7 +28,7 @@ import spock.lang.Issue
 class EclipseWtpModelIntegrationTest extends AbstractEclipseIntegrationTest {
 
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     String component
 
@@ -39,10 +38,9 @@ class EclipseWtpModelIntegrationTest extends AbstractEclipseIntegrationTest {
         file('someExtraSourceDir').mkdirs()
         file('src/foo/bar').mkdirs()
 
-        def repoDir = file("repo")
-        maven(repoDir).module("gradle", "foo").publish()
-        maven(repoDir).module("gradle", "bar").publish()
-        maven(repoDir).module("gradle", "baz").publish()
+        mavenRepo.module("gradle", "foo").publish()
+        mavenRepo.module("gradle", "bar").publish()
+        mavenRepo.module("gradle", "baz").publish()
 
         //when
         runEclipseTask """
@@ -56,7 +54,7 @@ configurations {
 }
 
 repositories {
-  maven { url "${repoDir.toURI()}" }
+  maven { url "${mavenRepo.uri}" }
 }
 
 dependencies {
@@ -112,6 +110,44 @@ eclipse {
         assert facet.contains('1.333')
     }
 
+    @Issue("GRADLE-2653")
+    @Test
+    void "wtp component respects configuration modifications"() {
+        //given
+        mavenRepo.module("gradle", "foo").publish()
+        mavenRepo.module("gradle", "bar").publish()
+        mavenRepo.module("gradle", "baz").publish()
+        mavenRepo.module("gradle", "baz", "2.0").publish()
+
+        //when
+        runEclipseTask """
+apply plugin: 'java'
+apply plugin: 'war'
+apply plugin: 'eclipse-wtp'
+
+repositories {
+  maven { url "${mavenRepo.uri}" }
+}
+
+dependencies {
+  compile 'gradle:foo:1.0', 'gradle:bar:1.0', 'gradle:baz:1.0'
+}
+
+configurations.compile {
+  exclude module: 'bar' //an exclusion
+  resolutionStrategy.force 'gradle:baz:2.0' //forced module
+}
+        """
+
+        //when
+        component = getFile([:], '.settings/org.eclipse.wst.common.component').text
+
+        //then
+        component.contains('foo-1.0.jar')
+        component.contains('baz-2.0.jar') //forced version
+        !component.contains('bar') //excluded
+    }
+
     @Test
     void allowsConfiguringHooksForComponent() {
         //given
@@ -217,9 +253,9 @@ eclipse {
         assert facet.contains('<be>cool</be>')
     }
 
-    @Ignore("GRADLE-1487")
+    @Issue("GRADLE-2661")
     @Test
-    void allowsFileDependencies() {
+    void "file dependencies respect plus minus configurations"() {
         //when
         runEclipseTask """
 apply plugin: 'java'
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksIntegrationTest.groovy
index 0ea5b7d..7fd7e34 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/ConfigurationHooksIntegrationTest.groovy
@@ -23,7 +23,7 @@ import org.junit.Test
 
 class ConfigurationHooksIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Test
     void triggersBeforeAndWhenConfigurationHooks() {
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 5a61db1..d53c9da 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaIntegrationTest.groovy
@@ -18,11 +18,11 @@ package org.gradle.plugins.ide.idea
 
 import org.custommonkey.xmlunit.Diff
 import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
-import org.custommonkey.xmlunit.XMLAssert
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.internal.os.OperatingSystem
 import org.gradle.plugins.ide.AbstractIdeIntegrationTest
 import org.gradle.test.fixtures.file.TestFile
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 
@@ -30,7 +30,7 @@ import java.util.regex.Pattern
 
 class IdeaIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Test
     void mergesImlCorrectly() {
@@ -95,6 +95,17 @@ apply plugin: 'idea'
         assertHasExpectedContents('root.iml')
     }
 
+    @Ignore
+    @Test
+    void addsScalaFacetAndCompilerLibraries() {
+        executer.withTasks('idea').run()
+
+        assertHasExpectedContents('root.ipr')
+        assertHasExpectedContents('project1/project1.iml')
+        assertHasExpectedContents('project2/project2.iml')
+        assertHasExpectedContents('project3/project3.iml')
+    }
+
     @Test
     void outputDirsDefaultToToIdeaDefaults() {
         runIdeaTask("apply plugin: 'java'; apply plugin: 'idea'")
@@ -348,7 +359,7 @@ apply plugin: "idea"
         Diff diff = new Diff(expectedXml, actualXml)
         diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
         try {
-            XMLAssert.assertXMLEqual(diff, true)
+            assert diff.similar()
         } catch (AssertionError e) {
             if (OperatingSystem.current().unix) {
                 def process = ["diff", expectedFile.absolutePath, file.absolutePath].execute()
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
index ffad1da..08a2bf8 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaModuleIntegrationTest.groovy
@@ -24,7 +24,7 @@ import spock.lang.Issue
 
 class IdeaModuleIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Test
     void enablesCustomizationsOnNewModel() {
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 ef544a9..773e9c6 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
@@ -25,7 +25,7 @@ import org.junit.Test
  */
 class IdeaMultiModuleIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Test
     void buildsCorrectModuleDependencies() {
diff --git a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
index 226833a..5bae9c3 100644
--- a/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
+++ b/subprojects/ide/src/integTest/groovy/org/gradle/plugins/ide/idea/IdeaProjectIntegrationTest.groovy
@@ -24,7 +24,7 @@ import spock.lang.Issue
 
 class IdeaProjectIntegrationTest extends AbstractIdeIntegrationTest {
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Issue("GRADLE-1011")
     @Test
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/build.gradle
new file mode 100644
index 0000000..5a91209
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/build.gradle
@@ -0,0 +1,3 @@
+allprojects {
+    apply plugin: "idea"
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project1/project1.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project1/project1.iml.xml
new file mode 100644
index 0000000..e430939
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project1/project1.iml.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output/>
+    <orderEntry type="inheritedJdk"/>
+    <content url="file://$MODULE_DIR$/">
+      <excludeFolder url="file://$MODULE_DIR$/build"/>
+      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
+    </content>
+    <orderEntry type="sourceFolder" forTests="false"/>
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/jar/@SHA1@/scala-library-2.9.2.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/source/@SHA1@/scala-library-2.9.2-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+  </component>
+  <component name="ModuleRootManager"/>
+  <component name="FacetManager">
+    <facet type="scala" name="Scala">
+      <configuration>
+        <option name="compilerLibraryLevel" value="Project"/>
+        <option name="compilerLibraryName" value="scala-compiler-2.9.2"/>
+      </configuration>
+    </facet>
+  </component>
+</module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project2/project2.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project2/project2.iml.xml
new file mode 100644
index 0000000..d9f8fb8
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project2/project2.iml.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output/>
+    <orderEntry type="inheritedJdk"/>
+    <content url="file://$MODULE_DIR$/">
+      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
+      <excludeFolder url="file://$MODULE_DIR$/build"/>
+    </content>
+    <orderEntry type="sourceFolder" forTests="false"/>
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.10.0/jar/@SHA1@/scala-library-2.10.0.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.10.0/source/@SHA1@/scala-library-2.10.0-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+  </component>
+  <component name="ModuleRootManager"/>
+  <component name="FacetManager">
+    <facet type="scala" name="Scala">
+      <configuration>
+        <option name="compilerLibraryLevel" value="Project"/>
+        <option name="compilerLibraryName" value="scala-compiler-2.10.0"/>
+      </configuration>
+    </facet>
+  </component>
+</module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project3/project3.iml.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project3/project3.iml.xml
new file mode 100644
index 0000000..e430939
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/project3/project3.iml.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output/>
+    <orderEntry type="inheritedJdk"/>
+    <content url="file://$MODULE_DIR$/">
+      <excludeFolder url="file://$MODULE_DIR$/build"/>
+      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
+    </content>
+    <orderEntry type="sourceFolder" forTests="false"/>
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/jar/@SHA1@/scala-library-2.9.2.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/source/@SHA1@/scala-library-2.9.2-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+  </component>
+  <component name="ModuleRootManager"/>
+  <component name="FacetManager">
+    <facet type="scala" name="Scala">
+      <configuration>
+        <option name="compilerLibraryLevel" value="Project"/>
+        <option name="compilerLibraryName" value="scala-compiler-2.9.2"/>
+      </configuration>
+    </facet>
+  </component>
+</module>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/root.ipr.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/root.ipr.xml
new file mode 100644
index 0000000..e8f66e8
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/expectedFiles/root.ipr.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac"/>
+    <resourceExtensions>
+      <entry name=".+\.(properties|xml|html|dtd|tld)"/>
+      <entry name=".+\.(gif|png|jpeg|jpg)"/>
+    </resourceExtensions>
+    <wildcardResourcePatterns>
+      <entry name="!?*.groovy"/>
+      <entry name="!?*.java"/>
+    </wildcardResourcePatterns>
+    <annotationProcessing enabled="false" useClasspath="true"/>
+  </component>
+  <component name="CopyrightManager" default="">
+    <module2copyright/>
+  </component>
+  <component name="DependencyValidationManager">
+    <option name="SKIP_IMPORT_STATEMENTS" value="false"/>
+  </component>
+  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false"/>
+  <component name="GradleUISettings">
+    <setting name="root"/>
+  </component>
+  <component name="GradleUISettings2">
+    <setting name="root"/>
+  </component>
+  <component name="IdProvider" IDEtalkID="11DA1DB66DD62DDA1ED602B7079FE97C"/>
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY"/>
+    <option name="OPTION_SCOPE" value="protected"/>
+    <option name="OPTION_HIERARCHY" value="true"/>
+    <option name="OPTION_NAVIGATOR" value="true"/>
+    <option name="OPTION_INDEX" value="true"/>
+    <option name="OPTION_SEPARATE_INDEX" value="true"/>
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false"/>
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false"/>
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false"/>
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true"/>
+    <option name="OPTION_DEPRECATED_LIST" value="true"/>
+    <option name="OTHER_OPTIONS" value=""/>
+    <option name="HEAP_SIZE"/>
+    <option name="LOCALE"/>
+    <option name="OPEN_IN_BROWSER" value="true"/>
+  </component>
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/root.iml" filepath="$PROJECT_DIR$/root.iml"/>
+      <module fileurl="file://$PROJECT_DIR$/project3/project3.iml" filepath="$PROJECT_DIR$/project3/project3.iml"/>
+      <module fileurl="file://$PROJECT_DIR$/project2/project2.iml" filepath="$PROJECT_DIR$/project2/project2.iml"/>
+      <module fileurl="file://$PROJECT_DIR$/project1/project1.iml" filepath="$PROJECT_DIR$/project1/project1.iml"/>
+    </modules>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-type="JavaSDK" assert-jdk-15="true" project-jdk-name="1.7">
+    <output url="file://$PROJECT_DIR$/out"/>
+  </component>
+  <component name="SvnBranchConfigurationManager">
+    <option name="mySupportsUserInfoFilter" value="true"/>
+  </component>
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs=""/>
+  </component>
+  <component name="masterDetails">
+    <states>
+      <state key="ArtifactsStructureConfigurable.UI">
+        <UIState>
+          <splitter-proportions>
+            <SplitterProportionsDataImpl/>
+          </splitter-proportions>
+          <settings/>
+        </UIState>
+      </state>
+      <state key="Copyright.UI">
+        <UIState>
+          <splitter-proportions>
+            <SplitterProportionsDataImpl/>
+          </splitter-proportions>
+        </UIState>
+      </state>
+      <state key="ProjectJDKs.UI">
+        <UIState>
+          <splitter-proportions>
+            <SplitterProportionsDataImpl>
+              <option name="proportions">
+                <list>
+                  <option value="0.2"/>
+                </list>
+              </option>
+            </SplitterProportionsDataImpl>
+          </splitter-proportions>
+          <last-edited>1.6</last-edited>
+        </UIState>
+      </state>
+      <state key="ScopeChooserConfigurable.UI">
+        <UIState>
+          <splitter-proportions>
+            <SplitterProportionsDataImpl/>
+          </splitter-proportions>
+          <settings/>
+        </UIState>
+      </state>
+    </states>
+  </component>
+  <component name="libraryTable">
+    <library name="scala-compiler-2.9.2">
+      <CLASSES>
+        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-compiler/2.9.2/jar/@SHA1@/scala-compiler-2.9.2.jar!/"/>
+        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.9.2/jar/@SHA1@/scala-library-2.9.2.jar!/"/>
+      </CLASSES>
+      <JAVADOC/>
+      <SOURCES/>
+    </library>
+    <library name="scala-compiler-2.10.0">
+      <CLASSES>
+        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-compiler/2.10.0/jar/@SHA1@/scala-compiler-2.10.0.jar!/"/>
+        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-library/2.10.0/jar/@SHA1@/scala-library-2.10.0.jar!/"/>
+        <root url="jar://@CACHE_DIR@/org.scala-lang/scala-reflect/2.10.0/jar/@SHA1@/scala-reflect-2.10.0.jar!/"/>
+      </CLASSES>
+      <JAVADOC/>
+      <SOURCES/>
+    </library>
+  </component>
+</project>
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project1/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project1/build.gradle
new file mode 100644
index 0000000..40dfc05
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project1/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "scala"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "org.scala-lang:scala-library:2.9.2"
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project2/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project2/build.gradle
new file mode 100644
index 0000000..58275ac
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project2/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "scala"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "org.scala-lang:scala-library:2.10.0"
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project3/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project3/build.gradle
new file mode 100644
index 0000000..40dfc05
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/project3/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: "scala"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "org.scala-lang:scala-library:2.9.2"
+}
\ No newline at end of file
diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/settings.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/settings.gradle
new file mode 100644
index 0000000..0f37466
--- /dev/null
+++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/idea/IdeaIntegrationTest/addsScalaFacetAndCompilerLibraries/settings.gradle
@@ -0,0 +1,3 @@
+include "project1", "project2", "project3"
+
+rootProject.name = "root"
\ No newline at end of file
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
index d1ff2e8..6e061fd 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/eclipse/model/internal/WtpComponentFactory.groovy
@@ -16,14 +16,12 @@
 package org.gradle.plugins.ide.eclipse.model.internal
 
 import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.Dependency
-import org.gradle.api.artifacts.ExternalDependency
-import org.gradle.api.artifacts.SelfResolvingDependency
 import org.gradle.plugins.ide.eclipse.EclipsePlugin
 import org.gradle.plugins.ide.eclipse.model.EclipseWtpComponent
 import org.gradle.plugins.ide.eclipse.model.WbDependentModule
 import org.gradle.plugins.ide.eclipse.model.WbResource
 import org.gradle.plugins.ide.eclipse.model.WtpComponent
+import org.gradle.plugins.ide.internal.IdeDependenciesExtractor
 
 /**
  * @author Hans Dockter
@@ -86,22 +84,19 @@ class WtpComponentFactory {
 
     // must NOT include transitive library dependencies
     private Set getEntriesFromLibraries(Set plusConfigurations, Set minusConfigurations, EclipseWtpComponent wtp, String deployPath) {
-        Set declaredDependencies = getDependencies(plusConfigurations, minusConfigurations,
-                { it instanceof ExternalDependency})
+        def extractor = new IdeDependenciesExtractor()
+        //below is not perfect because we're skipping the unresolved dependencies completely
+        //however, it should be better anyway. Sometime soon we will hopefully change the wtp component stuff
+        def externals = extractor.resolvedExternalDependencies(plusConfigurations, minusConfigurations)
+        def locals = extractor.extractLocalFileDependencies(plusConfigurations, minusConfigurations)
 
-        Set libFiles = wtp.project.configurations.detachedConfiguration((declaredDependencies as Dependency[])).files +
-                getSelfResolvingFiles(getDependencies(plusConfigurations, minusConfigurations,
-                        { it instanceof SelfResolvingDependency && !(it instanceof org.gradle.api.artifacts.ProjectDependency)}))
+        def libFiles = (externals + locals)*.file
 
         libFiles.collect { file ->
             createWbDependentModuleEntry(file, wtp.fileReferenceFactory, deployPath)
         }
     }
 
-    private LinkedHashSet getSelfResolvingFiles(LinkedHashSet<SelfResolvingDependency> dependencies) {
-        dependencies.collect { it.resolve() }.flatten() as LinkedHashSet
-    }
-
     private WbDependentModule createWbDependentModuleEntry(File file, FileReferenceFactory fileReferenceFactory, String deployPath) {
         def ref = fileReferenceFactory.fromFile(file)
         def handleSnippet
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
index f9d7feb..46ed86a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/IdeaPlugin.groovy
@@ -13,8 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.plugins.ide.idea;
-
+package org.gradle.plugins.ide.idea
 
 import org.gradle.api.JavaVersion
 import org.gradle.api.Project
@@ -22,6 +21,7 @@ import org.gradle.api.plugins.JavaPlugin
 import org.gradle.internal.reflect.Instantiator
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.plugins.ide.idea.internal.IdeaNameDeduper
+import org.gradle.plugins.ide.idea.internal.IdeaScalaConfigurer
 import org.gradle.plugins.ide.internal.IdePlugin
 import org.gradle.plugins.ide.idea.model.*
 
@@ -34,7 +34,6 @@ import javax.inject.Inject
  *  @author Hans Dockter
  */
 class IdeaPlugin extends IdePlugin {
-
     private final Instantiator instantiator
     IdeaModel model
 
@@ -57,6 +56,7 @@ class IdeaPlugin extends IdePlugin {
         configureIdeaProject(project)
         configureIdeaModule(project)
         configureForJavaPlugin(project)
+        configureForScalaPlugin()
 
         hookDeduplicationToTheRoot(project)
     }
@@ -172,6 +172,12 @@ class IdeaPlugin extends IdePlugin {
         }
     }
 
+    private void configureForScalaPlugin() {
+        if (isRoot(project)) {
+            new IdeaScalaConfigurer(project).configure()
+        }
+    }
+
     private boolean isRoot(Project project) {
         return project.parent == null
     }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaScalaConfigurer.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaScalaConfigurer.groovy
new file mode 100644
index 0000000..84f77bf
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/internal/IdeaScalaConfigurer.groovy
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.idea.internal
+
+import org.gradle.api.Project
+import org.gradle.api.XmlProvider
+import org.gradle.api.plugins.scala.ScalaBasePlugin
+import org.gradle.plugins.ide.idea.IdeaPlugin
+import org.gradle.plugins.ide.idea.model.FilePath
+import org.gradle.plugins.ide.idea.model.IdeaModule
+import org.gradle.plugins.ide.idea.model.ModuleLibrary
+import org.gradle.plugins.ide.idea.model.ProjectLibrary
+
+class IdeaScalaConfigurer {
+    private final Project rootProject
+
+    IdeaScalaConfigurer(Project rootProject) {
+        this.rootProject = rootProject
+    }
+
+    void configure() {
+        rootProject.gradle.projectsEvaluated {
+            def scalaProjects = findProjectsApplyingIdeaAndScalaPlugins()
+            scalaProjects.tasks.ideaModule*.dependsOn(rootProject.tasks.ideaProject)
+            Map<String, ProjectLibrary> scalaCompilerLibraries = [:]
+
+            rootProject.ideaProject.doFirst {
+                scalaCompilerLibraries = resolveScalaCompilerLibraries(project, scalaProjects)
+                declareUniqueProjectLibraries(scalaCompilerLibraries.values() as Set)
+            }
+
+            rootProject.configure(scalaProjects) { org.gradle.api.Project prj ->
+                idea.module.iml.withXml { XmlProvider xmlProvider ->
+                    declareScalaFacet(scalaCompilerLibraries[prj.path], xmlProvider.asNode())
+                }
+            }
+        }
+    }
+
+    private Map<String, ProjectLibrary> resolveScalaCompilerLibraries(Project rootProject, Collection<Project> scalaProjects) {
+        def scalaCompilerLibraries = [:]
+
+        for (scalaProject in scalaProjects) {
+            def scalaPlugin = scalaProject.plugins.getPlugin(ScalaBasePlugin)
+            IdeaModule ideaModule = scalaProject.idea.module
+
+            // could make resolveDependencies() cache its result for later use by GenerateIdeaModule
+            def dependencies = ideaModule.resolveDependencies()
+            def moduleLibraries = dependencies.findAll { it instanceof ModuleLibrary }
+            def filePaths = moduleLibraries.collectMany { it.classes.findAll { it instanceof FilePath } }
+            def files = filePaths.collect { it.file }
+
+            def scalaClasspath = scalaPlugin.inferScalaCompilerClasspath(files)
+            def compilerJar = scalaPlugin.findScalaJar(scalaClasspath, "compiler")
+            def version = compilerJar == null ? "?" : scalaPlugin.getScalaVersion(compilerJar)
+            def library = createProjectLibrary("scala-compiler-$version", scalaClasspath)
+            def duplicate = scalaCompilerLibraries.values().find { it.classes == library.classes }
+            scalaCompilerLibraries[scalaProject.path] = duplicate ?: library
+        }
+
+        return scalaCompilerLibraries
+    }
+
+    private void declareUniqueProjectLibraries(Set<ProjectLibrary> projectLibraries) {
+        def existingLibraries = rootProject.idea.project.projectLibraries
+        def newLibraries = projectLibraries - existingLibraries
+        for (newLibrary in newLibraries) {
+            def originalName = newLibrary.name
+            def suffix = 1
+            while (existingLibraries.find { it.name == newLibrary.name }) {
+                newLibrary.name = "$originalName-${suffix++}"
+            }
+            existingLibraries << newLibrary
+        }
+    }
+
+    private void declareScalaFacet(ProjectLibrary scalaCompilerLibrary, Node iml) {
+        def facetManager = iml.component.find { it. at name == "FacetManager" }
+        if (!facetManager) {
+            facetManager = iml.appendNode("component", [name: "FacetManager"])
+        }
+
+        def scalaFacet = facetManager.facet.find { it. at type == "scala" }
+        if (!scalaFacet) {
+            scalaFacet = facetManager.appendNode("facet", [type: "scala", name: "Scala"])
+        }
+
+        def configuration = scalaFacet.configuration[0]
+        if (!configuration) {
+            configuration = scalaFacet.appendNode("configuration")
+        }
+
+        def libraryLevel = configuration.option.find { it. at name == "compilerLibraryLevel" }
+        if (!libraryLevel) {
+            libraryLevel = configuration.appendNode("option", [name: "compilerLibraryLevel"])
+        }
+        libraryLevel. at value = "Project"
+
+        def libraryName = configuration.option.find { it. at name == "compilerLibraryName" }
+        if (!libraryName) {
+            libraryName = configuration.appendNode("option", [name: "compilerLibraryName"])
+        }
+
+        libraryName. at value = scalaCompilerLibrary.name
+    }
+
+    private Collection<Project> findProjectsApplyingIdeaAndScalaPlugins() {
+        rootProject.allprojects.findAll {
+            it.plugins.hasPlugin(IdeaPlugin) && it.plugins.hasPlugin(ScalaBasePlugin)
+        }
+    }
+
+    private ProjectLibrary createProjectLibrary(String name, Iterable<File> jars) {
+        new ProjectLibrary(name: name, classes: jars as Set)
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
index 4e878cc..5170bc6 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaModule.groovy
@@ -269,9 +269,9 @@ class IdeaModule {
 
     /**
      * Enables advanced configuration like tinkering with the output xml
-     * or affecting the way existing *.iml content is merged with gradle build information
+     * or affecting the way existing *.iml content is merged with gradle build information.
      * <p>
-     * For example see docs for {@link IdeaModule}
+     * For example see docs for {@link IdeaModule}.
      */
     void iml(Closure closure) {
         ConfigureUtil.configure(closure, getIml())
@@ -309,6 +309,7 @@ class IdeaModule {
      * IdeaModule instances should be configured with all necessary information by the plugin or user.
      */
     final org.gradle.api.Project project
+
     PathFactory pathFactory
 
     /**
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
index 6660143..b4bf3fb 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/IdeaProject.groovy
@@ -16,6 +16,7 @@
 
 package org.gradle.plugins.ide.idea.model
 
+import org.gradle.api.Incubating
 import org.gradle.plugins.ide.api.XmlFileContentMerger
 import org.gradle.util.ConfigureUtil
 
@@ -26,6 +27,8 @@ import org.gradle.util.ConfigureUtil
  * Typically you don't have configure IDEA module directly because Gradle configures it for you.
  *
  * <pre autoTested=''>
+ * import org.gradle.plugins.ide.idea.model.*
+ *
  * apply plugin: 'java'
  * apply plugin: 'idea'
  *
@@ -43,6 +46,9 @@ import org.gradle.util.ConfigureUtil
  *
  *     //you can change the output file
  *     outputFile = new File(outputFile.parentFile, 'someBetterName.ipr')
+ *
+ *     //you can add project-level libraries
+ *     projectLibraries << new ProjectLibrary(name: "my-library", classes: [new Path("path/to/library")])
  *   }
  * }
  * </pre>
@@ -129,6 +135,12 @@ class IdeaProject {
     File outputFile
 
     /**
+     * The project-level libraries to be added to the IDEA project.
+     */
+    @Incubating
+    Set<ProjectLibrary> projectLibraries = [] as LinkedHashSet
+
+    /**
      * The name of the IDEA project. It is a convenience property that returns the name of the output file (without the file extension).
      * In IDEA, the project name is driven by the name of the 'ipr' file.
      */
@@ -162,7 +174,7 @@ class IdeaProject {
         def modulePaths = getModules().collect {
             getPathFactory().relativePath('PROJECT_DIR', it.outputFile)
         }
-        xmlProject.configure(modulePaths, getJdkName(), getLanguageLevel(), getWildcards())
+        xmlProject.configure(modulePaths, getJdkName(), getLanguageLevel(), getWildcards(), getProjectLibraries())
         ipr.whenMerged.execute(xmlProject)
     }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
index 1a20ed6..19a0288 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Module.groovy
@@ -28,7 +28,7 @@ class Module extends XmlPersistableConfigurationObject {
     static final String INHERITED = "inherited"
 
     /**
-     * The directory for the content root of the module.  Defaults to the project dirctory.
+     * The directory for the content root of the module.  Defaults to the project directory.
      * If null, the directory containing the output file will be used.
      */
     Path contentPath
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 eaa7d5d..bf5d0f6 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ModuleLibrary.groovy
@@ -22,30 +22,33 @@ package org.gradle.plugins.ide.idea.model
  */
 class ModuleLibrary implements Dependency {
     /**
-     * A set of {@link Path} instances for class libraries. Can be paths to jars or class folders.
+     * A set of Jar files or directories containing compiled code.
      */
-    Set<Path> classes
+    Set<Path> classes = [] as LinkedHashSet
 
     /**
-     * A set of {@link JarDirectory} instances for directories containing jars.
+     * A set of directories containing Jar files.
      */
-    Set<JarDirectory> jarDirectories
+    Set<JarDirectory> jarDirectories = [] as LinkedHashSet
 
     /**
-     * A set of {@link Path} instances for javadoc associated with the library elements.
+     * A set of Jar files or directories containing Javadoc.
      */
-    Set<Path> javadoc
+    Set<Path> javadoc = [] as LinkedHashSet
 
     /**
-     * A set of {@link Path} instances for source code associated with the library elements.
+     * A set of Jar files or directories containing source code.
      */
-    Set<Path> sources
+    Set<Path> sources = [] as LinkedHashSet
 
     /**
-     * The scope of this dependency. If <tt>null</tt>, the scope attribute is not added.
+     * The scope of this library. If <tt>null</tt>, the scope attribute is not added.
      */
     String scope
 
+    /**
+     * Whether the library is exported to dependent modules.
+     */
     boolean exported
 
     def ModuleLibrary(Collection<Path> classes, Collection<Path> javadoc, Collection<Path> sources, Collection<JarDirectory> jarDirectories, String scope) {
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
index 5b37165..25a03ae 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Path.groovy
@@ -22,17 +22,17 @@ package org.gradle.plugins.ide.idea.model
  */
 class Path {
     /**
-     * The url of the path. Must not be null
+     * The url of the path. Must not be null.
      */
     final String url
 
     /**
-     * The relative path of the path. Must not be null
+     * The relative path of the path. Must not be null.
      */
     final String relPath
 
     /**
-     * Canonical url
+     * Canonical url.
      */
     final String canonicalUrl
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
index a0cdbd1..a4ce258 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/Project.groovy
@@ -15,6 +15,7 @@
  */
 package org.gradle.plugins.ide.idea.model
 
+import org.gradle.api.Incubating
 import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.plugins.ide.internal.generator.XmlPersistableConfigurationObject
 
@@ -39,19 +40,28 @@ class Project extends XmlPersistableConfigurationObject {
      */
     Jdk jdk
 
+    /**
+     * The project-level libraries of the IDEA project.
+     */
+    @Incubating
+    Set<ProjectLibrary> projectLibraries = [] as LinkedHashSet
+
     private final PathFactory pathFactory
 
-    def Project(XmlTransformer xmlTransformer, pathFactory) {
+    Project(XmlTransformer xmlTransformer, pathFactory) {
         super(xmlTransformer)
         this.pathFactory = pathFactory
     }
 
-    def configure(Collection<Path> modulePaths, String jdkName, IdeaLanguageLevel languageLevel, Collection<String> wildcards) {
+    void configure(Collection<Path> modulePaths, String jdkName, IdeaLanguageLevel languageLevel,
+                   Collection<String> wildcards, Collection<ProjectLibrary> projectLibraries) {
         if (jdkName) {
             jdk = new Jdk(jdkName, languageLevel)
         }
         this.modulePaths.addAll(modulePaths)
         this.wildcards.addAll(wildcards)
+        // overwrite rather than append libraries
+        this.projectLibraries = projectLibraries
     }
 
     @Override protected void load(Node xml) {
@@ -66,6 +76,8 @@ class Project extends XmlPersistableConfigurationObject {
 
         jdk = new Jdk(Boolean.parseBoolean(jdkValues.'assert-keyword'), Boolean.parseBoolean(jdkValues.'jdk-15'),
                 jdkValues.languageLevel, jdkValues.'project-jdk-name')
+
+        loadProjectLibraries()
     }
 
     @Override protected String getDefaultResourceName() {
@@ -91,17 +103,19 @@ class Project extends XmlPersistableConfigurationObject {
         findProjectRootManager().@'assert-jdk-15' = jdk.jdk15
         findProjectRootManager(). at languageLevel = jdk.languageLevel
         findProjectRootManager().@'project-jdk-name' = jdk.projectJdkName
+
+        storeProjectLibraries()
     }
 
-    private def findProjectRootManager() {
+    private findProjectRootManager() {
         return xml.component.find { it. at name == 'ProjectRootManager'}
     }
 
-    private def findWildcardResourcePatterns() {
+    private findWildcardResourcePatterns() {
         xml.component.find { it. at name == 'CompilerConfiguration'}.wildcardResourcePatterns
     }
 
-    private def findModules() {
+    private findModules() {
         def moduleManager = xml.component.find { it. at name == 'ProjectModuleManager'}
         if (!moduleManager.modules) {
             moduleManager.appendNode('modules')
@@ -109,6 +123,37 @@ class Project extends XmlPersistableConfigurationObject {
         moduleManager.modules
     }
 
+    private Node findLibraryTable() {
+        def libraryTable = xml.component.find { it. at name == 'libraryTable' }
+        if (!libraryTable) {
+            libraryTable = xml.appendNode('component', [name:  'libraryTable'])
+        }
+        libraryTable
+    }
+
+    private void loadProjectLibraries() {
+        def libraryTable = findLibraryTable()
+        for (library in libraryTable.library) {
+            def name = library. at name
+            def classes = library.CLASSES.root. at url.collect { new File(it) }
+            def javadoc = library.JAVADOC.root. at url.collect { new File(it) }
+            def sources = library.SOURCES.root. at url.collect { new File(it) }
+            projectLibraries << new ProjectLibrary(name: name, classes: classes, javadoc: javadoc, sources: sources)
+        }
+    }
+
+    private void storeProjectLibraries() {
+        Node libraryTable = findLibraryTable()
+        if (projectLibraries.empty) {
+            xml.remove(libraryTable)
+            return
+        }
+        libraryTable.value = new NodeList()
+        for (library in projectLibraries) {
+            library.addToNode(libraryTable, pathFactory)
+        }
+    }
+
     boolean equals(o) {
         if (this.is(o)) { return true }
 
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ProjectLibrary.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ProjectLibrary.groovy
new file mode 100644
index 0000000..75f84ee
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/idea/model/ProjectLibrary.groovy
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.plugins.ide.idea.model
+
+import org.gradle.api.Incubating
+
+/**
+ * A project-level IDEA library.
+ */
+ at Incubating
+class ProjectLibrary {
+    /**
+     * The name of the library.
+     */
+    String name
+
+    /**
+     * A set of Jar files or directories containing compiled code.
+     */
+    Set<File> classes = [] as LinkedHashSet
+
+    /**
+     * A set of Jar files or directories containing Javadoc.
+     */
+    Set<File> javadoc = [] as LinkedHashSet
+
+    /**
+     * A set of Jar files or directories containing source code.
+     */
+    Set<File> sources = [] as LinkedHashSet
+
+    void addToNode(Node parentNode, PathFactory pathFactory) {
+        def builder = new NodeBuilder()
+
+        def attributes = [name: name]
+
+        def library = builder.library(attributes) {
+            CLASSES {
+                for (file in classes) {
+                    root(url: pathFactory.path(file).url)
+                }
+            }
+            JAVADOC {
+                for (file in javadoc) {
+                    root(url: pathFactory.path(file).url)
+                }
+            }
+            SOURCES {
+                for (file in sources) {
+                    root(url: pathFactory.path(file).url)
+                }
+            }
+        }
+
+        parentNode.append(library)
+    }
+
+    boolean equals(Object obj) {
+        if (this.is(obj)) {
+            return true
+        }
+        if (!(obj instanceof ProjectLibrary)) {
+            return false
+        }
+
+        ProjectLibrary that = (ProjectLibrary) obj
+
+        if (classes != that.classes) {
+            return false
+        }
+        if (javadoc != that.javadoc) {
+            return false
+        }
+        if (name != that.name) {
+            return false
+        }
+        if (sources != that.sources) {
+            return false
+        }
+
+        return true
+    }
+
+    int hashCode() {
+        int result
+        result = name.hashCode()
+        result = 31 * result + classes.hashCode()
+        result = 31 * result + javadoc.hashCode()
+        result = 31 * result + sources.hashCode()
+        return result
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
index a52daa0..beb65d7 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/IdeDependenciesExtractor.groovy
@@ -17,9 +17,7 @@
 package org.gradle.plugins.ide.internal
 
 import org.gradle.api.Project
-import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
 import org.gradle.api.specs.Spec
-import org.gradle.api.specs.Specs
 import org.gradle.api.artifacts.*
 
 /**
@@ -72,23 +70,11 @@ class IdeDependenciesExtractor {
                                                            boolean downloadSources, boolean downloadJavadoc) {
         def out = []
 
-        def allResolvedDependencies = resolveDependencies(plusConfigurations, minusConfigurations)
-
-        Set sourceDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
-            addSourceArtifact(dependency)
-        }
-
-        Map<String, File> sourceFiles = downloadSources ? getFiles(confContainer.detachedConfiguration(sourceDependencies as Dependency[]), "sources") : [:]
-
-        Set javadocDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
-            addJavadocArtifact(dependency)
-        }
-
-        Map<String, File> javadocFiles = downloadJavadoc ? getFiles(confContainer.detachedConfiguration(javadocDependencies as Dependency[]), "javadoc") : [:]
+        def downloader = new JavadocAndSourcesDownloader(confContainer, plusConfigurations, minusConfigurations, downloadSources, downloadJavadoc)
 
         resolvedExternalDependencies(plusConfigurations, minusConfigurations).each { IdeRepoFileDependency dependency ->
-            dependency.sourceFile = sourceFiles[dependency.file.name]
-            dependency.javadocFile = javadocFiles[dependency.file.name]
+            dependency.sourceFile = downloader.sourceFor(dependency.file.name)
+            dependency.javadocFile = downloader.javadocFor(dependency.file.name)
             out << dependency
         }
 
@@ -136,7 +122,7 @@ class IdeDependenciesExtractor {
         }
     }
 
-    protected Collection<IdeRepoFileDependency> resolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+    public Collection<IdeRepoFileDependency> resolvedExternalDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
         LinkedHashMap<File, IdeRepoFileDependency> out = [:]
         for (plusConfiguration in plusConfigurations) {
             for (artifact in plusConfiguration.resolvedConfiguration.lenientConfiguration.getArtifacts({ it instanceof ExternalDependency } as Spec)) {
@@ -150,61 +136,4 @@ class IdeDependenciesExtractor {
         }
         out.values()
     }
-
-    private Set<ResolvedDependency> resolveDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
-        def result = new LinkedHashSet()
-        for (plusConfiguration in plusConfigurations) {
-            result.addAll(getAllDeps(plusConfiguration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
-        }
-        for (minusConfiguration in minusConfigurations) {
-            result.removeAll(getAllDeps(minusConfiguration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
-        }
-        result
-    }
-
-    private Set getAllDeps(Collection deps, Set allDeps = new LinkedHashSet()) {
-        deps.each { ResolvedDependency resolvedDependency ->
-            def notSeenBefore = allDeps.add(resolvedDependency)
-            if (notSeenBefore) { // defend against circular dependencies
-                getAllDeps(resolvedDependency.children, allDeps)
-            }
-        }
-        allDeps
-    }
-
-    private List getResolvableDependenciesForAllResolvedDependencies(Set allResolvedDependencies, Closure configureClosure) {
-        return allResolvedDependencies.collect { ResolvedDependency resolvedDependency ->
-            def dependency = new DefaultExternalModuleDependency(resolvedDependency.moduleGroup, resolvedDependency.moduleName, resolvedDependency.moduleVersion,
-                    resolvedDependency.configuration)
-            dependency.transitive = false
-            configureClosure.call(dependency)
-            dependency
-        }
-    }
-
-    private void addSourceArtifact(DefaultExternalModuleDependency dependency) {
-        dependency.artifact { artifact ->
-            artifact.name = dependency.name
-            artifact.type = 'source'
-            artifact.extension = 'jar'
-            artifact.classifier = 'sources'
-        }
-    }
-
-    private void addJavadocArtifact(DefaultExternalModuleDependency dependency) {
-        dependency.artifact { artifact ->
-            artifact.name = dependency.name
-            artifact.type = 'javadoc'
-            artifact.extension = 'jar'
-            artifact.classifier = 'javadoc'
-        }
-    }
-
-    private Map getFiles(Configuration configuration, String classifier) {
-        return (Map) configuration.resolvedConfiguration.lenientConfiguration.getFiles(Specs.satisfyAll()).inject([:]) { result, sourceFile ->
-            String key = sourceFile.name.replace("-${classifier}.jar", '.jar')
-            result[key] = sourceFile
-            result
-        }
-    }
 }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/JavadocAndSourcesDownloader.groovy b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/JavadocAndSourcesDownloader.groovy
new file mode 100644
index 0000000..e0e6d35
--- /dev/null
+++ b/subprojects/ide/src/main/groovy/org/gradle/plugins/ide/internal/JavadocAndSourcesDownloader.groovy
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.internal
+
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ResolvedDependency
+import org.gradle.api.artifacts.ExternalDependency
+import org.gradle.api.specs.Spec
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.specs.Specs
+import org.gradle.api.artifacts.ConfigurationContainer
+
+/**
+ * by Szczepan Faber, created at: 1/25/13
+ */
+class JavadocAndSourcesDownloader {
+
+    private Map<String, File> sourceFiles
+    private Map<String, File> javadocFiles
+
+    JavadocAndSourcesDownloader(ConfigurationContainer confContainer, Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations,
+                                boolean downloadSources, boolean downloadJavadoc) {
+        if (!downloadJavadoc && !downloadSources) {
+            return
+        }
+
+        def allResolvedDependencies = resolveDependencies(plusConfigurations, minusConfigurations)
+
+        if (downloadSources) {
+            Set sourceDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
+                addSourceArtifact(dependency)
+            }
+
+            sourceFiles = getFiles(confContainer.detachedConfiguration(sourceDependencies as Dependency[]), "sources")
+        }
+
+        if (downloadJavadoc) {
+            Set javadocDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
+                addJavadocArtifact(dependency)
+            }
+
+            javadocFiles = getFiles(confContainer.detachedConfiguration(javadocDependencies as Dependency[]), "javadoc")
+        }
+    }
+
+    File sourceFor(String name) {
+        sourceFiles?.get(name)
+    }
+
+    File javadocFor(String name) {
+        javadocFiles?.get(name)
+    }
+
+    private Set<ResolvedDependency> resolveDependencies(Collection<Configuration> plusConfigurations, Collection<Configuration> minusConfigurations) {
+        def result = new LinkedHashSet()
+        for (plusConfiguration in plusConfigurations) {
+            result.addAll(getAllDeps(plusConfiguration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
+        }
+        for (minusConfiguration in minusConfigurations) {
+            result.removeAll(getAllDeps(minusConfiguration.resolvedConfiguration.lenientConfiguration.getFirstLevelModuleDependencies({ it instanceof ExternalDependency } as Spec)))
+        }
+        result
+    }
+
+    private List getResolvableDependenciesForAllResolvedDependencies(Set allResolvedDependencies, Closure configureClosure) {
+        return allResolvedDependencies.collect { ResolvedDependency resolvedDependency ->
+            def dependency = new DefaultExternalModuleDependency(resolvedDependency.moduleGroup, resolvedDependency.moduleName, resolvedDependency.moduleVersion,
+                    resolvedDependency.configuration)
+            dependency.transitive = false
+            configureClosure.call(dependency)
+            dependency
+        }
+    }
+
+    private void addSourceArtifact(DefaultExternalModuleDependency dependency) {
+        dependency.artifact { artifact ->
+            artifact.name = dependency.name
+            artifact.type = 'source'
+            artifact.extension = 'jar'
+            artifact.classifier = 'sources'
+        }
+    }
+
+    private void addJavadocArtifact(DefaultExternalModuleDependency dependency) {
+        dependency.artifact { artifact ->
+            artifact.name = dependency.name
+            artifact.type = 'javadoc'
+            artifact.extension = 'jar'
+            artifact.classifier = 'javadoc'
+        }
+    }
+
+    private Map getFiles(Configuration configuration, String classifier) {
+        return (Map) configuration.resolvedConfiguration.lenientConfiguration.getFiles(Specs.satisfyAll()).inject([:]) { result, sourceFile ->
+            String key = sourceFile.name.replace("-${classifier}.jar", '.jar')
+            result[key] = sourceFile
+            result
+        }
+    }
+
+    private Set getAllDeps(Collection deps, Set allDeps = new LinkedHashSet()) {
+        deps.each { ResolvedDependency resolvedDependency ->
+            def notSeenBefore = allDeps.add(resolvedDependency)
+            if (notSeenBefore) { // defend against circular dependencies
+                getAllDeps(resolvedDependency.children, allDeps)
+            }
+        }
+        allDeps
+    }
+}
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
index 20da9c3..5e0fc4b 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/BuildModelAction.java
@@ -17,7 +17,9 @@ package org.gradle.tooling.internal.provider;
 
 import org.gradle.BuildResult;
 import org.gradle.GradleLauncher;
+import org.gradle.api.Action;
 import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.initialization.GradleLauncherAction;
 import org.gradle.initialization.ModelConfigurationListener;
 import org.gradle.initialization.TasksCompletionListener;
@@ -64,6 +66,7 @@ public class BuildModelAction implements GradleLauncherAction<ProjectVersion3> {
         } else {
             launcher.addListener(new ModelConfigurationListener() {
                 public void onConfigure(GradleInternal gradle) {
+                    ensureAllProjectsEvaluated(gradle);
                     model = builder.buildAll(gradle);
                 }
             });
@@ -71,6 +74,14 @@ public class BuildModelAction implements GradleLauncherAction<ProjectVersion3> {
         }
     }
 
+    private void ensureAllProjectsEvaluated(GradleInternal gradle) {
+        gradle.getRootProject().allprojects((Action) new Action<ProjectInternal>() {
+            public void execute(ProjectInternal projectInternal) {
+                projectInternal.evaluate();
+            }
+        });
+    }
+
     public ProjectVersion3 getResult() {
         return model;
     }
diff --git a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
index b42045d..02eb24a 100644
--- a/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
+++ b/subprojects/ide/src/main/groovy/org/gradle/tooling/internal/provider/IdeaModelBuilder.java
@@ -119,7 +119,6 @@ public class IdeaModelBuilder implements BuildsModel {
                 .setName(ideaModule.getName())
                 .setParent(ideaProject)
                 .setGradleProject(rootGradleProject.findByPath(ideaModule.getProject().getPath()))
-                .setModuleFileDir(ideaModule.getIml().getGenerateTo())
                 .setContentRoots(Collections.singletonList(contentRoot))
                 .setCompilerOutput(new DefaultIdeaCompilerOutput()
                     .setInheritOutputDirs(ideaModule.getInheritOutputDirs() != null ? ideaModule.getInheritOutputDirs() : false)
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectLibraryTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectLibraryTest.groovy
new file mode 100644
index 0000000..61909d3
--- /dev/null
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectLibraryTest.groovy
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.plugins.ide.idea.model
+
+import org.gradle.internal.SystemProperties
+
+import spock.lang.Specification
+
+class ProjectLibraryTest extends Specification {
+    def "has friendly defaults"() {
+        def library = new ProjectLibrary()
+
+        expect:
+        with(library) {
+            name == null
+            classes == [] as Set
+            sources == [] as Set
+            javadoc == [] as Set
+        }
+    }
+
+    def "defines equality as deep equality of all its properties"() {
+        expect:
+        new ProjectLibrary() == new ProjectLibrary()
+        new ProjectLibrary(name: "lib1") == new ProjectLibrary(name: "lib1")
+        new ProjectLibrary(name: "lib1", classes:  [new File("class/one"), new File("class/two")]) ==
+                new ProjectLibrary(name: "lib1", classes: [new File("class/two"), new File("class/one")])
+
+        new ProjectLibrary(name: "lib1") != new ProjectLibrary(name: "OTHER")
+        new ProjectLibrary(name: "lib1", classes:  [new File("class/one"), new File("class/two")]) !=
+                new ProjectLibrary(name: "lib1", classes:  [new File("class/one"), new File("class/OTHER")])
+    }
+
+    def "generates correct XML"() {
+        def userHome = new File(SystemProperties.javaIoTmpDir)
+
+        def lib = new ProjectLibrary(name: "lib",
+                classes: [new File(userHome, "class/one.jar"), new File(userHome, "class/two.jar")] as LinkedHashSet,
+                javadoc: [new File(userHome, "javadoc/one.jar"), new File(userHome, "javadoc/two.jar")] as LinkedHashSet,
+                sources: [new File(userHome, "source/one.jar"), new File(userHome, "source/two.jar")] as LinkedHashSet)
+
+        def pathFactory = new PathFactory()
+        pathFactory.addPathVariable("USER_HOME", userHome)
+
+        when:
+        def parent = new Node(null, "parent")
+        lib.addToNode(parent, pathFactory)
+
+        then:
+        def writer = new StringWriter()
+        def printer = new XmlNodePrinter(new IndentPrinter(writer))
+        printer.print(parent)
+        writer.toString().trim() == """
+<parent>
+  <library name="lib">
+    <CLASSES>
+      <root url="jar://\$USER_HOME\$/class/one.jar!/"/>
+      <root url="jar://\$USER_HOME\$/class/two.jar!/"/>
+    </CLASSES>
+    <JAVADOC>
+      <root url="jar://\$USER_HOME\$/javadoc/one.jar!/"/>
+      <root url="jar://\$USER_HOME\$/javadoc/two.jar!/"/>
+    </JAVADOC>
+    <SOURCES>
+      <root url="jar://\$USER_HOME\$/source/one.jar!/"/>
+      <root url="jar://\$USER_HOME\$/source/two.jar!/"/>
+    </SOURCES>
+  </library>
+</parent>
+""".trim()
+    }
+}
diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
index 14d3128..12b7c46 100644
--- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
+++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/idea/model/ProjectTest.groovy
@@ -43,7 +43,7 @@ class ProjectTest extends Specification {
 
         when:
         project.load(customProjectReader)
-        project.configure(modules, "1.6", new IdeaLanguageLevel("JDK_1_5"), ['?*.groovy'])
+        project.configure(modules, "1.6", new IdeaLanguageLevel("JDK_1_5"), ['?*.groovy'], [])
 
         then:
         project.modulePaths as Set == (customModules + modules) as Set
@@ -51,6 +51,17 @@ class ProjectTest extends Specification {
         project.jdk == new Jdk("1.6", new IdeaLanguageLevel(JavaVersion.VERSION_1_5))
     }
 
+    def "project libraries are overwritten with generated content"() {
+        def libraries = [new ProjectLibrary(name: "newlib", classes: [path("newlib1.jar")])]
+
+        when:
+        project.load(customProjectReader)
+        project.configure([], "1.6", new IdeaLanguageLevel("JDK_1_5"), [], libraries)
+
+        then:
+        project.projectLibraries as List == libraries
+    }
+
     def loadDefaults() {
         when:
         project.loadDefaults()
@@ -59,12 +70,13 @@ class ProjectTest extends Specification {
         project.modulePaths.size() == 0
         project.wildcards == [] as Set
         project.jdk == new Jdk(true, true, "JDK_1_5", null)
+        project.projectLibraries.empty
     }
 
     def toXml_shouldContainCustomValues() {
         when:
         project.loadDefaults()
-        project.configure([], "1.6", new IdeaLanguageLevel("JDK_1_5"), ['?*.groovy'])
+        project.configure([], "1.6", new IdeaLanguageLevel("JDK_1_5"), ['?*.groovy'], [])
         def xml = toXmlReader
         def other = new Project(new XmlTransformer(), pathFactory)
         other.load(xml)
diff --git a/subprojects/ide/src/test/resources/org/gradle/plugins/ide/idea/model/customProject.xml b/subprojects/ide/src/test/resources/org/gradle/plugins/ide/idea/model/customProject.xml
index ec189ec..3f93486 100644
--- a/subprojects/ide/src/test/resources/org/gradle/plugins/ide/idea/model/customProject.xml
+++ b/subprojects/ide/src/test/resources/org/gradle/plugins/ide/idea/model/customProject.xml
@@ -57,4 +57,15 @@
   <component name="VcsDirectoryMappings">
     <mapping directory="" vcs="Git" />
   </component>
+  <component name="libraryTable">
+  <library name="scala-compiler-2.10.0">
+    <CLASSES>
+      <root url="jar://$USER_HOME$/lib1.jar!/" />
+      <root url="jar://$USER_HOME$/lib2.jar!/" />
+      <root url="jar://$USER_HOME$/lib3.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
 </project>
diff --git a/subprojects/integ-test/integ-test.gradle b/subprojects/integ-test/integ-test.gradle
index aa85109..8be66bf 100644
--- a/subprojects/integ-test/integ-test.gradle
+++ b/subprojects/integ-test/integ-test.gradle
@@ -6,7 +6,7 @@ dependencies {
     integTestCompile project(':coreImpl')
     integTestCompile libraries.ant
     integTestCompile libraries.xmlunit
-    integTestCompile libraries.nekohtml
+    integTestCompile libraries.jsoup
 
     integTestRuntime rootProject.configurations.testRuntime.allDependencies
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
index 127188a..7dd6d35 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
@@ -55,7 +55,7 @@ public class CacheProjectIntegrationTest extends AbstractIntegrationTest {
         String version = GradleVersion.current().version
         projectDir = file("project")
         projectDir.mkdirs()
-        userHomeDir = new TestFile(executer.gradleUserHomeDir)
+        userHomeDir = executer.gradleUserHomeDir
         buildFile = projectDir.file('build.gradle')
         ScriptSource source = new UriScriptSource("build file", buildFile)
         propertiesFile = userHomeDir.file("caches/$version/scripts/$source.className/ProjectScript/no_buildscript/cache.properties")
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy
index fdd8b36..cbb3694 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CharacterEncodingIntegTest.groovy
@@ -17,13 +17,10 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
-import spock.lang.IgnoreIf
 import spock.lang.Unroll
 
 import java.nio.charset.Charset
 
- at IgnoreIf({ GradleContextualExecuter.embedded })
 class CharacterEncodingIntegTest extends AbstractIntegrationSpec {
 
     def executerEncoding(String inputEncoding) {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
index 097ab24..2195d06 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
@@ -31,12 +31,12 @@ import org.junit.Test
 
 public class CommandLineIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
     @Rule public final PreconditionVerifier verifier = new PreconditionVerifier()
 
     @Before
     void setup() {
-        executer.requireGradleHome(true)
+        executer.requireGradleHome()
     }
 
     @Test
@@ -131,7 +131,7 @@ public class CommandLineIntegrationTest extends AbstractIntegrationTest {
     @Test
     public void canSpecifySystemPropertiesUsingGradleOptsEnvironmentVariable() {
         // the actual testing is done in the build script.
-        executer.withTasks("checkSystemProperty").withGradleOpts('-DcustomProp1=custom-value', "-DcustomProp2=custom value").run();
+        executer.withTasks("checkSystemProperty").withEnvironmentVars("GRADLE_OPTS": '-DcustomProp1=custom-value "-DcustomProp2=custom value"').run();
     }
 
     @Test
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy
index 7d2030c..7ebf7c6 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy
@@ -17,14 +17,14 @@
 
 package org.gradle.integtests
 
+import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 import org.junit.Test
-import org.gradle.integtests.fixtures.AbstractIntegrationTest
 
 class ExecIntegrationTest extends AbstractIntegrationTest {
     @Rule
-    public final TestResources testResources = new TestResources()
+    public final TestResources testResources = new TestResources(testDirectoryProvider)
 
     @Test
     public void canExecuteJava() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
index 9a59e3f..0c075fc 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
@@ -28,7 +28,7 @@ import static org.hamcrest.Matchers.equalTo
 import static org.junit.Assert.assertThat
 
 class IncrementalBuildIntegrationTest extends AbstractIntegrationTest {
-    @Rule public final TestResources resource = new TestResources()
+    @Rule public final TestResources resource = new TestResources(testDirectoryProvider)
 
     @Test
     public void skipsTaskWhenOutputFileIsUpToDate() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
index db95eca..80c9a7b 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
@@ -16,17 +16,17 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.*
 
 class IncrementalTestIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
 
     @Before
     public void before() {
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
     }
 
     @Test
@@ -59,7 +59,7 @@ class IncrementalTestIntegrationTest extends AbstractIntegrationTest {
     public void executesTestsWhenSelectedTestsChange() {
         executer.withTasks('test').run()
 
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('JUnitTest')
 
         // Include more tests
@@ -81,7 +81,7 @@ class IncrementalTestIntegrationTest extends AbstractIntegrationTest {
         //TODO this exposes a possible problem: When changing the test framework stale xml result files from former test framework are still present.
         executer.withTasks('cleanTest', 'test').run().assertTasksNotSkipped(':cleanTest',':test')
 
-        result = new DefaultTestExecutionResult(testDirectory)
+        result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('TestNGTest')
 
         executer.withTasks('test').run().assertTasksNotSkipped()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
index b8bcbc2..28112e4 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
@@ -24,9 +24,9 @@ class InitScriptExecutionIntegrationTest extends AbstractIntegrationSpec {
     def "executes init.gradle from user home dir"() {
         given:
         executer.requireOwnGradleUserHomeDir()
-        
+
         and:
-        gradleUserHomeDir.file('init.gradle') << 'println "greetings from user home"'
+        executer.gradleUserHomeDir.file('init.gradle') << 'println "greetings from user home"'
 
         when:
         run()
@@ -35,26 +35,22 @@ class InitScriptExecutionIntegrationTest extends AbstractIntegrationSpec {
         output.contains("greetings from user home")
     }
 
-    protected TestFile getGradleUserHomeDir() {
-        new TestFile(executer.gradleUserHomeDir)
-    }
-
     def "executes init scripts from init.d directory in user home dir in alphabetical order"() {
         given:
         executer.requireOwnGradleUserHomeDir()
 
         and:
-        gradleUserHomeDir.file('init.d/a.gradle') << 'println "init #a#"'
-        gradleUserHomeDir.file('init.d/b.gradle') << 'println "init #b#"'
-        gradleUserHomeDir.file('init.d/c.gradle') << 'println "init #c#"'
+        executer.gradleUserHomeDir.file('init.d/a.gradle') << 'println "init #a#"'
+        executer.gradleUserHomeDir.file('init.d/b.gradle') << 'println "init #b#"'
+        executer.gradleUserHomeDir.file('init.d/c.gradle') << 'println "init #c#"'
 
         when:
         run()
 
         then:
-        def a = output.indexOf('init #a#') 
-        def b = output.indexOf('init #b#') 
-        def c = output.indexOf('init #c#') 
+        def a = output.indexOf('init #a#')
+        def b = output.indexOf('init #b#')
+        def c = output.indexOf('init #c#')
         a < b
         b < c
     }
@@ -129,7 +125,7 @@ try {
         then:
         notThrown(Throwable)
     }
-    
+
     def "init script can inject configuration into the root project and all projects"() {
         given:
         settingsFile << "include 'a', 'b'"
@@ -143,7 +139,7 @@ rootProject {
     task root(dependsOn: allprojects*.worker)
 }
         """
-        
+
         when:
         executer.withArguments("-I", "init.gradle")
         run "root"
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
index e38d3e3..bb2b47f 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.groovy
@@ -19,6 +19,7 @@ package org.gradle.integtests
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.executer.ExecutionFailure
 import org.gradle.test.fixtures.file.TestFile
+import org.junit.Ignore
 import org.junit.Test
 
 class JavaProjectIntegrationTest extends AbstractIntegrationTest {
@@ -120,6 +121,8 @@ version = ''
         testFile("build/libs/empty.jar").assertIsFile();
     }
 
+    // TODO: translate to new source set/packaging model
+    @Ignore
     @Test
     public void "task registered as a builder of resources is executed"() {
         TestFile buildFile = testFile("build.gradle");
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
index c6a48c0..6d04f14 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/MultiProjectDependencyIntegrationTest.groovy
@@ -249,7 +249,10 @@ project(':$from') {
     def failingBuild(def project) {
         buildFile << """
 project(':$project') {
-    task fail << {
+    //fail needs to have a dependendency on compileJava
+    //this way all the java project dependencies are built first in paralle mode
+    //if 'fail 'does not have any dependencies it will be scheduled to execute very early in parallel mode
+    task fail(dependsOn: compileJava) << {
         throw new RuntimeException('failure in $project')
     }
     jar.dependsOn fail
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
index 9e31b9e..6e332b9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
@@ -33,7 +33,7 @@ import static org.junit.Assert.assertTrue
  */
 class OsgiProjectSampleIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('osgi')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'osgi')
 
     @Test
     public void osgiProjectSamples() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy
index 64b9490..556ab0b 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ParallelProjectExecutionIntegrationTest.groovy
@@ -29,6 +29,7 @@ public class ParallelProjectExecutionIntegrationTest extends AbstractIntegration
 
         settingsFile << 'include "a", "b", "c", "d"'
         buildFile << """
+assert gradle.startParameter.parallelThreadCount != 0
 allprojects {
     task pingServer << {
         URL url = new URL("http://localhost:${blockingServer.port}/" + project.path)
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy
index 7861a0a..923585e 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProfilingIntegrationTest.groovy
@@ -16,7 +16,8 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.cyberneko.html.parsers.SAXParser
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
 
 class ProfilingIntegrationTest extends AbstractIntegrationSpec {
     def "can generate profiling report"() {
@@ -26,23 +27,15 @@ allprojects {
     apply plugin: 'java'
 }
 '''
-
         when:
         executer.withArguments("--profile").withTasks("build").run()
 
         then:
         def reportFile = file('build/reports/profile').listFiles().find { it.name ==~ /profile-.+.html/ }
-        Node content = parse(reportFile)
-        content.depthFirst().find { it.name() == 'TD' && it.text() == ':jar' }
-        content.depthFirst().find { it.name() == 'TD' && it.text() == ':a:jar' }
-        content.depthFirst().find { it.name() == 'TD' && it.text() == ':b:jar' }
-        content.depthFirst().find { it.name() == 'TD' && it.text() == ':c:jar' }
-    }
-
-    private Node parse(File reportFile) {
-        def text = reportFile.getText('utf-8').readLines()
-        def withoutDocType = text.subList(1, text.size()).join('\n')
-        def content = new XmlParser(new SAXParser()).parseText(withoutDocType)
-        return content
+        Document document = Jsoup.parse(reportFile, null);
+        !document.select("TD:contains(:jar)").isEmpty()
+        !document.select("TD:contains(:a:jar)").isEmpty()
+        !document.select("TD:contains(:b:jar)").isEmpty()
+        !document.select("TD:contains(:c:jar)").isEmpty()
     }
-}
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
index 7c5dd03..55769d0 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.integtests
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -24,7 +24,7 @@ import org.junit.Test
 
 class ProjectLayoutIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
 
     @Test
     public void canHaveSomeSourceAndResourcesInSameDirectoryAndSomeInDifferentDirectories() {
@@ -168,7 +168,7 @@ sourceSets.main.java {
 
         file('build').assertDoesNotExist()
 
-        DefaultTestExecutionResult results = new DefaultTestExecutionResult(file(), 'target')
+        JUnitXmlTestExecutionResult results = new JUnitXmlTestExecutionResult(file(), 'target')
         results.assertTestClassesExecuted('PersonTest')
         results.testClass('PersonTest').assertTestsExecuted('ok')
     }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
index 2efbf39..68b74d6 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
@@ -47,7 +47,7 @@ class WaterProjectIntegrationTest {
     private final GradleDistribution dist = new UnderDevelopmentGradleDistribution();
     private final GradleExecuter executer = new GradleContextualExecuter(dist, temporaryFolder);
 
-    @Rule public final Sample sample = new Sample(WATER_NAME)
+    @Rule public final Sample sample = new Sample(temporaryFolder, WATER_NAME)
 
     @Test
     public void waterProject() {
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 7535154..ab3ca6a 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
@@ -159,7 +159,7 @@ assert classesDir.directory
 
         when:
         // Need the forking executer for this to work. Embedded executer will not fork a new process if jvm doesn't match.
-        def out = executer.requireGradleHome(true).run().output
+        def out = executer.requireGradleHome().run().output
 
         then:
         out.contains("javaHome=" + alternateJavaHome.canonicalPath)
@@ -174,7 +174,7 @@ assert System.getProperty('some-prop') == 'some-value'
 """
 
         when:
-        executer.requireGradleHome(true).withNoDefaultJvmArgs().run()
+        executer.requireGradleHome().withNoDefaultJvmArgs().run()
 
         then:
         noExceptionThrown()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest.groovy
index 4ac5aaa..08df3f8 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggerIsEnabledIntegrationTest.groovy
@@ -22,7 +22,7 @@ import org.junit.Rule
 import spock.lang.Unroll
 
 class LoggerIsEnabledIntegrationTest extends AbstractIntegrationSpec {
-    @Rule TestResources resources
+    @Rule TestResources resources = new TestResources(temporaryFolder)
 
     @Unroll
     def "logger.isEnabled() works correctly for log level #level"() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
index a19ee7b..6f20307 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/logging/LoggingIntegrationTest.groovy
@@ -31,8 +31,8 @@ import org.junit.Test
  */
 class LoggingIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final TestResources resources = new TestResources()
-    @Rule public final Sample sampleResources = new Sample()
+    @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
+    @Rule public final Sample sampleResources = new Sample(testDirectoryProvider)
 
     private final LogOutput logOutput = new LogOutput() {{
         quiet(
@@ -225,22 +225,22 @@ class LoggingIntegrationTest extends AbstractIntegrationTest {
 
         String initScript = new File(loggingDir, 'init.gradle').absolutePath
         String[] allArgs = level.args + ['-I', initScript]
-        return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withArguments(allArgs).withTasks('log').run()
+        return executer.noExtraLogging().inDirectory(loggingDir).withArguments(allArgs).withTasks('log').run()
     }
 
     def runBroken(LogLevel level) {
         TestFile loggingDir = testDirectory
 
-        return executer.setAllowExtraLogging(false).inDirectory(loggingDir).withTasks('broken').runWithFailure()
+        return executer.noExtraLogging().inDirectory(loggingDir).withTasks('broken').runWithFailure()
     }
 
     def runMultiThreaded(LogLevel level) {
         resources.maybeCopy('LoggingIntegrationTest/multiThreaded')
-        return executer.setAllowExtraLogging(false).withArguments(level.args).withTasks('log').run()
+        return executer.noExtraLogging().withArguments(level.args).withTasks('log').run()
     }
 
     def runSample(LogLevel level) {
-        return executer.setAllowExtraLogging(false).inDirectory(sampleResources.dir).withArguments(level.args).withTasks('log').run()
+        return executer.noExtraLogging().inDirectory(sampleResources.dir).withArguments(level.args).withTasks('log').run()
     }
 
     void checkOutput(Closure run, LogLevel level) {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
index 7ab7452..6719171 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyHttpPublishIntegrationTest.groovy
@@ -43,7 +43,7 @@ credentials {
     @Rule
     public final HttpServer server = new HttpServer()
 
-    @Rule ProgressLoggingFixture progressLogging
+    @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
 
     private IvyModule module
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
index fbcb054..0de942a 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
@@ -53,7 +53,11 @@ uploadArchives {
         then:
         def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
         ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.jar")
-        ivyModule.ivy.dependencies.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
-        ivyModule.ivy.dependencies.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
+
+        with (ivyModule.ivy) {
+            dependencies.size() == 2
+            dependencies["commons-collections:commons-collections:3.2.1"].hasConf("compile->default")
+            dependencies["commons-io:commons-io:1.4"].hasConf("runtime->default")
+        }
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy
index 68be4c8..6247e45 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/IvySFtpPublishIntegrationTest.groovy
@@ -25,7 +25,7 @@ class IvySFtpPublishIntegrationTest extends AbstractIntegrationSpec {
     @Rule
     public final SFTPServer sftpServer = new SFTPServer(this)
     @Rule
-    ProgressLoggingFixture progressLogging
+    ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
 
     public void "can publish using SftpResolver"() {
         given:
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
index 4902ea05..13cc507 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
@@ -26,7 +26,7 @@ import org.junit.Test
 public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationTest {
 
     @Rule
-    public final Sample sample = new Sample("ivypublish")
+    public final Sample sample = new Sample(testDirectoryProvider, "ivypublish")
 
     @Test
     public void testPublish() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
index bb54f80..55c6bb5 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenJavaProjectPublishIntegrationTest.groovy
@@ -54,7 +54,7 @@ uploadArchives {
         then:
         def mavenModule = mavenRepo.module("org.gradle.test", "publishTest", "1.9")
         mavenModule.assertArtifactsPublished("publishTest-1.9.pom", "publishTest-1.9.jar")
-        mavenModule.parsedPom.scopes.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
-        mavenModule.parsedPom.scopes.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
+        mavenModule.parsedPom.scopes.compile.assertDependsOn("commons-collections:commons-collections:3.2.1")
+        mavenModule.parsedPom.scopes.runtime.assertDependsOn("commons-io:commons-io:1.4")
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
index 957ee83..3997356 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenMultiProjectPublishIntegrationTest.groovy
@@ -36,7 +36,7 @@ project(":project1") {
 
         then:
         def pom = mavenModule.parsedPom
-        pom.scopes.compile.assertDependsOn("org.gradle.test", "project2", "1.9")
+        pom.scopes.compile.assertDependsOn("org.gradle.test:project2:1.9")
     }
 
     @Issue("GRADLE-443")
@@ -58,7 +58,7 @@ project(":project2") {
 
         then:
         def pom = mavenModule.parsedPom
-        pom.scopes.compile.assertDependsOn("org.gradle.test", "changed", "1.9")
+        pom.scopes.compile.assertDependsOn("org.gradle.test:changed:1.9")
     }
 
     @Issue("GRADLE-443")
@@ -84,7 +84,7 @@ project(":project2") {
 
         then:
         def pom = mavenModule.parsedPom
-        pom.scopes.compile.assertDependsOn("org.gradle.test", "changed", "1.9")
+        pom.scopes.compile.assertDependsOn("org.gradle.test:changed:1.9")
     }
 
     def "project dependency correctly reflected in POM if second artifact is published which differs in classifier"() {
@@ -111,7 +111,7 @@ project(":project2") {
 
         then:
         def pom = mavenModule.parsedPom
-        pom.scopes.compile.assertDependsOn("org.gradle.test", "project2", "1.9")
+        pom.scopes.compile.assertDependsOn("org.gradle.test:project2:1.9")
     }
 
     private void createBuildScripts(String append = "") {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIgnoresMavenSettingsTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIgnoresMavenSettingsTest.groovy
new file mode 100644
index 0000000..102b10d
--- /dev/null
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIgnoresMavenSettingsTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.publish.maven
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+import spock.lang.Ignore
+import spock.lang.Issue
+
+class MavenPublishIgnoresMavenSettingsTest extends AbstractIntegrationSpec {
+    @Rule
+    public final HttpServer server = new HttpServer()
+
+    @Ignore
+    @Issue("GRADLE-2681")
+    def "gradle ignores maven mirror configuration for uploading archives"() {
+        given:
+
+        TestFile m2Home = temporaryFolder.createDir("m2_home");
+        m2Home.file("conf/settings.xml").createFile().text = """
+<settings>
+  <mirrors>
+    <mirror>
+      <id>ACME</id>
+      <name>ACME Central</name>
+      <url>http://acme.maven.org/maven2</url>
+      <mirrorOf>*</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+"""
+        settingsFile << "rootProject.name = 'root'"
+        executer.withEnvironmentVars(M2_HOME: m2Home.absolutePath)
+        buildFile << """
+   apply plugin: 'java'
+   apply plugin: 'maven'
+   group = 'group'
+   version = '1.0'
+   uploadArchives {
+       repositories {
+           mavenDeployer {
+               repository(url: "${mavenRepo.uri}")
+           }
+       }
+   }
+   """
+        when:
+        run("uploadArchives")
+        then:
+        !result.output.contains("Uploading: group/root/1.0/root-1.0.jar to repository ACME at http://acme.maven.org/maven2")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
index 716242c..68151a1 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishIntegrationTest.groovy
@@ -200,7 +200,7 @@ uploadArchives {
 
         then:
         def module = mavenRepo.module('org.gradle', 'test', '1.0-SNAPSHOT')
-        module.assertArtifactsPublished('test-1.0-SNAPSHOT.jar', 'test-1.0-SNAPSHOT.pom')
+        module.assertArtifactsPublished("test-${module.publishArtifactVersion}.jar", "test-${module.publishArtifactVersion}.pom")
     }
 
     def "can publish multiple deployments with attached artifacts"() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
index bc44445..238c030 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/MavenPublishRespectsPomConfigurationTest.groovy
@@ -77,6 +77,6 @@ class MavenPublishRespectsPomConfigurationTest extends AbstractIntegrationSpec {
         project1Module.assertArtifactsPublished("custom_project1-0.1.pom", "custom_project1-0.1.jar")
         def project2Module = mavenRepo.module("org.gradle.test", "project2", "0.1")
         project2Module.assertArtifactsPublished("project2-0.1.pom", "project2-0.1.jar")
-        project2Module.pom.scopes.compile.assertDependsOn("org.gradle.test", "custom_project1", "0.1")
+        project2Module.parsedPom.scopes.compile.assertDependsOn("org.gradle.test:custom_project1:0.1")
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
index 0c30ded..29598c4 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenPomGenerationIntegrationTest.groovy
@@ -37,7 +37,7 @@ class SamplesMavenPomGenerationIntegrationTest extends AbstractIntegrationTest {
     private TestFile pomProjectDir
 
     @Rule public Resources resources = new Resources();
-    @Rule public final Sample sample = new Sample('maven/pomGeneration')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'maven/pomGeneration')
 
     @Before
     void setUp() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
index 6e8ef36..f40ad61 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/publish/maven/SamplesMavenQuickstartIntegrationTest.groovy
@@ -33,7 +33,7 @@ import org.junit.Test
  */
 class SamplesMavenQuickstartIntegrationTest extends AbstractIntegrationTest {
     @Rule public Resources resources = new Resources();
-    @Rule public final Sample sample = new Sample('maven/quickstart')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'maven/quickstart')
 
     private TestFile pomProjectDir
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy
index 8aa2300..20dd7c4 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAnnounceIntegrationTest.groovy
@@ -22,7 +22,7 @@ import org.junit.Rule
 
 class SamplesAnnounceIntegrationTest extends AbstractIntegrationSpec {
 
-    @Rule Sample sample = new Sample("announce")
+    @Rule Sample sample = new Sample(temporaryFolder, "announce")
 
     def "make some announcements"() {
         // tweak sample to print all messages to standard out
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
index 253723d..c0afe98 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesAntlrIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -24,7 +24,7 @@ import org.junit.Test
 
 class SamplesAntlrIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('antlr')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'antlr')
 
     @Test
     public void canBuild() {
@@ -34,7 +34,7 @@ class SamplesAntlrIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').withArguments("--no-opt").run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.GrammarTest')
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy
index 51fae82..79f7e06 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesApplicationIntegrationTest.groovy
@@ -23,7 +23,7 @@ import org.junit.Rule
 
 class SamplesApplicationIntegrationTest extends AbstractIntegrationSpec {
 
-    @Rule Sample sample = new Sample('application')
+    @Rule Sample sample = new Sample(temporaryFolder, 'application')
 
     def canRunTheApplicationUsingRunTask() {
         when:
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesClientModuleDependenciesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesClientModuleDependenciesIntegrationTest.groovy
index b5191fa..706b1b9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesClientModuleDependenciesIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesClientModuleDependenciesIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 
 class SamplesClientModuleDependenciesIntegrationTest extends AbstractIntegrationSpec {
-    @Rule Sample sample = new Sample("clientModuleDependencies")
+    @Rule Sample sample = new Sample(temporaryFolder, "clientModuleDependencies")
 
     def "resolve shared"() {
         inDirectory(sample.dir.file("shared"))
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
index 086748d..e603b85 100755
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCodeQualityIntegrationTest.groovy
@@ -26,14 +26,14 @@ import org.junit.Test
  */
 class SamplesCodeQualityIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('codeQuality')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'codeQuality')
 
     @Test
     public void checkReportsGenerated() {
         TestFile projectDir = sample.dir
         TestFile buildDir = projectDir.file('build')
 
-        executer.inDirectory(projectDir).requireGradleHome(true).withTasks('check').run()
+        executer.inDirectory(projectDir).requireGradleHome().withTasks('check').run()
 
         buildDir.file('reports/checkstyle/main.xml').assertIsFile()
         buildDir.file('reports/codenarc/main.html').assertIsFile()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy
index 77b831b..ac71e4d 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomBuildLanguageIntegrationTest.groovy
@@ -23,7 +23,7 @@ import org.junit.Test
 
 class SamplesCustomBuildLanguageIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('customBuildLanguage')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'customBuildLanguage')
 
     @Test
     public void testBuildProductDistributions() {
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 2a86406..de6acc3 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesCustomPluginIntegrationTest.groovy
@@ -16,12 +16,12 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.junit.Rule
 
 class SamplesCustomPluginIntegrationTest extends AbstractIntegrationSpec {
-    @Rule public final Sample sample = new Sample('customPlugin')
+    @Rule public final Sample sample = new Sample(temporaryFolder, 'customPlugin')
 
     def getProducerDir() {
         return sample.dir.file('plugin')
@@ -36,7 +36,7 @@ class SamplesCustomPluginIntegrationTest extends AbstractIntegrationSpec {
         executer.inDirectory(producerDir).withTasks('check').run()
 
         then:
-        def result = new DefaultTestExecutionResult(producerDir)
+        def result = new JUnitXmlTestExecutionResult(producerDir)
         result.assertTestClassesExecuted('org.gradle.GreetingTaskTest', 'org.gradle.GreetingPluginTest')
     }
 
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
index 0d9b4ce..365d1a8 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesExcludesAndClassifiersIntegrationTest.groovy
@@ -30,7 +30,7 @@ import static org.junit.Assert.assertThat
  */
 class SamplesExcludesAndClassifiersIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('userguide/artifacts/excludesAndClassifiers')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'userguide/artifacts/excludesAndClassifiers')
 
     @Test
     public void checkExcludeAndClassifier() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
index f8ec1c4..a7407d9 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -25,7 +25,7 @@ import org.junit.Test
 
 class SamplesGroovyCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('groovy/customizedLayout')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'groovy/customizedLayout')
 
     @Test
     public void groovyProjectQuickstartSample() {
@@ -33,7 +33,7 @@ class SamplesGroovyCustomizedLayoutIntegrationTest extends AbstractIntegrationTe
         executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(groovyProjectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(groovyProjectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyMultiProjectIntegrationTest.groovy
index cd49402..cf34f36 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
@@ -30,7 +30,7 @@ import static org.hamcrest.Matchers.containsString
 class SamplesGroovyMultiProjectIntegrationTest extends AbstractIntegrationTest {
     static final String TEST_PROJECT_NAME = 'testproject'
 
-    @Rule public final Sample sample = new Sample('groovy/multiproject')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'groovy/multiproject')
 
     private List mainFiles = ['JavaPerson', 'GroovyPerson', 'GroovyJavaPerson']
     private List excludedFiles = ['ExcludeJava', 'ExcludeGroovy', 'ExcludeGroovyJava']
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
index 933d789..e5c2a28 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesGroovyQuickstartIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -25,7 +25,7 @@ import org.junit.Test
 
 class SamplesGroovyQuickstartIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('groovy/quickstart')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'groovy/quickstart')
 
     @Test
     public void groovyProjectQuickstartSample() {
@@ -33,7 +33,7 @@ class SamplesGroovyQuickstartIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(groovyProjectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(groovyProjectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy
index f3bed10..df718fe 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaApiAndImplIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.junit.Rule
 
 class SamplesJavaApiAndImplIntegrationTest extends AbstractIntegrationSpec {
 
-    @Rule public final Sample apiAndImpl = new Sample('java/apiAndImpl')
+    @Rule public final Sample apiAndImpl = new Sample(temporaryFolder, 'java/apiAndImpl')
 
     static combined = ""
     static api = "-api"
@@ -53,9 +53,9 @@ class SamplesJavaApiAndImplIntegrationTest extends AbstractIntegrationSpec {
         module(combined).assertArtifactsPublished("apiAndImpl-1.0.jar", "apiAndImpl-1.0.pom")
 
         and: // poms have the right dependencies
-        compileDependenciesOf(api).assertDependsOnArtifacts("commons-codec")
-        compileDependenciesOf(impl).assertDependsOnArtifacts("commons-lang")
-        compileDependenciesOf(combined).assertDependsOnArtifacts("commons-lang", "commons-codec")
+        compileDependenciesOf(api).assertDependsOn("commons-codec:commons-codec:1.5")
+        compileDependenciesOf(impl).assertDependsOn("commons-lang:commons-lang:2.6")
+        compileDependenciesOf(combined).assertDependsOn("commons-lang:commons-lang:2.6", "commons-codec:commons-codec:1.5")
 
         and: // the fat jar contains classes from api and impl
         jar(combined).file("doubler/Doubler.class").exists()
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
index b26a29d..6313f15 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaBaseIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -29,7 +29,7 @@ import org.junit.Test
 
 class SamplesJavaBaseIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('java/base')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/base')
 
     @Test
     public void canBuildAndUploadJar() {
@@ -39,7 +39,7 @@ class SamplesJavaBaseIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir.file('test'))
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir.file('test'))
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
index ad35772..7424337 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaCustomizedLayoutIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -29,7 +29,7 @@ import org.junit.Test
 
 class SamplesJavaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('java/customizedLayout')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/customizedLayout')
 
     @Test
     public void canBuildAndUploadJar() {
@@ -39,7 +39,7 @@ class SamplesJavaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
index 7b828cb..aafa6f5 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaMultiProjectIntegrationTest.groovy
@@ -19,7 +19,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Before
@@ -43,7 +43,7 @@ class SamplesJavaMultiProjectIntegrationTest extends AbstractIntegrationTest {
     private TestFile javaprojectDir
     private List projects;
 
-    @Rule public final Sample sample = new Sample('java/multiproject')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/multiproject')
 
     @Before
     void setUp() {
@@ -64,7 +64,7 @@ class SamplesJavaMultiProjectIntegrationTest extends AbstractIntegrationTest {
         TestFile buildSrcDir = javaprojectDir.file('buildSrc')
 
         buildSrcDir.file('build/libs/buildSrc.jar').assertIsFile()
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(buildSrcDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(buildSrcDir)
         result.assertTestClassesExecuted('org.gradle.buildsrc.BuildSrcClassTest')
     }
 
@@ -85,10 +85,10 @@ class SamplesJavaMultiProjectIntegrationTest extends AbstractIntegrationTest {
         assertExists(javaprojectDir, WEBAPP_PATH, packagePrefix, WEBAPP_NAME, 'TestTest.class')
 
         // Check test results and report
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir.file(SHARED_NAME))
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir.file(SHARED_NAME))
         result.assertTestClassesExecuted('org.gradle.shared.PersonTest')
 
-        result = new DefaultTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
+        result = new JUnitXmlTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
         result.assertTestClassesExecuted('org.gradle.webservice.TestTestTest')
 
         // Check contents of shared jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy
index 091578f..a5e4f1e 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaOnlyIfIntegrationTest.groovy
@@ -25,7 +25,7 @@ import org.junit.Test
 
 public class SamplesJavaOnlyIfIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('java/onlyif')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/onlyif')
 
     /**
      * runs a build 3 times.
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
index a3e4f68..da03b3c 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -28,7 +28,7 @@ import org.junit.Test
  */
 class SamplesJavaProjectWithIntTestsIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('java/withIntegrationTests')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/withIntegrationTests')
 
     @Test
     public void canRunIntegrationTests() {
@@ -38,7 +38,7 @@ class SamplesJavaProjectWithIntTestsIntegrationTest extends AbstractIntegrationT
         executer.inDirectory(javaprojectDir).withTasks('clean', 'integrationTest').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonIntegrationTest')
     }
 }
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
index 631cbc1..c5cfe15 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesJavaQuickstartIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -33,7 +33,7 @@ import static org.junit.Assert.assertThat
  */
 class SamplesJavaQuickstartIntegrationTest extends  AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('java/quickstart')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/quickstart')
 
     @Test
     public void canBuildAndUploadJar() {
@@ -43,7 +43,7 @@ class SamplesJavaQuickstartIntegrationTest extends  AbstractIntegrationTest {
         executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(javaprojectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(javaprojectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check jar exists
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
index e81d5b7..41c20b4 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndGroovyIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -27,7 +27,7 @@ import static org.hamcrest.Matchers.containsString
 
 class SamplesMixedJavaAndGroovyIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('groovy/mixedJavaAndGroovy')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'groovy/mixedJavaAndGroovy')
 
     @Test
     public void canBuildJar() {
@@ -35,7 +35,7 @@ class SamplesMixedJavaAndGroovyIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.PersonTest')
 
         // Check contents of jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
index a343694..19af0b6 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesMixedJavaAndScalaIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -27,7 +27,7 @@ import static org.hamcrest.Matchers.containsString
 
 class SamplesMixedJavaAndScalaIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('scala/mixedJavaAndScala')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/mixedJavaAndScala')
 
     @Test
     public void canBuildJar() {
@@ -37,7 +37,7 @@ class SamplesMixedJavaAndScalaIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.sample.PersonTest')
 
         // Check contents of Jar
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 aced1e3..4263c25 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
@@ -22,7 +22,7 @@ import org.junit.Rule
 
 class SamplesMultiProjectBuildSrcIntegrationTest extends AbstractIntegrationSpec {
 
-  @Rule public final Sample sample = new Sample()
+  @Rule public final Sample sample = new Sample(temporaryFolder)
 
   @UsesSample("multiProjectBuildSrc")
   def "plugins from buildSrc subprojects are available"() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
index 05371b8..83a82d8 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesRepositoriesIntegrationTest.groovy
@@ -29,7 +29,7 @@ import static org.junit.Assert.assertThat
  */
 class SamplesRepositoriesIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('userguide/artifacts/defineRepository')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'userguide/artifacts/defineRepository')
 
     @Test
     public void repositoryNotations() {
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
index fd64a47..069a7a7 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaCustomizedLayoutIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Rule
@@ -25,7 +25,7 @@ import org.junit.Test
 
 class SamplesScalaCustomizedLayoutIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('scala/customizedLayout')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/customizedLayout')
 
     @Test
     public void canBuildJar() {
@@ -35,7 +35,7 @@ class SamplesScalaCustomizedLayoutIntegrationTest extends AbstractIntegrationTes
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
 
         // Check contents of Jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
index 0f9e8f6..9ff9711 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaQuickstartIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.integtests.samples
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.test.fixtures.file.TestFile
 import org.junit.Before
@@ -28,7 +28,7 @@ import static org.hamcrest.Matchers.containsString
 
 class SamplesScalaQuickstartIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample('scala/quickstart')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'scala/quickstart')
 
     private TestFile projectDir
 
@@ -43,7 +43,7 @@ class SamplesScalaQuickstartIntegrationTest extends AbstractIntegrationTest {
         executer.inDirectory(projectDir).withTasks('clean', 'build').run()
 
         // Check tests have run
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(projectDir)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(projectDir)
         result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
 
         // Check contents of Jar
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
index adbedba..42bd699 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesScalaZincIntegrationTest.groovy
@@ -26,7 +26,7 @@ import spock.lang.IgnoreIf
 @IgnoreIf({!JavaVersion.current().java6Compatible})
 class SamplesScalaZincIntegrationTest extends AbstractIntegrationSpec {
 
-    @Rule Sample sample = new Sample('scala/zinc')
+    @Rule Sample sample = new Sample(temporaryFolder, 'scala/zinc')
 
     def canBuildJar() {
         given:
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 b8d2338..2ea1387 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
@@ -27,7 +27,7 @@ import org.junit.Rule
 class SamplesWebProjectIntegrationTest extends AbstractIntegrationSpec {
     static final String WEB_PROJECT_NAME = 'customised'
 
-    @Rule public final Sample sample = new Sample('webApplication/customised')
+    @Rule public final Sample sample = new Sample(temporaryFolder, 'webApplication/customised')
 
     def "can build war"() {
         when:
diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy
index 2891460..03bdf3e 100644
--- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy
+++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/SamplesWebQuickstartIntegrationTest.groovy
@@ -27,7 +27,7 @@ import spock.lang.Unroll
  * @author Hans Dockter
  */
 class SamplesWebQuickstartIntegrationTest extends AbstractIntegrationSpec {
-    @Rule public final Sample sample = new Sample('webApplication/quickstart')
+    @Rule public final Sample sample = new Sample(temporaryFolder, 'webApplication/quickstart')
 
     def "can build a war"() {
         given:
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDependencyResolutionTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDependencyResolutionTest.groovy
index e9e5fcd..ec97881 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDependencyResolutionTest.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AbstractDependencyResolutionTest.groovy
@@ -18,6 +18,7 @@ package org.gradle.integtests.fixtures
 
 import org.gradle.test.fixtures.ivy.IvyFileRepository
 import org.gradle.test.fixtures.ivy.IvyHttpRepository
+import org.gradle.test.fixtures.maven.M2Installation
 import org.gradle.test.fixtures.maven.MavenFileRepository
 import org.gradle.test.fixtures.maven.MavenHttpRepository
 import org.gradle.test.fixtures.server.http.HttpServer
@@ -28,10 +29,16 @@ import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
 
 abstract class AbstractDependencyResolutionTest extends AbstractIntegrationSpec {
     @Rule public final HttpServer server = new HttpServer()
+    private M2Installation m2Installation = new M2Installation(testDirectory)
 
-    def "setup"() {
+    def setup() {
         server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
         requireOwnGradleUserHomeDir()
+        executer.beforeExecute m2Installation
+    }
+
+    M2Installation getM2Installation() {
+        m2Installation
     }
 
     IvyFileRepository ivyRepo(def dir = 'ivy-repo') {
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 46963ea..2ee4618 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
@@ -42,8 +42,6 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
     private MavenFileRepository mavenRepo
     private IvyFileRepository ivyRepo
 
-    private List<Closure> executerActions = []
-
     protected TestFile getBuildFile() {
         testDirectory.file('build.gradle')
     }
@@ -97,20 +95,14 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
     }
 
     protected ExecutionResult succeeds(String... tasks) {
-        supplyExecuterActions()
         result = executer.withTasks(*tasks).run()
     }
 
-    void supplyExecuterActions() {
-        executerActions.each { it(executer) }
-    }
-
     protected ExecutionFailure runAndFail(String... tasks) {
         fails(*tasks)
     }
     
     protected ExecutionFailure fails(String... tasks) {
-        supplyExecuterActions()
         failure = executer.withTasks(*tasks).runWithFailure()
         result = failure
     }
@@ -193,11 +185,6 @@ class AbstractIntegrationSpec extends Specification implements TestDirectoryProv
         executer
     }
 
-    public GradleExecuter alwaysUsing(Closure executerAction) {
-        executerActions.add(executerAction)
-        executer
-    }
-
     def createZip(String name, Closure cl) {
         TestFile zipRoot = file("${name}.root")
         TestFile zip = file(name)
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy
index f6aab87..7edd8fb 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/AutoTestedSamplesUtil.groovy
@@ -16,6 +16,8 @@
 
 package org.gradle.integtests.fixtures
 
+import org.gradle.internal.SystemProperties
+
 /**
  * @author: Szczepan Faber, created at: 3/28/11
  */
@@ -35,7 +37,7 @@ class AutoTestedSamplesUtil {
     }
 
     String findDir(String dir) {
-        def workDir = System.getProperty("user.dir")
+        def workDir = SystemProperties.currentDir
         def candidates = [
             "$workDir/$dir",        //when ran from IDEA
             "$workDir/../../$dir"  //when ran from command line
@@ -54,6 +56,9 @@ I tried looking for a root folder here: $candidates
         file.text.eachMatch(/(?ms).*?<pre autoTested.*?>(.*?)<\/pre>(.*?)/) {
             def sample = it[1]
             sample = sample.replaceAll(/(?m)^\s*?\*/, '')
+            sample = sample.replace('<', '<')
+            sample = sample.replace('>', '>')
+            sample = sample.replace('&', '&')
             try {
                 runner.call(file, sample)
             } catch (Exception e) {
@@ -63,7 +68,7 @@ Failed to execute sample:
 -File: $file
 -Sample:
 $sample
--Problem: see the full stactrace below.
+-Problem: see the full stacktrace below.
 *****
 """, e);
             }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java
index 06e1a57..fe92819 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/Sample.java
@@ -18,7 +18,6 @@ package org.gradle.integtests.fixtures;
 
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
-import org.gradle.test.fixtures.file.TestDirectoryProviderFinder;
 import org.gradle.test.fixtures.file.TestFile;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
@@ -36,18 +35,18 @@ public class Sample implements MethodRule {
     private final String defaultSampleName;
 
     private TestFile sampleDir;
+    private TestDirectoryProvider testDirectoryProvider;
 
-    public Sample(String defaultSampleName) {
+    public Sample(TestDirectoryProvider testDirectoryProvider, String defaultSampleName) {
+        this.testDirectoryProvider = testDirectoryProvider;
         this.defaultSampleName = defaultSampleName;
     }
 
-    public Sample() {
-        this.defaultSampleName = null;
+    public Sample(TestDirectoryProvider testDirectoryProvider) {
+        this(testDirectoryProvider, null);
     }
 
     public Statement apply(final Statement base, FrameworkMethod method, Object target) {
-        final TestDirectoryProvider testDirectoryProvider = new TestDirectoryProviderFinder().findFor(target);
-
         final String sampleName = getSampleName(method);
         sampleDir = sampleName == null ? null : testDirectoryProvider.getTestDirectory().file(sampleName);
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
index ddb0bc4..aeb69c0 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/TestResources.java
@@ -17,7 +17,6 @@
 package org.gradle.integtests.fixtures;
 
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
-import org.gradle.test.fixtures.file.TestDirectoryProviderFinder;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.util.Resources;
 import org.junit.rules.MethodRule;
@@ -37,16 +36,12 @@ import java.util.Collection;
  */
 public class TestResources implements MethodRule {
     private final Logger logger = LoggerFactory.getLogger(TestResources.class);
-    private TestDirectoryProvider testWorkDirProvider;
+    private final TestDirectoryProvider testWorkDirProvider;
     private final Collection<String> extraResources;
     private final Resources resources = new Resources();
 
-    // allows to leave instantiation to Spock
-    public TestResources() {
-        this(new String[0]);
-    }
-
-    public TestResources(String... extraResources) {
+    public TestResources(TestDirectoryProvider testDirectoryProvider, String... extraResources) {
+        testWorkDirProvider = testDirectoryProvider;
         this.extraResources = Arrays.asList(extraResources);
     }
 
@@ -56,7 +51,6 @@ public class TestResources implements MethodRule {
 
     public Statement apply(Statement base, final FrameworkMethod method, Object target) {
         final Statement statement = resources.apply(base, method, target);
-        testWorkDirProvider = new TestDirectoryProviderFinder().findFor(target);
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy
index 3bff9ef..c7fe692 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/UserGuideSamplesRunner.groovy
@@ -123,7 +123,7 @@ class UserGuideSamplesRunner extends Runner {
         try {
             println("Test Id: $run.id, dir: $run.subDir, execution dir: $run.executionDir args: $run.args")
 
-            executer.setAllowExtraLogging(false).inDirectory(run.executionDir).withArguments(run.args as String[]).withEnvironmentVars(run.envs)
+            executer.noExtraLogging().inDirectory(run.executionDir).withArguments(run.args as String[]).withEnvironmentVars(run.envs)
 
             def result = run.expectFailure ? executer.runWithFailure() : executer.run()
             if (run.outputFile) {
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 80c66d3..8f761a4 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
@@ -21,8 +21,9 @@ import org.gradle.api.internal.ClosureBackedAction;
 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;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
 import org.gradle.test.fixtures.file.TestFile;
 import org.gradle.util.DeprecationLogger;
@@ -54,7 +55,7 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     private Map<String, String> environmentVars = new HashMap<String, String>();
     private List<File> initScripts = new ArrayList<File>();
     private String executable;
-    private File gradleUserHomeDir = buildContext.getGradleUserHomeDir();
+    private TestFile gradleUserHomeDir = buildContext.getGradleUserHomeDir();
     private File userHomeDir;
     private File javaHome;
     private File buildScript;
@@ -72,6 +73,7 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     private boolean stackTraceChecksOn = true;
 
     private final ActionBroadcast<GradleExecuter> beforeExecute = new ActionBroadcast<GradleExecuter>();
+    private final Set<Action> afterExecute = new LinkedHashSet<Action>();
 
     private final TestDirectoryProvider testDirectoryProvider;
     private final GradleDistribution distribution;
@@ -126,6 +128,14 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         beforeExecute.add(new ClosureBackedAction<GradleExecuter>(action));
     }
 
+    public void afterExecute(Action<? super GradleExecuter> action) {
+        afterExecute.add(action);
+    }
+
+    public void afterExecute(Closure action) {
+        afterExecute.add(new ClosureBackedAction<GradleExecuter>(action));
+    }
+
     public GradleExecuter inDirectory(File directory) {
         workingDir = directory;
         return this;
@@ -160,7 +170,7 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         }
         executer.withTasks(tasks);
         executer.withArguments(args);
-        executer.withEnvironmentVars(getAllEnvironmentVars());
+        executer.withEnvironmentVars(getMergedEnvironmentVars());
         executer.usingExecutable(executable);
         if (quiet) {
             executer.withQuietLogging();
@@ -185,7 +195,7 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         if (noDefaultJvmArgs) {
             executer.withNoDefaultJvmArgs();
         }
-        executer.setAllowExtraLogging(allowExtraLogging);
+        executer.noExtraLogging();
 
         if (!deprecationChecksOn) {
             executer.withDeprecationChecksDisabled();
@@ -193,7 +203,9 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         if (!stackTraceChecksOn) {
             executer.withStackTraceChecksDisabled();
         }
-        executer.requireGradleHome(isRequireGradleHome());
+        if (requireGradleHome) {
+            executer.requireGradleHome();
+        }
 
         return executer;
     }
@@ -218,12 +230,12 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return this;
     }
 
-    public File getGradleUserHomeDir() {
+    public TestFile getGradleUserHomeDir() {
         return gradleUserHomeDir;
     }
 
     public GradleExecuter withGradleUserHomeDir(File userHomeDir) {
-        this.gradleUserHomeDir = userHomeDir;
+        this.gradleUserHomeDir = userHomeDir == null ? null : new TestFile(userHomeDir);
         return this;
     }
 
@@ -235,7 +247,10 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return userHomeDir;
     }
 
-    public List<String> getGradleOpts() {
+    /**
+     * Returns the gradle opts set with withGradleOpts() (does not consider any set via withEnvironmentVars())
+     */
+    protected List<String> getGradleOpts() {
         return gradleOpts;
     }
 
@@ -327,19 +342,55 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
     public GradleExecuter withEnvironmentVars(Map<String, ?> environment) {
         environmentVars.clear();
         for (Map.Entry<String, ?> entry : environment.entrySet()) {
-            if (entry.getKey().equals("GRADLE_OPTS")) {
-                throw new IllegalArgumentException("GRADLE_OPTS cannot be set via withEnvironmentVars(), use withGradleOpts()");
-            }
             environmentVars.put(entry.getKey(), entry.getValue().toString());
         }
         return this;
     }
 
-    protected Map<String, String> getAllEnvironmentVars() {
+    /**
+     * Returns the effective env vars, having merged in specific settings.
+     *
+     * For example, GRADLE_OPTS will be anything that was specified via withEnvironmentVars() and withGradleOpts(). JAVA_HOME will also be set according to getJavaHome().
+     */
+    protected Map<String, String> getMergedEnvironmentVars() {
+        Map<String, String> environmentVars = new HashMap<String, String>(getEnvironmentVars());
+        environmentVars.put("GRADLE_OPTS", toJvmArgsString(getMergedGradleOpts()));
+        if (!environmentVars.containsKey("JAVA_HOME")) {
+            environmentVars.put("JAVA_HOME", getJavaHome().getAbsolutePath());
+        }
         return environmentVars;
     }
 
-    public Map<String, String> getEnvironmentVars() {
+    protected String toJvmArgsString(Iterable<String> jvmArgs) {
+        StringBuilder result = new StringBuilder();
+        for (String jvmArg : jvmArgs) {
+            if (result.length() > 0) {
+                result.append(" ");
+            }
+            if (jvmArg.contains(" ")) {
+                assert !jvmArg.contains("\"");
+                result.append('"');
+                result.append(jvmArg);
+                result.append('"');
+            } else {
+                result.append(jvmArg);
+            }
+        }
+
+        return result.toString();
+    }
+
+    private List<String> getMergedGradleOpts() {
+        List<String> gradleOpts = new ArrayList<String>(getGradleOpts());
+        String gradleOptsEnv = getEnvironmentVars().get("GRADLE_OPTS");
+        if (gradleOptsEnv != null) {
+            gradleOpts.addAll(JvmOptions.fromString(gradleOptsEnv));
+        }
+
+        return gradleOpts;
+    }
+
+    protected Map<String, String> getEnvironmentVars() {
         return environmentVars;
     }
 
@@ -446,25 +497,44 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
             properties.put("user.home", getUserHomeDir().getAbsolutePath());
         }
 
-        properties.put(DaemonParameters.IDLE_TIMEOUT_SYS_PROPERTY, "" + (daemonIdleTimeoutSecs * 1000));
-        properties.put(DaemonParameters.BASE_DIR_SYS_PROPERTY, daemonBaseDir.getAbsolutePath());
+        properties.put(GradleProperties.IDLE_TIMEOUT_PROPERTY, "" + (daemonIdleTimeoutSecs * 1000));
+        properties.put(GradleProperties.BASE_DIR_PROPERTY, daemonBaseDir.getAbsolutePath());
         properties.put(DeprecationLogger.ORG_GRADLE_DEPRECATION_TRACE_PROPERTY_NAME, "true");
 
-        properties.put("java.io.tmpdir", getTmpDir().createDir().getAbsolutePath());
+        String tmpDirPath = getTmpDir().createDir().getAbsolutePath();
+        if (!tmpDirPath.contains(" ") || getDistribution().isSupportsSpacesInGradleAndJavaOpts()) {
+            properties.put("java.io.tmpdir", tmpDirPath);
+        }
+
+        properties.put("file.encoding", getDefaultCharacterEncoding());
+
         return properties;
     }
 
     public final GradleHandle start() {
+        assert afterExecute.isEmpty() : "afterExecute actions are not implemented for async execution";
         fireBeforeExecute();
         assertCanExecute();
-        return doStart();
+        try {
+            return doStart();
+        } finally {
+            reset();
+        }
     }
 
     public final ExecutionResult run() {
         fireBeforeExecute();
         assertCanExecute();
         try {
-            return checkResult(doRun());
+            return doRun();
+        } finally {
+            finished();
+        }
+    }
+
+    private void finished() {
+        try {
+            new ActionBroadcast<GradleExecuter>(afterExecute).execute(this);
         } finally {
             reset();
         }
@@ -474,9 +544,9 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         fireBeforeExecute();
         assertCanExecute();
         try {
-            return checkResult(doRunWithFailure());
+            return doRunWithFailure();
         } finally {
-            reset();
+            finished();
         }
     }
 
@@ -500,67 +570,51 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return this;
     }
 
-    protected <T extends ExecutionResult> T checkResult(T result) {
+    protected Action<ExecutionResult> getResultAssertion() {
+        ActionBroadcast<ExecutionResult> assertions = new ActionBroadcast<ExecutionResult>();
+
         if (stackTraceChecksOn) {
-            // Assert that nothing unexpected was logged
-            assertOutputHasNoStackTraces(result);
-            assertErrorHasNoStackTraces(result);
-        }
-        if (deprecationChecksOn) {
-            assertOutputHasNoDeprecationWarnings(result);
-        }
-
-        if (getExecutable() == null) {
-            // Assert that no temp files are left lying around
-            // Note: don't do this if a custom executable is used, as we don't know (and probably don't care) whether the executable cleans up or not
-            TestFile[] testFiles = getTmpDir().listFiles();
-            if (testFiles != null) {
-                List<String> unexpectedFiles = new ArrayList<String>();
-                for (File file : testFiles) {
-                    if (!file.getName().matches("maven-artifact\\d+.tmp")) {
-                        unexpectedFiles.add(file.getName());
+            assertions.add(new Action<ExecutionResult>() {
+                public void execute(ExecutionResult executionResult) {
+                    assertNoStackTraces(executionResult.getOutput(), "Standard output");
+
+                    String error = executionResult.getError();
+                    if (executionResult instanceof ExecutionFailure) {
+                        // Axe everything after the expected exception
+                        int pos = error.indexOf("* Exception is:" + TextUtil.getPlatformLineSeparator());
+                        if (pos >= 0) {
+                            error = error.substring(0, pos);
+                        }
                     }
+                    assertNoStackTraces(error, "Standard error");
                 }
-//            Assert.assertThat(unexpectedFiles, Matchers.isEmpty());
-            }
-        }
-
-        return result;
-    }
-
-    private void assertOutputHasNoStackTraces(ExecutionResult result) {
-        assertNoStackTraces(result.getOutput(), "Standard output");
-    }
 
-    public void assertErrorHasNoStackTraces(ExecutionResult result) {
-        String error = result.getError();
-        if (result instanceof ExecutionFailure) {
-            // Axe everything after the expected exception
-            int pos = error.indexOf("* Exception is:" + TextUtil.getPlatformLineSeparator());
-            if (pos >= 0) {
-                error = error.substring(0, pos);
-            }
+                private void assertNoStackTraces(String output, String displayName) {
+                    if (containsLine(matchesRegexp("\\s+(at\\s+)?[\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
+                        throw new AssertionError(String.format("%s contains an unexpected stack trace:%n=====%n%s%n=====%n", displayName, output));
+                    }
+                }
+            });
         }
-        assertNoStackTraces(error, "Standard error");
-    }
 
-    public void assertOutputHasNoDeprecationWarnings(ExecutionResult result) {
-        assertNoDeprecationWarnings(result.getOutput(), "Standard output");
-        assertNoDeprecationWarnings(result.getError(), "Standard error");
-    }
+        if (deprecationChecksOn) {
+            assertions.add(new Action<ExecutionResult>() {
+                public void execute(ExecutionResult executionResult) {
+                    assertNoDeprecationWarnings(executionResult.getOutput(), "Standard output");
+                    assertNoDeprecationWarnings(executionResult.getError(), "Standard error");
+                }
 
-    private void assertNoDeprecationWarnings(String output, String displayName) {
-        boolean javacWarning = containsLine(matchesRegexp(".*use(s)? or override(s)? a deprecated API\\.")).matches(output);
-        boolean deprecationWarning = containsLine(matchesRegexp(".* deprecated.*")).matches(output);
-        if (deprecationWarning && !javacWarning) {
-            throw new AssertionError(String.format("%s contains a deprecation warning:%n=====%n%s%n=====%n", displayName, output));
+                private void assertNoDeprecationWarnings(String output, String displayName) {
+                    boolean javacWarning = containsLine(matchesRegexp(".*use(s)? or override(s)? a deprecated API\\.")).matches(output);
+                    boolean deprecationWarning = containsLine(matchesRegexp(".* deprecated.*")).matches(output);
+                    if (deprecationWarning && !javacWarning) {
+                        throw new AssertionError(String.format("%s contains a deprecation warning:%n=====%n%s%n=====%n", displayName, output));
+                    }
+                }
+            });
         }
-    }
 
-    private void assertNoStackTraces(String output, String displayName) {
-        if (containsLine(matchesRegexp("\\s+(at\\s+)?[\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
-            throw new AssertionError(String.format("%s contains an unexpected stack trace:%n=====%n%s%n=====%n", displayName, output));
-        }
+        return assertions;
     }
 
     public GradleExecuter withDeprecationChecksDisabled() {
@@ -579,11 +633,8 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return new TestFile(getTestDirectoryProvider().getTestDirectory(), "tmp");
     }
 
-    /**
-     * set true to allow the executer to increase the log level if necessary to help out debugging. Set false to make the executer never update the log level.
-     */
-    public GradleExecuter setAllowExtraLogging(boolean allowExtraLogging) {
-        this.allowExtraLogging = allowExtraLogging;
+    public GradleExecuter noExtraLogging() {
+        this.allowExtraLogging = false;
         return this;
     }
 
@@ -595,8 +646,8 @@ public abstract class AbstractGradleExecuter implements GradleExecuter {
         return requireGradleHome;
     }
 
-    public GradleExecuter requireGradleHome(boolean requireGradleHome) {
-        this.requireGradleHome = requireGradleHome;
+    public GradleExecuter requireGradleHome() {
+        this.requireGradleHome = true;
         return this;
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/BuildServerGradleDistribution.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/BuildServerGradleDistribution.groovy
new file mode 100644
index 0000000..cfa3f55
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/BuildServerGradleDistribution.groovy
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer
+
+import org.gradle.test.fixtures.file.TestFile
+
+class BuildServerGradleDistribution extends DownloadableGradleDistribution {
+    private String buildNumberVersion
+
+    public BuildServerGradleDistribution(String buildNumberVersion, TestFile versionDir) {
+        super(convertToBuildVersion(buildNumberVersion), versionDir)
+        this.buildNumberVersion = buildNumberVersion - '#';
+    }
+
+    static String convertToBuildVersion(String buildNumberVersion) {
+        def url = new URL("http://builds.gradle.org/repository/download/bt45/${buildNumberVersion - "#"}/build-receipt.properties?guest=1")
+        def buildReceipt = url.text
+        Properties props = new Properties()
+        props.load(new StringReader(buildReceipt))
+        return props.getProperty("versionNumber");
+    }
+
+    @Override
+    protected URL getDownloadURL() {
+        return new URL("http://builds.gradle.org/repository/download/bt45/${buildNumberVersion}/distributions/gradle-${URLEncoder.encode(version.version, "UTF-8")}-bin.zip?guest=1");
+    }
+}
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 bb0334e..bf5e6eb 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
@@ -53,7 +53,7 @@ public class DaemonGradleExecuter extends ForkingGradleExecuter {
     }
 
     @Override
-    public List<String> getGradleOpts() {
+    protected List<String> getGradleOpts() {
         if (isNoDefaultJvmArgs()) {
             return super.getGradleOpts();
         } else {
@@ -75,7 +75,7 @@ public class DaemonGradleExecuter extends ForkingGradleExecuter {
             }));
 
             List<String> gradleOpts = new ArrayList<String>(super.getGradleOpts());
-            gradleOpts.add(String.format("-Dorg.gradle.jvmArgs=%s", quotedArgs));
+            gradleOpts.add("-Dorg.gradle.jvmArgs=" +  quotedArgs);
             return gradleOpts;
         }
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java
index e3740be..61b9de6 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DefaultGradleDistribution.java
@@ -132,6 +132,10 @@ public class DefaultGradleDistribution implements GradleDistribution {
         return isSameOrNewer("1.0-milestone-5");
     }
 
+    public boolean isFullySupportsIvyRepository() {
+        return isSameOrNewer("1.0-milestone-7");
+    }
+
     protected boolean isSameOrNewer(String otherVersion) {
         return isVersion(otherVersion) || version.compareTo(GradleVersion.version(otherVersion)) > 0;
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy
index 55e0dd1..8962401 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DependencyResolutionFailure.groovy
@@ -17,6 +17,7 @@
 package org.gradle.integtests.fixtures.executer
 
 import static org.gradle.util.Matchers.*;
+import org.hamcrest.Matcher
 
 /**
  * by Szczepan Faber, created at: 12/12/12
@@ -24,8 +25,9 @@ import static org.gradle.util.Matchers.*;
 public class DependencyResolutionFailure {
     private final ExecutionFailure failure
 
-    DependencyResolutionFailure(ExecutionFailure failure) {
+    DependencyResolutionFailure(ExecutionFailure failure, String configuration) {
         this.failure = failure
+        assertFailedConfiguration(configuration);
     }
 
     DependencyResolutionFailure assertFailedConfiguration(String configuration) {
@@ -42,4 +44,9 @@ public class DependencyResolutionFailure {
         failure.assertHasCause(cause)
         this
     }
+
+    DependencyResolutionFailure assertThatCause(Matcher<String> matcher) {
+        failure.assertThatCause(matcher)
+        this
+    }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DownloadableGradleDistribution.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DownloadableGradleDistribution.groovy
new file mode 100644
index 0000000..5000654
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DownloadableGradleDistribution.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.integtests.fixtures.executer
+
+import org.gradle.CacheUsage
+import org.gradle.api.Action
+import org.gradle.cache.PersistentCache
+import org.gradle.cache.internal.*
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.util.GradleVersion
+
+abstract class DownloadableGradleDistribution extends DefaultGradleDistribution {
+
+    private static final CACHE_FACTORY = createCacheFactory()
+
+    private static CacheFactory createCacheFactory() {
+        return new DefaultCacheFactory(
+                new DefaultFileLockManager(
+                        new DefaultProcessMetaDataProvider(
+                                NativeServices.getInstance().get(org.gradle.internal.nativeplatform.ProcessEnvironment)),
+                        20 * 60 * 1000 // allow up to 20 minutes to download a distribution
+                )).create()
+    }
+
+    protected TestFile versionDir
+    private PersistentCache cache
+
+    DownloadableGradleDistribution(String version, TestFile versionDir) {
+        super(GradleVersion.version(version), versionDir.file("gradle-$version"), versionDir.file("gradle-$version-bin.zip"))
+        this.versionDir = versionDir
+    }
+
+    TestFile getBinDistribution() {
+        download()
+        super.getBinDistribution()
+    }
+
+    def TestFile getGradleHomeDir() {
+        download()
+        super.getGradleHomeDir()
+    }
+
+    private void download() {
+        if (cache == null) {
+            def downloadAction = { cache ->
+                URL url = getDownloadURL();
+                System.out.println("downloading $url")
+                super.binDistribution.copyFrom(url)
+                super.binDistribution.usingNativeTools().unzipTo(versionDir)
+            }
+            //noinspection GrDeprecatedAPIUsage
+            cache = CACHE_FACTORY.open(versionDir, version.version, CacheUsage.ON, null, [:], FileLockManager.LockMode.Shared, downloadAction as Action)
+        }
+
+        super.binDistribution.assertIsFile()
+        super.gradleHomeDir.assertIsDir()
+    }
+
+    abstract protected URL getDownloadURL();
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java
index 9a3737f..5ef30e8 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionFailure.java
@@ -34,5 +34,5 @@ public interface ExecutionFailure extends ExecutionResult {
 
     ExecutionFailure assertTestsFailed();
 
-    DependencyResolutionFailure getDependencyResolutionFailure(); //TODO SF use this fixture where possible
+    DependencyResolutionFailure assertResolutionFailure(String configuration);
 }
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 ccb0382..828d3ab 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
@@ -36,11 +36,6 @@ public interface ExecutionResult {
     ExecutionResult assertTasksExecuted(String... taskPaths);
 
     /**
-     * Asserts that exactly the given set of projects have been evaluated in the given order.
-     */
-    ExecutionResult assertProjectsEvaluated(String... projectPaths);
-
-    /**
      * Returns the tasks that were skipped, in an undefined order. Note: ignores buildSrc tasks.
      */
     Set<String> getSkippedTasks();
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 532af38..54a49dd 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
@@ -16,17 +16,22 @@
 
 package org.gradle.integtests.fixtures.executer;
 
+import org.gradle.api.Action;
 import org.gradle.internal.Factory;
 import org.gradle.internal.nativeplatform.jna.WindowsHandlesManipulator;
 import org.gradle.internal.os.OperatingSystem;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.launcher.daemon.registry.DaemonRegistryServices;
 import org.gradle.process.internal.ExecHandleBuilder;
+import org.gradle.process.internal.JvmOptions;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
 import org.gradle.test.fixtures.file.TestFile;
 
 import java.io.File;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
 
 import static org.junit.Assert.fail;
 
@@ -41,7 +46,20 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
     }
 
     public void assertCanExecute() throws AssertionError {
-        // Can run any build
+        if (!getDistribution().isSupportsSpacesInGradleAndJavaOpts()) {
+            Map<String, String> mergedEnvironmentVars = getMergedEnvironmentVars();
+            for (String envVarName : Arrays.asList("JAVA_OPTS", "GRADLE_OPTS")) {
+                String envVarValue = mergedEnvironmentVars.get(envVarName);
+                if (envVarValue == null) {
+                    continue;
+                }
+                for (String arg : JvmOptions.fromString(envVarValue)) {
+                    if (arg.contains(" ")) {
+                        throw new AssertionError(String.format("Env var %s contains arg with space (%s) which is not supported", envVarName, arg));
+                    }
+                }
+            }
+        }
     }
 
     @Override
@@ -68,13 +86,13 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
             }
         };
 
-        // Override some of the user's environment
+        // Clear the user's environment
         builder.environment("GRADLE_HOME", "");
-        builder.environment("JAVA_HOME", getJavaHome());
-        builder.environment("GRADLE_OPTS", getGradleOptsString());
+        builder.environment("JAVA_HOME", "");
+        builder.environment("GRADLE_OPTS", "");
         builder.environment("JAVA_OPTS", "");
 
-        builder.environment(getAllEnvironmentVars());
+        builder.environment(getMergedEnvironmentVars());
         builder.workingDir(getWorkingDir());
         builder.setStandardInput(getStdin());
 
@@ -90,15 +108,15 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
 
     @Override
     public GradleHandle doStart() {
-        return createGradleHandle(getDefaultCharacterEncoding(), new Factory<ExecHandleBuilder>() {
+        return createGradleHandle(getResultAssertion(), getDefaultCharacterEncoding(), new Factory<ExecHandleBuilder>() {
             public ExecHandleBuilder create() {
                 return createExecHandleBuilder();
             }
         }).start();
     }
 
-    protected ForkingGradleHandle createGradleHandle(String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
-        return new ForkingGradleHandle(encoding, execHandleFactory);
+    protected ForkingGradleHandle createGradleHandle(Action<ExecutionResult> resultAssertion, String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
+        return new ForkingGradleHandle(resultAssertion, encoding, execHandleFactory);
     }
 
     protected ExecutionResult doRun() {
@@ -110,22 +128,13 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
     }
 
     @Override
-    public List<String> getGradleOpts() {
+    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();
-
-            if (value.contains(" ") && !getDistribution().isSupportsSpacesInGradleAndJavaOpts()) {
-                getLogger().warn("Removing '{}' from GRADLE_OPTS (value: {}) as it contains spaces, and this Gradle version ({}) cannot handle spaces in GRADLE_OPTS",
-                        key, value, getDistribution().getVersion().getVersion()
-                );
-                continue;
-            }
-
             gradleOpts.add(String.format("-D%s=%s", key, value));
         }
-
         gradleOpts.add("-ea");
 
         //uncomment for debugging
@@ -135,32 +144,6 @@ class ForkingGradleExecuter extends AbstractGradleExecuter {
         return gradleOpts;
     }
 
-    @Override
-    protected Map<String, String> getImplicitJvmSystemProperties() {
-        Map<String, String> implicitJvmSystemProperties = new LinkedHashMap<String, String>(super.getImplicitJvmSystemProperties());
-        implicitJvmSystemProperties.put("file.encoding", getDefaultCharacterEncoding());
-        return implicitJvmSystemProperties;
-    }
-
-    private String getGradleOptsString() {
-        StringBuilder result = new StringBuilder();
-        for (String gradleOpt : getGradleOpts()) {
-            if (result.length() > 0) {
-                result.append(" ");
-            }
-            if (gradleOpt.contains(" ")) {
-                assert !gradleOpt.contains("\"");
-                result.append('"');
-                result.append(gradleOpt);
-                result.append('"');
-            } else {
-                result.append(gradleOpt);
-            }
-        }
-
-        return result.toString();
-    }
-
     private interface ExecHandlerConfigurer {
         void configure(ExecHandleBuilder builder);
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleHandle.java
index 9439dfb..ae3a99e 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleHandle.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ForkingGradleHandle.java
@@ -17,6 +17,7 @@ package org.gradle.integtests.fixtures.executer;
 
 import org.apache.commons.io.output.CloseShieldOutputStream;
 import org.apache.commons.io.output.TeeOutputStream;
+import org.gradle.api.Action;
 import org.gradle.internal.Factory;
 import org.gradle.internal.UncheckedException;
 import org.gradle.process.ExecResult;
@@ -32,11 +33,13 @@ class ForkingGradleHandle extends OutputScrapingGradleHandle {
 
     final private ByteArrayOutputStream standardOutput = new ByteArrayOutputStream();
     final private ByteArrayOutputStream errorOutput = new ByteArrayOutputStream();
+    private final Action<ExecutionResult> resultAssertion;
 
     private ExecHandle execHandle;
     private final String outputEncoding;
 
-    public ForkingGradleHandle(String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
+    public ForkingGradleHandle(Action<ExecutionResult> resultAssertion, String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
+        this.resultAssertion = resultAssertion;
         this.execHandleFactory = execHandleFactory;
         this.outputEncoding = outputEncoding;
     }
@@ -111,6 +114,9 @@ class ForkingGradleHandle extends OutputScrapingGradleHandle {
                     expectFailure ? "did not fail" : "failed", execHandle.getDirectory(), execHandle.getCommand(), execHandle.getArguments(), output, error);
             throw new UnexpectedBuildFailure(message);
         }
-        return expectFailure ? toExecutionFailure(output, error) : toExecutionResult(output, error);
+
+        ExecutionResult executionResult = expectFailure ? toExecutionFailure(output, error) : toExecutionResult(output, error);
+        resultAssertion.execute(executionResult);
+        return executionResult;
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java
index 55b0818..263aaae 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/GradleDistribution.java
@@ -89,4 +89,9 @@ public interface GradleDistribution {
      * See http://issues.gradle.org/browse/GRADLE-1730
      */
     boolean isSupportsSpacesInGradleAndJavaOpts();
+
+    /**
+     * The 'ivy' repository was introduced in Milestone-3, but early versions didn't work with spaces in the artifact pattern.
+     */
+    boolean isFullySupportsIvyRepository();
 }
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 66b1a01..646bdb4 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
@@ -19,6 +19,7 @@ import groovy.lang.Closure;
 import org.gradle.api.Action;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
+import org.gradle.test.fixtures.file.TestFile;
 
 import java.io.File;
 import java.io.InputStream;
@@ -203,24 +204,67 @@ public interface GradleExecuter {
      */
     void beforeExecute(Closure action);
 
+    /**
+     * Adds an action to be called immediately after execution
+     */
+    void afterExecute(Action<? super GradleExecuter> action);
+
+    /**
+     * Adds an action to be called immediately after execution
+     */
+    void afterExecute(Closure action);
+
+    /**
+     * The directory that the executer will use for any test specific storage.
+     *
+     * May or may not be the same directory as the build to be run.
+     */
     TestDirectoryProvider getTestDirectoryProvider();
 
+    /**
+     * Disables asserting that the execution did not trigger any deprecation warnings.
+     */
     GradleExecuter withDeprecationChecksDisabled();
 
+    /**
+     * Disables asserting that no unexpected stacktraces are present in the output.
+     */
     GradleExecuter withStackTraceChecksDisabled();
 
-    GradleExecuter setAllowExtraLogging(boolean allowExtraLogging);
-
-    boolean isRequireGradleHome();
+    /**
+     * An executer may decide to implicitly bump the logging level, unless this is called.
+     */
+    GradleExecuter noExtraLogging();
 
-    GradleExecuter requireGradleHome(boolean requireGradleHome);
+    /**
+     * Requires that there is a gradle home for the execution, which in process execution does not.
+     */
+    GradleExecuter requireGradleHome();
 
+    /**
+     * Configures that any daemons launched by or during the execution are unique to the test.
+     *
+     * This value is persistent across executions in the same test.
+     */
     GradleExecuter requireIsolatedDaemons();
 
+    /**
+     * Configures a unique gradle user home dir for the test.
+     *
+     * The gradle user home dir used will be underneath the {@link #getTestDirectoryProvider()} directory.
+     *
+     * This value is persistent across executions in the same test.
+     */
     GradleExecuter requireOwnGradleUserHomeDir();
 
-    File getGradleUserHomeDir();
+    /**
+     * The Gradle user home dir that will be used for executions.
+     */
+    TestFile getGradleUserHomeDir();
 
+    /**
+     * The distribution used to execute.
+     */
     GradleDistribution getDistribution();
 
     /**
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
old mode 100644
new mode 100755
index b4adf49..28a8100
--- 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
@@ -34,6 +34,7 @@ import org.gradle.api.logging.LogLevel;
 import org.gradle.api.logging.StandardOutputListener;
 import org.gradle.api.tasks.TaskState;
 import org.gradle.cli.CommandLineParser;
+import org.gradle.cli.ParsedCommandLine;
 import org.gradle.execution.MultipleBuildFailures;
 import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.initialization.DefaultGradleLauncherFactory;
@@ -42,6 +43,7 @@ import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.nativeplatform.ProcessEnvironment;
 import org.gradle.internal.nativeplatform.services.NativeServices;
 import org.gradle.launcher.Main;
+import org.gradle.launcher.daemon.configuration.GradlePropertiesConfigurer;
 import org.gradle.launcher.daemon.registry.DaemonRegistry;
 import org.gradle.process.internal.JavaExecHandleBuilder;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
@@ -57,7 +59,6 @@ import java.util.*;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CopyOnWriteArraySet;
 
-import static java.util.Arrays.asList;
 import static org.gradle.util.Matchers.*;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
@@ -87,8 +88,8 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
         } catch (Exception e) {
             throw new UnexpectedBuildFailure(e);
         }
-        return new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
-                outputListener.toString(), errorListener.toString());
+        return assertResult(new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
+                outputListener.toString(), errorListener.toString()));
     }
 
     @Override
@@ -100,14 +101,19 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             doRun(outputListener, errorListener, buildListener).rethrowFailure();
             throw new AssertionError("expected build to fail but it did not.");
         } catch (GradleException e) {
-            return new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
-                    outputListener.writer.toString(), errorListener.writer.toString(), e);
+            return assertResult(new InProcessExecutionFailure(buildListener.executedTasks, buildListener.skippedTasks,
+                    outputListener.writer.toString(), errorListener.writer.toString(), e));
         }
     }
 
+    private <T extends ExecutionResult> T assertResult(T result) {
+        getResultAssertion().execute(result);
+        return result;
+    }
+
     @Override
     protected GradleHandle doStart() {
-        return new ForkingGradleHandle(getDefaultCharacterEncoding(), new Factory<JavaExecHandleBuilder>() {
+        return new ForkingGradleHandle(getResultAssertion(), getDefaultCharacterEncoding(), new Factory<JavaExecHandleBuilder>() {
             public JavaExecHandleBuilder create() {
                 JavaExecHandleBuilder builder = new JavaExecHandleBuilder(new IdentityFileResolver());
                 builder.workingDir(getWorkingDir());
@@ -126,7 +132,7 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
         Properties originalSysProperties = new Properties();
         originalSysProperties.putAll(System.getProperties());
         File originalUserDir = new File(originalSysProperties.getProperty("user.dir"));
-        Map<String, String> originalEnv = System.getenv();
+        Map<String, String> originalEnv = new HashMap<String, String>(System.getenv());
 
         // Augment the environment for the execution
         System.setIn(getStdin());
@@ -145,7 +151,11 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
         CommandLineParser parser = new CommandLineParser();
         DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
         converter.configure(parser);
-        converter.convert(parser.parse(getAllArgs()), parameter);
+        ParsedCommandLine parsedCommandLine = parser.parse(getAllArgs());
+        converter.convert(parsedCommandLine, parameter);
+
+        //I'm not sure if below is safe
+        new GradlePropertiesConfigurer().configureStartParameter(parameter);
 
         DefaultGradleLauncherFactory factory = (DefaultGradleLauncherFactory) GradleLauncher.getFactory();
         factory.addListener(listener);
@@ -158,12 +168,12 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             // Restore the environment
             System.setProperties(originalSysProperties);
             processEnvironment.maybeSetProcessDir(originalUserDir);
-            for (Map.Entry<String, String> entry : originalEnv.entrySet()) {
-                String oldValue = entry.getValue();
+            for (String envVar: getEnvironmentVars().keySet()) {
+                String oldValue = originalEnv.get(envVar);
                 if (oldValue != null) {
-                    processEnvironment.maybeSetEnvironmentVariable(entry.getKey(), oldValue);
+                    processEnvironment.maybeSetEnvironmentVariable(envVar, oldValue);
                 } else {
-                    processEnvironment.maybeRemoveEnvironmentVariable(entry.getKey());
+                    processEnvironment.maybeRemoveEnvironmentVariable(envVar);
                 }
             }
             factory.removeListener(listener);
@@ -178,7 +188,10 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
     public void assertCanExecute() {
         assertNull(getExecutable());
         assertEquals(getJavaHome(), Jvm.current().getJavaHome());
-        assertEquals(getDefaultCharacterEncoding(), Charset.defaultCharset().name());
+        String defaultEncoding = getImplicitJvmSystemProperties().get("file.encoding");
+        if (defaultEncoding != null) {
+            assertEquals(Charset.forName(defaultEncoding), Charset.defaultCharset());
+        }
         assertFalse(isRequireGradleHome());
     }
 
@@ -300,11 +313,6 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             return this;
         }
 
-        public ExecutionResult assertProjectsEvaluated(String... projectPaths) {
-            new OutputScraper(getOutput()).assertProjectsEvaluated(asList(projectPaths));
-            return this;
-        }
-
         public Set<String> getSkippedTasks() {
             return new HashSet<String>(skippedTasks);
         }
@@ -409,8 +417,8 @@ class InProcessGradleExecuter extends AbstractGradleExecuter {
             return this;
         }
 
-        public DependencyResolutionFailure getDependencyResolutionFailure() {
-            return new DependencyResolutionFailure(this);
+        public DependencyResolutionFailure assertResolutionFailure(String configuration) {
+            return new DependencyResolutionFailure(this, configuration);
         }
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InitScriptExecuterFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InitScriptExecuterFixture.groovy
new file mode 100644
index 0000000..4133565
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InitScriptExecuterFixture.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer
+
+import org.junit.rules.MethodRule
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.junit.runners.model.Statement
+import org.junit.runners.model.FrameworkMethod
+
+/**
+ * by Szczepan Faber, created at: 2/1/13
+ */
+abstract class InitScriptExecuterFixture implements MethodRule {
+
+    GradleExecuter executer
+    TestDirectoryProvider testDir
+
+    InitScriptExecuterFixture(GradleExecuter executer, TestDirectoryProvider testDir) {
+        this.executer = executer
+        this.testDir = testDir
+    }
+
+    abstract String initScriptContent()
+
+    abstract void afterBuild()
+
+    Statement apply(Statement base, FrameworkMethod method, Object target) {
+        def temporaryFolder = testDir.testDirectory
+        def initFile = temporaryFolder.file(this.getClass().getSimpleName() + "-init.gradle")
+        initFile.text = initScriptContent()
+        executer.beforeExecute {
+            usingInitScript(initFile)
+        }
+        executer.afterExecute {
+            afterBuild()
+        }
+
+        return new Statement() {
+            public void evaluate() throws Throwable {
+                base.evaluate();
+            }
+        }
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/IntegrationTestBuildContext.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/IntegrationTestBuildContext.java
index dec09f1..af8fe5c 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/IntegrationTestBuildContext.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/IntegrationTestBuildContext.java
@@ -67,6 +67,9 @@ public class IntegrationTestBuildContext {
             return new UnderDevelopmentGradleDistribution();
         }
         TestFile previousVersionDir = getGradleUserHomeDir().getParentFile().file("previousVersion");
+        if(version.startsWith("#")){
+            return new BuildServerGradleDistribution(version, previousVersionDir.file(version));
+        }
         return new ReleasedGradleDistribution(version, previousVersionDir.file(version));
     }
 
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScraper.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScraper.groovy
deleted file mode 100644
index b79cc23..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScraper.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures.executer
-
-/**
- * by Szczepan Faber, created at: 12/21/12
- */
-class OutputScraper {
-    List<String> evaluatedProjects
-    final static String EVAL_NOT_FOUND = "No evaluated projects found. Make sure you run the build with '-i'."
-    final static String ALL_EVAL_ERROR = "The gradle.projectsEvaluated hook must not be fired before all projects have been evaluated."
-
-    OutputScraper(String output) {
-        this(getEvaluatedProjects(output))
-    }
-    OutputScraper(List<String> evaluatedProjects) {
-        this.evaluatedProjects = evaluatedProjects
-    }
-
-    void assertProjectsEvaluated(Collection expectedProjects) {
-        if (!expectedProjects.empty && evaluatedProjects.empty) {
-            throw new AssertionError(EVAL_NOT_FOUND)
-        }
-        assert evaluatedProjects == expectedProjects as List
-    }
-
-    static List<String> getEvaluatedProjects(String output) {
-        def evaluatedProjects = []
-        def allEvaluated = false
-        output.eachLine {
-            if (it.startsWith('All projects evaluated.')) {
-                allEvaluated = true
-            } else if (it.startsWith('Evaluating root project')) {
-                assertNotEvaluated(allEvaluated, it)
-                evaluatedProjects << ':'
-            } else {
-                def m = it =~ /Evaluating project '(.*)' using.*/
-                if (m) {
-                    assertNotEvaluated(allEvaluated, it)
-                    evaluatedProjects << m.group(1)
-                }
-            }
-        }
-        evaluatedProjects
-    }
-
-    final static void assertNotEvaluated(boolean allEvaluated, String line) {
-        assert !allEvaluated : ALL_EVAL_ERROR + " Found this line after evaluation of all projects: $line"
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java
index 3a22dce..94963b3 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java
@@ -94,7 +94,7 @@ public class OutputScrapingExecutionFailure extends OutputScrapingExecutionResul
         return this;
     }
 
-    public DependencyResolutionFailure getDependencyResolutionFailure() {
-        return new DependencyResolutionFailure(this);
+    public DependencyResolutionFailure assertResolutionFailure(String configuration) {
+        return new DependencyResolutionFailure(this, configuration);
     }
 }
\ No newline at end of file
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 4a462f4..ba3bc85 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
@@ -24,7 +24,6 @@ import java.io.StringReader;
 import java.util.*;
 import java.util.regex.Pattern;
 
-import static java.util.Arrays.asList;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
 import static org.junit.Assert.assertThat;
@@ -67,11 +66,6 @@ public class OutputScrapingExecutionResult implements ExecutionResult {
         return this;
     }
 
-    public ExecutionResult assertProjectsEvaluated(String... projectPaths) {
-        new OutputScraper(getOutput()).assertProjectsEvaluated(asList(projectPaths));
-        return this;
-    }
-
     public Set<String> getSkippedTasks() {
         return new HashSet<String>(grepTasks(skippedTaskPattern));
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleExecuter.java
index 7ab0329..172e7fc 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleExecuter.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleExecuter.java
@@ -16,6 +16,7 @@
 
 package org.gradle.integtests.fixtures.executer;
 
+import org.gradle.api.Action;
 import org.gradle.internal.Factory;
 import org.gradle.process.internal.ExecHandleBuilder;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
@@ -37,7 +38,7 @@ class ParallelForkingGradleExecuter extends ForkingGradleExecuter {
     }
 
     @Override
-    protected ForkingGradleHandle createGradleHandle(String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
-        return new ParallelForkingGradleHandle(encoding, execHandleFactory);
+    protected ForkingGradleHandle createGradleHandle(Action<ExecutionResult> resultAssertion, String encoding, Factory<ExecHandleBuilder> execHandleFactory) {
+        return new ParallelForkingGradleHandle(resultAssertion, encoding, execHandleFactory);
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java
index dfc05ed..e233b2b 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ParallelForkingGradleHandle.java
@@ -16,20 +16,23 @@
 
 package org.gradle.integtests.fixtures.executer;
 
+import org.gradle.api.Action;
 import org.gradle.internal.Factory;
 import org.gradle.process.internal.AbstractExecHandleBuilder;
+import org.gradle.util.SingleMessageLogger;
 
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
+import static java.lang.String.format;
 import static org.hamcrest.Matchers.equalTo;
 import static org.junit.Assert.assertThat;
 
 public class ParallelForkingGradleHandle extends ForkingGradleHandle {
 
-    public ParallelForkingGradleHandle(String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
-        super(outputEncoding, execHandleFactory);
+    public ParallelForkingGradleHandle(Action<ExecutionResult> resultAssertion, String outputEncoding, Factory<? extends AbstractExecHandleBuilder> execHandleFactory) {
+        super(resultAssertion, outputEncoding, execHandleFactory);
     }
 
     @Override
@@ -60,11 +63,8 @@ public class ParallelForkingGradleHandle extends ForkingGradleHandle {
         @Override
         public String getOutput() {
             String output = super.getOutput();
-            String parallelWarningPrefix = "Parallel project execution is an \"incubating\" feature";
-            if (output.startsWith(parallelWarningPrefix)) {
-                return output.replaceFirst(String.format("(?m)%s.+$\n", parallelWarningPrefix), "");
-            }
-            return output;
+            String parallelWarningPrefix = String.format(SingleMessageLogger.INCUBATION_MESSAGE, ".*");
+            return output.replaceFirst(format("(?m)%s.*$\n", parallelWarningPrefix), "");
         }
 
         @Override
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy
index 02765ab..d791ba6 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProgressLoggingFixture.groovy
@@ -17,16 +17,50 @@
 package org.gradle.integtests.fixtures.executer
 
 import org.gradle.test.fixtures.file.TestDirectoryProvider
-import org.gradle.test.fixtures.file.TestDirectoryProviderFinder
 import org.gradle.test.fixtures.file.TestFile
-import org.gradle.util.RuleHelper
-import org.junit.rules.MethodRule
-import org.junit.runners.model.FrameworkMethod
-import org.junit.runners.model.Statement
 
-class ProgressLoggingFixture implements MethodRule {
+class ProgressLoggingFixture extends InitScriptExecuterFixture {
 
-    private TestFile loggingOutputFile = null
+    private TestFile fixtureData
+
+    ProgressLoggingFixture(GradleExecuter executer, TestDirectoryProvider testDir) {
+        super(executer, testDir)
+    }
+
+    List<String> progressContent
+
+    @Override
+    String initScriptContent() {
+        fixtureData = testDir.testDirectory.file("progress-fixture.log")
+        """import org.gradle.logging.internal.*
+           File outputFile = file("${fixtureData.toURI()}")
+           OutputEventListener outputEventListener = new OutputEventListener() {
+                void onOutput(OutputEvent event) {
+                    if (event instanceof ProgressStartEvent) {
+                        outputFile << "[START \$event.description]\\n"
+                    } else if (event instanceof ProgressEvent) {
+                        outputFile << "[\$event.status]\\n"
+                    } else if (event instanceof ProgressCompleteEvent) {
+                        outputFile << "[END \$event.description]\\n"
+                    }
+                }
+           }
+           def loggingOutputInternal = gradle.services.get(LoggingOutputInternal)
+           loggingOutputInternal.addOutputEventListener(outputEventListener)
+           buildFinished{
+                loggingOutputInternal.removeOutputEventListener(outputEventListener)
+           }"""
+    }
+
+    @Override
+    void afterBuild() {
+        if (fixtureData.exists()) {
+            progressContent = fixtureData.text.readLines()
+            assert fixtureData.delete()
+        } else {
+            progressContent = []
+        }
+    }
 
     boolean downloadProgressLogged(String url) {
         return progressLogged("Download", url)
@@ -37,7 +71,7 @@ class ProgressLoggingFixture implements MethodRule {
     }
 
     private boolean progressLogged(String operation, String url) {
-        def lines = loggingOutputFile.exists() ? loggingOutputFile.text.readLines() : []
+        def lines = progressContent
         def startIndex = lines.indexOf("[START " + operation + " " + url + "]")
         if (startIndex == -1) {
             return false
@@ -46,41 +80,4 @@ class ProgressLoggingFixture implements MethodRule {
         lines = lines[0..lines.indexOf("[END " + operation + " " + url + "]")]
         lines.size() >= 2
     }
-
-    Statement apply(Statement base, FrameworkMethod method, Object target) {
-        TestFile initFile
-        GradleExecuter executer = RuleHelper.getField(target, GradleExecuter)
-        TestDirectoryProvider testDirectoryProvider = new TestDirectoryProviderFinder().findFor(target)
-        TestFile temporaryFolder = testDirectoryProvider.testDirectory
-        loggingOutputFile = temporaryFolder.file("loggingoutput.log")
-        initFile = temporaryFolder.file("progress-logging-init.gradle")
-        initFile.text = """import org.gradle.logging.internal.*
-                           File outputFile = file("${loggingOutputFile.toURI()}")
-                           OutputEventListener outputEventListener = new OutputEventListener() {
-                                void onOutput(OutputEvent event) {
-                                    if (event instanceof ProgressStartEvent) {
-                                        outputFile << "[START \$event.description]\\n"
-                                    } else if (event instanceof ProgressEvent) {
-                                        outputFile << "[\$event.status]\\n"
-                                    } else if (event instanceof ProgressCompleteEvent) {
-                                        outputFile << "[END \$event.description]\\n"
-                                    }
-                                }
-                           }
-                           def loggingOutputInternal = gradle.services.get(LoggingOutputInternal)
-                           loggingOutputInternal.addOutputEventListener(outputEventListener)
-                           buildFinished{
-                                loggingOutputInternal.removeOutputEventListener(outputEventListener)
-                           }"""
-        executer.beforeExecute {
-            usingInitScript(initFile)
-        }
-
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-            }
-        };
-    }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProjectLifecycleFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProjectLifecycleFixture.groovy
new file mode 100644
index 0000000..8187711
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ProjectLifecycleFixture.groovy
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.fixtures.executer
+
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+
+import static java.util.Arrays.asList
+
+class ProjectLifecycleFixture extends InitScriptExecuterFixture {
+
+    private TestFile fixtureData
+
+    ProjectLifecycleFixture(GradleExecuter executer, TestDirectoryProvider testDir) {
+        super(executer, testDir)
+    }
+
+    List<String> configuredProjects
+
+    String initScriptContent() {
+        fixtureData = testDir.testDirectory.file("lifecycle-fixture-data.txt")
+        """File outputFile = file("${fixtureData.toURI()}")
+           def listener = new ProjectEvaluationListener() {
+                void afterEvaluate(Project project, ProjectState state) {
+                    outputFile << project.path + ";"
+                }
+                void beforeEvaluate(Project project) {}
+           }
+           gradle.addListener(listener)
+           buildFinished {
+               gradle.removeListener(listener)
+           }"""
+    }
+
+    void afterBuild() {
+        configuredProjects = asList(fixtureData.text.split(";"))
+        assert fixtureData.delete()
+    }
+
+    void assertProjectsConfigured(String ... projectPaths) {
+        assert configuredProjects == projectPaths
+    }
+}
\ No newline at end of file
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ReleasedGradleDistribution.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ReleasedGradleDistribution.groovy
index fe1666e..0c12656 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ReleasedGradleDistribution.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ReleasedGradleDistribution.groovy
@@ -15,64 +15,17 @@
  */
 package org.gradle.integtests.fixtures.executer
 
-import org.gradle.CacheUsage
-import org.gradle.api.Action
-import org.gradle.cache.PersistentCache
-import org.gradle.cache.internal.CacheFactory
-import org.gradle.cache.internal.DefaultCacheFactory
-import org.gradle.cache.internal.DefaultFileLockManager
-import org.gradle.cache.internal.DefaultProcessMetaDataProvider
-import org.gradle.cache.internal.FileLockManager.LockMode
-import org.gradle.internal.nativeplatform.ProcessEnvironment
-import org.gradle.internal.nativeplatform.services.NativeServices
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.DistributionLocator
-import org.gradle.util.GradleVersion
 
-class ReleasedGradleDistribution extends DefaultGradleDistribution {
-
-    private static final CACHE_FACTORY = createCacheFactory()
-
-    private static CacheFactory createCacheFactory() {
-        return new DefaultCacheFactory(
-                new DefaultFileLockManager(
-                        new DefaultProcessMetaDataProvider(
-                                NativeServices.getInstance().get(ProcessEnvironment)),
-                        20 * 60 * 1000 // allow up to 20 minutes to download a distribution
-                )).create()
-    }
-
-    private final TestFile versionDir
-    private PersistentCache cache
+class ReleasedGradleDistribution extends DownloadableGradleDistribution {
 
     ReleasedGradleDistribution(String version, TestFile versionDir) {
-        super(GradleVersion.version(version), versionDir.file("gradle-$version"), versionDir.file("gradle-$version-bin.zip"))
-        this.versionDir = versionDir
+        super(version, versionDir)
     }
 
-    TestFile getBinDistribution() {
-        download()
-        super.getBinDistribution()
-    }
-
-    def TestFile getGradleHomeDir() {
-        download()
-        super.getGradleHomeDir()
-    }
-
-    private void download() {
-        if (cache == null) {
-            def downloadAction = { cache ->
-                URL url = new DistributionLocator().getDistributionFor(getVersion()).toURL()
-                System.out.println("downloading $url")
-                super.binDistribution.copyFrom(url)
-                super.binDistribution.usingNativeTools().unzipTo(versionDir)
-            }
-            //noinspection GrDeprecatedAPIUsage
-            cache = CACHE_FACTORY.open(versionDir, version.version, CacheUsage.ON, null, [:], LockMode.Shared, downloadAction as Action)
-        }
-
-        super.binDistribution.assertIsFile()
-        super.gradleHomeDir.assertIsDir()
+    @Override
+    protected URL getDownloadURL() {
+        return new DistributionLocator().getDistributionFor(getVersion()).toURL();
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy
index 1f5e553..2eeb441 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptor.groovy
@@ -15,18 +15,17 @@
  */
 
 package org.gradle.test.fixtures.ivy
-
 import groovy.xml.QName
-
-import java.util.regex.Pattern
+import org.apache.commons.lang.StringUtils
 
 class IvyDescriptor {
-    final Map<String, IvyDescriptorDependencyConfiguration> dependencies = [:]
-    Map<String, IvyDescriptorArtifact> artifacts = [:]
     Map<String, IvyDescriptorConfiguration> configurations = [:]
+    List<IvyDescriptorArtifact> artifacts = []
+    Map<String, IvyDescriptorDependency> dependencies = [:]
     String organisation
     String module
     String revision
+    String status
     String description
 
     IvyDescriptor(File ivyFile) {
@@ -34,43 +33,64 @@ class IvyDescriptor {
         organisation = ivy.info[0]. at organisation
         module = ivy.info[0]. at module
         revision = ivy.info[0]. at revision
+        status = ivy.info[0]. at status
         description = ivy.info[0].description[0]?.text()
 
-        ivy.configurations[0].conf.each {
+        ivy.configurations.conf.each {
             configurations[it. at name] = new IvyDescriptorConfiguration(
                     name: it. at name, visibility: it. at visibility, description: it. at description,
-                    extend: (it. at extends ?: "").split(",")*.trim()
+                    extend: it. at extends == null ? null : it. at extends.split(",")*.trim()
             )
         }
 
-        ivy.dependencies.dependency.each { dep ->
-            def configName = dep. at conf ?: "default"
-            def matcher = Pattern.compile("(\\w+)->\\w+").matcher(configName)
-            if (matcher.matches()) {
-                configName = matcher.group(1)
-            }
-            def config = dependencies[configName]
-            if (!config) {
-                config = new IvyDescriptorDependencyConfiguration()
-                dependencies[configName] = config
-            }
-            config.addDependency(dep. at org, dep. at name, dep. at rev)
-        }
-
         ivy.publications.artifact.each { artifact ->
             def ivyArtifact = new IvyDescriptorArtifact(
                     name: artifact. at name, type: artifact. at type,
-                    ext: artifact. at ext, conf: artifact. at conf.split(",") as List,
+                    ext: artifact. at ext,
+                    conf: artifact. at conf == null ? null : artifact. at conf.split(",") as List,
                     mavenAttributes: artifact.attributes().findAll { it.key instanceof QName && it.key.namespaceURI == "http://ant.apache.org/ivy/maven" }.collectEntries { [it.key.localPart, it.value] }
             )
 
-            artifacts.put(ivyArtifact.name, ivyArtifact)
+            artifacts.add(ivyArtifact)
+        }
+
+
+        ivy.dependencies.dependency.each { dep ->
+            def ivyDependency = new IvyDescriptorDependency(
+                    org: dep. at org,
+                    module: dep. at name,
+                    revision: dep. at rev,
+                    conf: dep. at conf
+            )
+            def key = "${ivyDependency.org}:${ivyDependency.module}:${ivyDependency.revision}"
+            dependencies[key] = ivyDependency
         }
+    }
 
+    IvyDescriptorArtifact expectArtifact(String name, String ext, String classifier = null) {
+        return oneResult(artifacts.findAll({
+            it.name == name && it.ext == ext && it.classifier == classifier
+        }), [name, ext, classifier])
     }
 
     IvyDescriptorArtifact expectArtifact(String name) {
-        assert artifacts.containsKey(name)
-        artifacts[name]
+        return oneResult(artifacts.findAll({
+            it.name == name
+        }), [name])
+    }
+
+    private static IvyDescriptorArtifact oneResult(List<IvyDescriptorArtifact> artifacts, def description) {
+        assert artifacts.size() > 0 : "Expected artifact not found: $description"
+        assert artifacts.size() == 1 : "Multiple artifacts found: $description"
+        return artifacts.get(0)
+    }
+
+    def assertDependsOn(String[] expected) {
+        assert dependencies.size() == expected.length
+        expected.each {
+            String key = StringUtils.substringBefore(it, "@")
+            String conf = StringUtils.substringAfter(it, "@") + "->default"
+            assert dependencies[key].hasConf(conf)
+        }
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorArtifact.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorArtifact.groovy
index 4fdf73e..bfbc7dd 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorArtifact.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorArtifact.groovy
@@ -22,4 +22,25 @@ class IvyDescriptorArtifact {
     String ext
     List<String> conf
     Map<String, String> mavenAttributes
+
+    void hasAttributes(def ext, def type, def conf, def classifier = null) {
+        assert this.ext == ext
+        assert this.type == type
+        assert this.conf == conf
+        assert this.classifier == classifier
+    }
+
+    IvyDescriptorArtifact hasConf(def conf) {
+        assert this.conf == conf
+        return this
+    }
+
+    IvyDescriptorArtifact hasType(def type) {
+        assert this.type == type
+        return this
+    }
+
+    String getClassifier() {
+        this.mavenAttributes.get("classifier")
+    }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependency.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependency.groovy
new file mode 100644
index 0000000..a3ca117
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependency.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.ivy
+
+class IvyDescriptorDependency {
+    String org
+    String module
+    String revision
+    String conf
+
+    IvyDescriptorDependency hasConf(def conf) {
+        assert this.conf == conf
+        return this
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependencyConfiguration.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependencyConfiguration.groovy
deleted file mode 100644
index 66a8652..0000000
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyDescriptorDependencyConfiguration.groovy
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.test.fixtures.ivy
-
-class IvyDescriptorDependencyConfiguration {
-    final dependencies = []
-
-    void addDependency(String org, String module, String revision) {
-        dependencies << [org: org, module: module, revision: revision]
-    }
-
-    void assertDependsOn(String org, String module, String revision) {
-        def dep = [org: org, module: module, revision: revision]
-        if (!dependencies.find { it == dep}) {
-            throw new AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
-        }
-    }
-}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy
index 1a1ed52..c28754a 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyFileModule.groovy
@@ -17,6 +17,8 @@ package org.gradle.test.fixtures.ivy
 
 import org.apache.ivy.core.IvyPatternHelper
 import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.gradle.api.Action
+import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.util.hash.HashUtil
 
@@ -33,6 +35,7 @@ class IvyFileModule extends AbstractIvyModule {
     String status = "integration"
     boolean noMetaData
     int publishCount = 1
+    XmlTransformer transformer = new XmlTransformer()
 
     IvyFileModule(String ivyPattern, String artifactPattern, TestFile moduleDir, String organisation, String module, String revision) {
         this.ivyPattern = ivyPattern
@@ -46,18 +49,33 @@ class IvyFileModule extends AbstractIvyModule {
         configurations['default'] = [extendsFrom: ['runtime'], transitive: true]
     }
 
+    IvyFileModule configuration(String name, List extendsFrom = []) {
+        configurations[name] = [extendsFrom: extendsFrom, transitive: true]
+        return this
+    }
+
+    IvyFileModule withXml(Closure action) {
+        transformer.addAction(action);
+        return this
+    }
+
     /**
      * Adds an additional artifact to this module.
      * @param options Can specify any of name, type or classifier
      * @return this
      */
     IvyFileModule artifact(Map<String, ?> options) {
-        artifacts << [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null]
+        artifacts << [name: options.name ?: module, type: options.type ?: 'jar', classifier: options.classifier ?: null, conf: options.conf ?: '*']
         return this
     }
 
     IvyFileModule dependsOn(String organisation, String module, String revision) {
-        dependencies << [organisation: organisation, module: module, revision: revision]
+        dependsOn([organisation: organisation, module: module, revision: revision])
+        return this
+    }
+
+    IvyFileModule dependsOn(Map<String, String> attributes) {
+        dependencies << attributes
         return this
     }
 
@@ -124,7 +142,9 @@ class IvyFileModule extends AbstractIvyModule {
         }
 
         publish(ivyFile) {
-            ivyFile.text = """<?xml version="1.0" encoding="UTF-8"?>
+            transformer.transform(ivyFile, new Action<Writer>() {
+                void execute(Writer ivyFileWriter) {
+                    ivyFileWriter << """<?xml version="1.0" encoding="UTF-8"?>
 <ivy-module version="1.0" xmlns:m="http://ant.apache.org/ivy/maven">
     <!-- ${publishCount} -->
 	<info organisation="${organisation}"
@@ -134,34 +154,38 @@ class IvyFileModule extends AbstractIvyModule {
 	/>
 	<configurations>"""
             configurations.each { name, config ->
-                ivyFile << "<conf name='$name' visibility='public'"
+                ivyFileWriter << "<conf name='$name' visibility='public'"
                 if (config.extendsFrom) {
-                    ivyFile << " extends='${config.extendsFrom.join(',')}'"
+                    ivyFileWriter << " extends='${config.extendsFrom.join(',')}'"
                 }
                 if (!config.transitive) {
-                    ivyFile << " transitive='false'"
+                    ivyFileWriter << " transitive='false'"
                 }
-                ivyFile << "/>"
+                ivyFileWriter << "/>"
             }
-            ivyFile << """</configurations>
+            ivyFileWriter << """</configurations>
 	<publications>
 """
             artifacts.each { artifact ->
-                ivyFile << """<artifact name="${artifact.name}" type="${artifact.type}" ext="${artifact.type}" conf="*" m:classifier="${artifact.classifier ?: ''}"/>
+                ivyFileWriter << """<artifact name="${artifact.name}" type="${artifact.type}" ext="${artifact.type}" conf="${artifact.conf}" m:classifier="${artifact.classifier ?: ''}"/>
 """
             }
-            ivyFile << """
+            ivyFileWriter << """
 	</publications>
 	<dependencies>
 """
             dependencies.each { dep ->
-                ivyFile << """<dependency org="${dep.organisation}" name="${dep.module}" rev="${dep.revision}"/>
+                def confAttribute = dep.conf == null ? "" : """ conf="${dep.conf}" """
+                def revConstraint = dep.revConstraint == null ? "" : """ revConstraint="${dep.revConstraint}" """
+                ivyFileWriter << """<dependency org="${dep.organisation}" name="${dep.module}" rev="${dep.revision}" ${confAttribute} ${revConstraint}/>
 """
             }
-            ivyFile << """
+            ivyFileWriter << """
     </dependencies>
 </ivy-module>
         """
+                }
+            })
         }
         return this
     }
@@ -200,4 +224,32 @@ class IvyFileModule extends AbstractIvyModule {
         return HashUtil.createHash(file, algorithm).asHexString()
     }
 
+    void assertNotPublished() {
+        ivyFile.assertDoesNotExist()
+    }
+
+    void assertPublished() {
+        assert ivyFile.assertExists()
+        assert ivy.organisation == organisation
+        assert ivy.module == module
+        assert ivy.revision == revision
+    }
+
+    void assertPublishedAsJavaModule() {
+        assertPublished()
+        assertArtifactsPublished("${module}-${revision}.jar", "ivy-${revision}.xml")
+        ivy.expectArtifact(module, "jar").hasAttributes("jar", "jar", ["runtime"], null)
+    }
+
+    void assertPublishedAsWebModule() {
+        assertPublished()
+        assertArtifactsPublished("${module}-${revision}.war", "ivy-${revision}.xml")
+        ivy.expectArtifact(module, "war").hasAttributes("war", "war", ["master"])
+    }
+
+    void assertPublishedAsEarModule() {
+        assertPublished()
+        assertArtifactsPublished("${module}-${revision}.ear", "ivy-${revision}.xml")
+        ivy.expectArtifact(module, "ear").hasAttributes("ear", "ear", ["master"])
+    }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy
index 68aa989..52dca48 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyHttpModule.groovy
@@ -55,6 +55,11 @@ class IvyHttpModule extends AbstractIvyModule {
         return this
     }
 
+    IvyHttpModule dependsOn(Map<String, String> attributes) {
+        backingModule.dependsOn(attributes)
+        return this
+    }
+
     IvyHttpModule artifact(Map<String, ?> options) {
         backingModule.artifact(options)
         return this
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java
index daa8b45..9d1e47b 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyModule.java
@@ -34,6 +34,8 @@ public interface IvyModule {
 
     IvyModule dependsOn(String organisation, String module, String revision);
 
+    IvyModule dependsOn(Map<String, String> attributes);
+
     IvyModule artifact(Map<String, ?> options);
 
     /**
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy
index d7c744d..5dfde12 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/M2Installation.groovy
@@ -27,12 +27,14 @@ class M2Installation implements Action<GradleExecuter> {
     final TestFile userM2Directory
     final TestFile userSettingsFile
     final TestFile globalMavenDirectory
+    final TestFile globalSettingsFile
 
     public M2Installation(TestFile m2Directory) {
         userHomeDir = m2Directory.createDir("maven_home")
         userM2Directory = userHomeDir.createDir(".m2")
         userSettingsFile = userM2Directory.file("settings.xml")
         globalMavenDirectory = userHomeDir.createDir("m2_home")
+        globalSettingsFile = globalMavenDirectory.file("conf/settings.xml")
     }
 
     MavenFileRepository mavenRepo() {
@@ -48,8 +50,7 @@ class M2Installation implements Action<GradleExecuter> {
     }
 
     M2Installation generateGlobalSettingsFile(MavenFileRepository globalRepository = mavenRepo()) {
-        def settings = globalMavenDirectory.file("conf/settings.xml").createFile()
-        settings.text = """
+        globalSettingsFile.createFile().text = """
 <settings>
     <localRepository>${globalRepository.rootDir.absolutePath}</localRepository>
 </settings>"""
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenDependency.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenDependency.groovy
new file mode 100644
index 0000000..c439240
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenDependency.groovy
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.maven
+
+class MavenDependency {
+    String groupId
+    String artifactId
+    String version
+    String classifier
+    String type
+
+    MavenDependency hasType(def type) {
+        assert this.type == type
+        return this
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy
index 7ee204a..9b33a43 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenFileModule.groovy
@@ -22,6 +22,7 @@ import org.gradle.util.hash.HashUtil
 import java.text.SimpleDateFormat
 
 class MavenFileModule implements MavenModule {
+    private static final String MAVEN_METADATA_FILE = "maven-metadata.xml"
     final TestFile moduleDir
     final String groupId
     final String artifactId
@@ -97,25 +98,44 @@ class MavenFileModule implements MavenModule {
         pomFile.assertDoesNotExist()
     }
 
-    void assertPublishedAsJavaModule() {
+    void assertPublished() {
         assert pomFile.assertExists()
         assert parsedPom.groupId == groupId
         assert parsedPom.artifactId == artifactId
         assert parsedPom.version == version
-        assertArtifactsPublished("${artifactId}-${version}.jar", "${artifactId}-${version}.pom")
+    }
+
+    void assertPublishedAsPomModule() {
+        assertPublished()
+        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.pom")
+        assert parsedPom.packaging == "pom"
+    }
+
+    void assertPublishedAsJavaModule() {
+        assertPublished()
+        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.jar", "${artifactId}-${publishArtifactVersion}.pom")
+        assert parsedPom.packaging == null
+    }
+
+    void assertPublishedAsWebModule() {
+        assertPublished()
+        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.war", "${artifactId}-${publishArtifactVersion}.pom")
+        assert parsedPom.packaging == 'war'
+    }
+
+    void assertPublishedAsEarModule() {
+        assertPublished()
+        assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.ear", "${artifactId}-${publishArtifactVersion}.pom")
+        assert parsedPom.packaging == 'ear'
     }
 
     /**
      * Asserts that exactly the given artifacts have been deployed, along with their checksum files
      */
     void assertArtifactsPublished(String... names) {
-        def artifactNames = names
+        def artifactNames = names as Set
         if (uniqueSnapshots && version.endsWith('-SNAPSHOT')) {
-            def metaData = new XmlParser().parse(moduleDir.file('maven-metadata.xml'))
-            def timestamp = metaData.versioning.snapshot.timestamp[0].text().trim()
-            def build = metaData.versioning.snapshot.buildNumber[0].text().trim()
-            artifactNames = names.collect { it.replace('-SNAPSHOT', "-${timestamp}-${build}")}
-            artifactNames.add("maven-metadata.xml")
+            artifactNames.add(MAVEN_METADATA_FILE)
         }
         assert moduleDir.isDirectory()
         Set actual = moduleDir.list() as Set
@@ -140,11 +160,11 @@ class MavenFileModule implements MavenModule {
     }
 
     TestFile getMetaDataFile() {
-        moduleDir.file("maven-metadata.xml")
+        moduleDir.file(MAVEN_METADATA_FILE)
     }
 
     TestFile getRootMetaDataFile() {
-        moduleDir.parentFile.file("maven-metadata.xml")
+        moduleDir.parentFile.file(MAVEN_METADATA_FILE)
     }
 
     TestFile getArtifactFile(Map options = [:]) {
@@ -179,11 +199,22 @@ class MavenFileModule implements MavenModule {
 
     String getPublishArtifactVersion() {
         if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
-            return "${version.replaceFirst('-SNAPSHOT$', '')}-${timestampFormat.format(publishTimestamp)}-${publishCount}"
+            return "${version.replaceFirst('-SNAPSHOT$', '')}-${uniqueSnapshotVersion}"
         }
         return version
     }
 
+    private String getUniqueSnapshotVersion() {
+        assert uniqueSnapshots && version.endsWith('-SNAPSHOT')
+        if (metaDataFile.isFile()) {
+            def metaData = new XmlParser().parse(metaDataFile.assertIsFile())
+            def timestamp = metaData.versioning.snapshot.timestamp[0].text().trim()
+            def build = metaData.versioning.snapshot.buildNumber[0].text().trim()
+            return "${timestamp}-${build}"
+        }
+        return "${timestampFormat.format(publishTimestamp)}-${publishCount}"
+    }
+
     Date getPublishTimestamp() {
         return new Date(updateFormat.parse("20100101120000").time + publishCount * 1000)
     }
@@ -194,7 +225,6 @@ class MavenFileModule implements MavenModule {
 
         updateRootMavenMetaData(rootMavenMetaData)
         if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) {
-            def metaDataFile = moduleDir.file('maven-metadata.xml')
             publish(metaDataFile) {
                 metaDataFile.text = """
 <metadata>
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenPom.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenPom.groovy
index 4c78de4..ac38347 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenPom.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenPom.groovy
@@ -42,7 +42,16 @@ class MavenPom {
                     scope = new MavenScope()
                     scopes[scopeName] = scope
                 }
-                scope.addDependency(dep.groupId.text(), dep.artifactId.text(), dep.version.text())
+                MavenDependency mavenDependency = new MavenDependency(
+                        groupId: dep.groupId.text(),
+                        artifactId: dep.artifactId.text(),
+                        version: dep.version.text(),
+                        classifier: dep.classifier ? dep.classifier.text() : null,
+                        type: dep.type ? dep.type.text() : null
+                )
+                def key = "${mavenDependency.groupId}:${mavenDependency.artifactId}:${mavenDependency.version}"
+                key += mavenDependency.classifier ? ":${mavenDependency.classifier}" : ""
+                scope.dependencies[key] = mavenDependency
             }
         }
     }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy
index 10d1b66..b67f38a 100644
--- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenScope.groovy
@@ -18,21 +18,30 @@
 
 package org.gradle.test.fixtures.maven
 
+import org.apache.commons.lang.StringUtils
+
 class MavenScope {
-    final dependencies = []
+    Map<String, MavenDependency> dependencies = [:]
 
-    void addDependency(String groupId, String artifactId, String version) {
-        dependencies << [groupId: groupId, artifactId: artifactId, version: version]
-    }
+    void assertDependsOn(String[] expected) {
+        assert dependencies.size() == expected.length
+        expected.each {
+            String key = StringUtils.substringBefore(it, "@")
+            def dependency = expectDependency(key)
 
-    void assertDependsOnArtifacts(String... artifactIds) {
-        assert dependencies.collect { it.artifactId} as Set == artifactIds as Set
+            String type = null
+            if (it != key) {
+                type = StringUtils.substringAfter(it, "@")
+            }
+            assert dependency.hasType(type)
+        }
     }
 
-    void assertDependsOn(String groupId, String artifactId, String version) {
-        def dep = [groupId: groupId, artifactId: artifactId, version: version]
-        if (!dependencies.find { it == dep }) {
-            throw new AssertionError("Could not find expected dependency $dep. Actual: $dependencies")
+    MavenDependency expectDependency(String key) {
+        final dependency = dependencies[key]
+        if (dependency == null) {
+            throw new AssertionError("Could not find expected dependency $dep. Actual: ${dependencies.values()}")
         }
+        return dependency
     }
 }
diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/publish/Identifier.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/publish/Identifier.java
new file mode 100644
index 0000000..cf46a7e
--- /dev/null
+++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/publish/Identifier.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.test.fixtures.publish;
+
+import org.gradle.internal.os.OperatingSystem;
+import org.gradle.util.GUtil;
+
+public class Identifier {
+    private static final String PUNCTUATION_CHARS = "-'!@#$%^&*()_+=,.?{}[]<>";
+    private static final String NON_ASCII_CHARS = "-√æず∫ʙぴ₦ガき∆ç√∫";
+    private static final String FILESYSTEM_RESERVED_CHARS = "-./\\?%*:|\"<>";
+    private static final String XML_MARKUP_CHARS = "-<with>some<xml-markup/></with>";
+
+    private final String suffix;
+
+    public Identifier(String suffix) {
+        this.suffix = GUtil.elvis(suffix, "");
+    }
+
+    public Identifier withPunctuation() {
+        return new Identifier(suffix + PUNCTUATION_CHARS);
+    }
+
+    public Identifier withNonAscii() {
+        return new Identifier(suffix + NON_ASCII_CHARS);
+    }
+
+    public Identifier withReservedFileSystemChars() {
+        return new Identifier(suffix + FILESYSTEM_RESERVED_CHARS);
+    }
+
+    public Identifier withMarkup() {
+        return new Identifier(suffix + XML_MARKUP_CHARS);
+    }
+
+    public Identifier withWhiteSpace() {
+        return new Identifier(suffix + " with white space");
+    }
+
+    public Identifier safeForFileName() {
+        return without(getUnsupportedFileNameCharacters());
+    }
+
+    public Identifier without(String toRemove) {
+        String newSuffix = suffix;
+        for (char c : toRemove.toCharArray()) {
+            newSuffix = newSuffix.replace(c, '-');
+        }
+        return new Identifier(newSuffix);
+    }
+
+    private static String getUnsupportedFileNameCharacters() {
+        if (OperatingSystem.current().isWindows()) {
+            return "<>:\"/\\|?*";
+        }
+        return "/\\";
+    }
+
+    public String decorate(String prefix) {
+        return prefix + suffix;
+    }
+
+    @Override
+    public String toString() {
+        return suffix;
+    }
+
+    public static Identifier getPunctuation() {
+        return new Identifier("").withPunctuation();
+    }
+
+    public static Identifier getNonAscii() {
+        return new Identifier("").withNonAscii();
+    }
+
+    public static Identifier getFileSystemReserved() {
+        return new Identifier("").withReservedFileSystemChars();
+    }
+
+    public static Identifier getXmlMarkup() {
+        return new Identifier("").withMarkup();
+    }
+
+    public static Identifier getWhiteSpace() {
+        return new Identifier("").withWhiteSpace();
+    }
+}
diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScraperTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScraperTest.groovy
deleted file mode 100644
index a5e01f7..0000000
--- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScraperTest.groovy
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.fixtures.executer
-
-import spock.lang.Specification
-
-/**
- * by Szczepan Faber, created at: 12/21/12
- */
-class OutputScraperTest extends Specification {
-
-    def output = """Included projects: [root project 'unknown-test-122', project ':api', project ':impl', project ':util']
-Evaluating root project 'unknown-test-122' using build file '/Users/szczepan/gradle/gradle.src/build/tmp/test files/AbstractIntegrationSpec/unknown-test-122/build.gradle'.
-Evaluating project ':api' using empty build file
-Selected primary task ':api:build'
-Evaluating project ':foo:bar' using build file '/Users/szczepan/gradle/gradle.src/build/tmp/test files/AbstractIntegrationSpec/unknown-test-122/api/build.gradle'.
-Selected primary task ':foo:bar:build'
-All projects evaluated.
-Tasks to be executed: ...
-"""
-
-    def "finds evaluated projects"() {
-        expect:
-        new OutputScraper("").evaluatedProjects == []
-        new OutputScraper(output).evaluatedProjects == [':', ':api', ':foo:bar']
-    }
-
-    def "sucessfully asserts projects evaluated"() {
-        expect:
-        new OutputScraper("").assertProjectsEvaluated([])
-        new OutputScraper(output).assertProjectsEvaluated([':', ':api', ':foo:bar'])
-    }
-
-    def "asserts projects evaluated"() {
-        when:
-        new OutputScraper(output).assertProjectsEvaluated(expected)
-        then:
-        thrown(AssertionError)
-
-        where:
-        expected << [
-            [':', ':api', ':foo:bar', 'extra'], //extra
-            ['extra', ':', ':api', ':foo:bar'], //extra
-            [':api', ':', ':foo:bar'],    //order
-            ['root', ':api', ':foo:bar'], //no match
-            []
-        ]
-    }
-
-    def "provides decent exception when no evaluated projects found"() {
-        when:
-        new OutputScraper("").assertProjectsEvaluated([":"])
-        then:
-        def ex = thrown(AssertionError)
-        ex.message == OutputScraper.EVAL_NOT_FOUND
-    }
-
-    def "all evaluated hook must be fired after all projects evaluated"() {
-        def output = """...
-Evaluating project ':foo' using build file...
-All projects evaluated.
-Evaluating project ':api' using build file...
-...
-"""
-
-        when:
-        new OutputScraper(output).assertProjectsEvaluated([":foo", ":api"])
-        then:
-        def ex = thrown(AssertionError)
-        ex.message.contains OutputScraper.ALL_EVAL_ERROR
-    }
-
-    def "all evaluated hook must be fired after root project evaluated"() {
-        def output = """...
-All projects evaluated.
-Evaluating root project 'foo' using build file...
-...
-"""
-
-        when:
-        new OutputScraper(output).assertProjectsEvaluated([":"])
-        then:
-        def ex = thrown(AssertionError)
-        ex.message.contains OutputScraper.ALL_EVAL_ERROR
-    }
-}
\ No newline at end of file
diff --git a/subprojects/internal-testing/internal-testing.gradle b/subprojects/internal-testing/internal-testing.gradle
index 751f5e2..4fb6bf8 100644
--- a/subprojects/internal-testing/internal-testing.gradle
+++ b/subprojects/internal-testing/internal-testing.gradle
@@ -28,6 +28,7 @@ dependencies {
     compile libraries.junit
     compile libraries.jmock
     compile libraries.spock
+    compile libraries.jsoup
     compile libraries.jsr305 //it is a guava dependency needed for compilation on ibm vm / windows
 }
 
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy
index 3201217..f73be8d 100644
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/DefaultTestExecutionResult.groovy
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012 the original author or authors.
+ * Copyright 2011 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,170 +15,84 @@
  */
 package org.gradle.integtests.fixtures
 
-import groovy.util.slurpersupport.GPathResult
 import org.gradle.test.fixtures.file.TestFile
 import org.hamcrest.Matcher
 
-import static org.hamcrest.Matchers.*
-import static org.junit.Assert.assertThat
 
 class DefaultTestExecutionResult implements TestExecutionResult {
-    private final TestFile buildDir
 
-    def DefaultTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
-        this.buildDir = projectDir.file(buildDirName)
-    }
+    def results = []
 
-    boolean hasJUnitXmlResults() {
-        xmlResultsDir().list().length > 0
+    public DefaultTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
+        results << new HtmlTestExecutionResult(projectDir)
+        results << new JUnitXmlTestExecutionResult(projectDir, buildDirName)
     }
 
     TestExecutionResult assertTestClassesExecuted(String... testClasses) {
-        Map<String, File> classes = findClasses()
-        assertThat(classes.keySet(), equalTo(testClasses as Set));
+        results.each { result ->
+            result.assertTestClassesExecuted(testClasses)
+        }
         this
     }
 
     TestClassExecutionResult testClass(String testClass) {
-        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass)
-    }
-
-    private def findTestClass(String testClass) {
-        def classes = findClasses()
-        assertThat(classes.keySet(), hasItem(testClass))
-        def classFile = classes.get(testClass)
-        assertThat(classFile, notNullValue())
-        return new XmlSlurper().parse(classFile)
+        new DefaultTestClassExecutionResult(results.collect {it.testClass(testClass)});
     }
 
-    private def findClasses() {
-        xmlResultsDir().assertIsDir()
+    private class DefaultTestClassExecutionResult implements TestClassExecutionResult {
+        def testClassResults
 
-        Map<String, File> classes = [:]
-        buildDir.file('test-results').eachFile { File file ->
-            def matcher = (file.name =~ /TEST-(.+)\.xml/)
-            if (matcher.matches()) {
-                classes[matcher.group(1)] = file
-            }
+        private DefaultTestClassExecutionResult(def classExecutionResults) {
+            this.testClassResults = classExecutionResults;
         }
-        return classes
-    }
-
-    private TestFile xmlResultsDir() {
-        buildDir.file('test-results')
-    }
-}
-
-class JUnitTestClassExecutionResult implements TestClassExecutionResult {
-    GPathResult testClassNode
-    String testClassName
-    boolean checked
-
-    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName) {
-        this.testClassNode = testClassNode
-        this.testClassName = testClassName
-    }
-
-    def JUnitTestClassExecutionResult(String content, String testClassName) {
-        this(new XmlSlurper().parse(new StringReader(content)), testClassName)
-    }
-
-    TestClassExecutionResult assertTestsExecuted(String... testNames) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), equalTo(testNames as Set))
-        this
-    }
-
-    TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
-        assert testClassNode. at tests == tests
-        assert testClassNode. at failures == failures
-        assert testClassNode. at errors == errors
-        this
-    }
-
-    TestClassExecutionResult assertTestPassed(String name) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), hasItem(name))
-        assertThat(testMethods[name].failure.size(), equalTo(0))
-        this
-    }
 
-    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
-        Map<String, Node> testMethods = findTests()
-        assertThat(testMethods.keySet(), hasItem(name))
-
-        def failures = testMethods[name].failure
-        assertThat("Expected ${messageMatchers.length} failures. Found: $failures", failures.size(), equalTo(messageMatchers.length))
+        TestClassExecutionResult assertTestsExecuted(String... testNames) {
+            testClassResults*.assertTestsExecuted(testNames)
+            return this
+        }
 
-        for (int i = 0; i < messageMatchers.length; i++) {
-            assertThat(failures[i]. at message.text(), messageMatchers[i])
+        TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
+            testClassResults*.assertTestCount(tests, failures, errors)
+            this
         }
-        this
-    }
 
-    TestClassExecutionResult assertTestSkipped(String name) {
-        throw new UnsupportedOperationException()
-    }
+        TestClassExecutionResult assertTestsSkipped(String... testNames) {
+            testClassResults*.assertTestsSkipped(testNames)
+            return this
+        }
 
-    TestClassExecutionResult assertTestsSkipped(String... testNames) {
-        Map<String, Node> testMethods = findIgnoredTests()
-        assertThat(testMethods.keySet(), equalTo(testNames as Set))
-        this
-    }
+        TestClassExecutionResult assertTestPassed(String name) {
+            testClassResults*.assertTestPassed(name)
+            this
+        }
 
-    TestClassExecutionResult assertConfigMethodPassed(String name) {
-        throw new UnsupportedOperationException();
-    }
+        TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
+            testClassResults*.assertTestFailed(name, messageMatchers)
+            this
+        }
 
-    TestClassExecutionResult assertConfigMethodFailed(String name) {
-        throw new UnsupportedOperationException();
-    }
+        TestClassExecutionResult assertTestSkipped(String name) {
+            testClassResults*.assertTestSkipped(name)
+        }
 
-    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
-        def stdout = testClassNode.'system-out'[0].text();
-        assertThat(stdout, matcher)
-        this
-    }
+        TestClassExecutionResult assertConfigMethodPassed(String name) {
+            testClassResults*.assertConfigMethodPassed(name)
+            this
+        }
 
-    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
-        def stderr = testClassNode.'system-err'[0].text();
-        assertThat(stderr, matcher)
-        this
-    }
+        TestClassExecutionResult assertConfigMethodFailed(String name) {
+            testClassResults*.assertConfigMethodFailed(name)
+            this
+        }
 
-    private def findTests() {
-        if (!checked) {
-            assertThat(testClassNode.name(), equalTo('testsuite'))
-            assertThat(testClassNode. at name.text(), equalTo(testClassName))
-            assertThat(testClassNode. at tests.text(), not(equalTo('')))
-            assertThat(testClassNode. at failures.text(), not(equalTo('')))
-            assertThat(testClassNode. at errors.text(), not(equalTo('')))
-            assertThat(testClassNode. at time.text(), not(equalTo('')))
-            assertThat(testClassNode. at timestamp.text(), not(equalTo('')))
-            assertThat(testClassNode. at hostname.text(), not(equalTo('')))
-            assertThat(testClassNode.properties.size(), equalTo(1))
-            testClassNode.testcase.each { node ->
-                assertThat(node. at classname.text(), equalTo(testClassName))
-                assertThat(node. at name.text(), not(equalTo('')))
-                assertThat(node. at time.text(), not(equalTo('')))
-                node.failure.each { failure ->
-                    assertThat(failure. at message.size(), equalTo(1))
-                    assertThat(failure. at type.text(), not(equalTo('')))
-                    assertThat(failure.text(), not(equalTo('')))
-                }
-            }
-            assertThat(testClassNode.'system-out'.size(), equalTo(1))
-            assertThat(testClassNode.'system-err'.size(), equalTo(1))
-            checked = true
+        TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+            testClassResults*.assertStdout(matcher)
+            this
         }
-        Map testMethods = [:]
-        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
-        return testMethods
-    }
 
-    private def findIgnoredTests() {
-        Map testMethods = [:]
-        testClassNode."ignored-testcase".each { testMethods[it. at name.text()] = it }
-        return testMethods
+        TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+            testClassResults*.assertStderr(matcher)
+            this
+        }
     }
 }
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/HtmlTestExecutionResult.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/HtmlTestExecutionResult.groovy
new file mode 100644
index 0000000..7f88634
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/HtmlTestExecutionResult.groovy
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures
+
+import org.hamcrest.Matcher
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+
+import static junit.framework.Assert.assertTrue
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.assertThat
+
+class HtmlTestExecutionResult implements TestExecutionResult {
+
+    private File htmlReportDirectory
+
+    public HtmlTestExecutionResult(File projectDirectory, String testReportDirectory = "tests") {
+        this.htmlReportDirectory = new File(projectDirectory, "build/reports/$testReportDirectory");
+    }
+
+    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
+        indexContainsTestClass(testClasses)
+        assertHtmlReportForTestClassExists(testClasses)
+        return this
+    }
+
+    def indexContainsTestClass(String... testClasses) {
+        def indexFile = new File(htmlReportDirectory, "index.html")
+        assert indexFile.exists()
+        Document html = Jsoup.parse(indexFile, null)
+        testClasses.each { testClass ->
+            assert html.select("a").find { it.text() == testClass } != null
+        }
+    }
+
+    def assertHtmlReportForTestClassExists(String... classNames) {
+        classNames.each {
+            assertTrue new File(htmlReportDirectory, "${it}.html").exists();
+        }
+    }
+
+    TestClassExecutionResult testClass(String testClass) {
+        return new HtmlTestClassExecutionResult(new File(htmlReportDirectory, "${testClass}.html"));
+    }
+
+    private static class HtmlTestClassExecutionResult implements TestClassExecutionResult {
+        private File htmlFile
+        private List testsExecuted = []
+        private List testsSucceeded = []
+        private Map testsFailures = [:]
+        private Set testsSkipped = []
+        private Document html
+
+        public HtmlTestClassExecutionResult(File htmlFile) {
+            this.htmlFile = htmlFile;
+            this.html = Jsoup.parse(htmlFile, null)
+            parseTestClassFile()
+        }
+
+        def parseTestClassFile() {
+            html.select("tr > td.success:eq(0)").each {
+                testsExecuted << it.text()
+                testsSucceeded << it.text()
+
+            }
+            html.select("tr > td.failures:eq(0)").each {
+                testsExecuted << it.text()
+                String failure = getFailureMessage(it.text());
+                testsFailures[it.text()] = failure
+            }
+
+            html.select("tr > td.skipped:eq(0)").each {
+                testsSkipped << it.text()
+                testsExecuted << it.text()
+            }
+            return this
+        }
+
+        String getFailureMessage(String testmethod) {
+            html.select("div#test:has(a[name=$testmethod]) > span > pre").text()
+        }
+
+        TestClassExecutionResult assertTestsExecuted(String... testNames) {
+            assertThat(testsExecuted - testsSkipped, equalTo(testNames as List))
+            return this
+        }
+
+        TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
+            assert tests == testsExecuted.size()
+            assert failures == testsFailures.size()
+            return this
+        }
+
+        TestClassExecutionResult assertTestsSkipped(String... testNames) {
+            assertThat(testsSkipped, equalTo(testNames as Set))
+            return this
+        }
+
+        TestClassExecutionResult assertTestPassed(String name) {
+            assert testsSucceeded.contains(name);
+            return this
+        }
+
+        TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
+            def message = testsFailures[name];
+            messageMatchers.each { it.matches(message) }
+            return this
+        }
+
+        TestClassExecutionResult assertTestSkipped(String name) {
+            assert testsSkipped.contains(name);
+            return this
+        }
+
+        TestClassExecutionResult assertConfigMethodPassed(String name) {
+            return null
+        }
+
+        TestClassExecutionResult assertConfigMethodFailed(String name) {
+            return null
+        }
+
+        TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+            matcher.matches(html.select("div#tab2 > span > pre").text())
+            return this;
+        }
+
+        TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+            matcher.matches(html.select("div#tab3 > span > pre").text())
+            return this;
+        }
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitXmlTestExecutionResult.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitXmlTestExecutionResult.groovy
new file mode 100644
index 0000000..48ee3e1
--- /dev/null
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/integtests/fixtures/JUnitXmlTestExecutionResult.groovy
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.integtests.fixtures
+
+import groovy.util.slurpersupport.GPathResult
+import org.gradle.test.fixtures.file.TestFile
+import org.hamcrest.Matcher
+
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+
+class JUnitXmlTestExecutionResult implements TestExecutionResult {
+    private final TestFile buildDir
+
+    def JUnitXmlTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
+        this.buildDir = projectDir.file(buildDirName)
+    }
+
+    boolean hasJUnitXmlResults() {
+        xmlResultsDir().list().length > 0
+    }
+
+    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
+        Map<String, File> classes = findClasses()
+        assertThat(classes.keySet(), equalTo(testClasses as Set));
+        this
+    }
+
+    TestClassExecutionResult testClass(String testClass) {
+        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass)
+    }
+
+    private def findTestClass(String testClass) {
+        def classes = findClasses()
+        assertThat(classes.keySet(), hasItem(testClass))
+        def classFile = classes.get(testClass)
+        assertThat(classFile, notNullValue())
+        return new XmlSlurper().parse(classFile)
+    }
+
+    private def findClasses() {
+        xmlResultsDir().assertIsDir()
+
+        Map<String, File> classes = [:]
+        buildDir.file('test-results').eachFile { File file ->
+            def matcher = (file.name =~ /TEST-(.+)\.xml/)
+            if (matcher.matches()) {
+                classes[matcher.group(1)] = file
+            }
+        }
+        return classes
+    }
+
+    private TestFile xmlResultsDir() {
+        buildDir.file('test-results')
+    }
+}
+
+class JUnitTestClassExecutionResult implements TestClassExecutionResult {
+    GPathResult testClassNode
+    String testClassName
+    boolean checked
+
+    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName) {
+        this.testClassNode = testClassNode
+        this.testClassName = testClassName
+    }
+
+    def JUnitTestClassExecutionResult(String content, String testClassName) {
+        this(new XmlSlurper().parse(new StringReader(content)), testClassName)
+    }
+
+    TestClassExecutionResult assertTestsExecuted(String... testNames) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertTestCount(int tests, int failures, int errors) {
+        assert testClassNode. at tests == tests
+        assert testClassNode. at failures == failures
+        assert testClassNode. at errors == errors
+        this
+    }
+
+    TestClassExecutionResult assertTestPassed(String name) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), hasItem(name))
+        assertThat(testMethods[name].failure.size(), equalTo(0))
+        this
+    }
+
+    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String>... messageMatchers) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), hasItem(name))
+
+        def failures = testMethods[name].failure
+        assertThat("Expected ${messageMatchers.length} failures. Found: $failures", failures.size(), equalTo(messageMatchers.length))
+
+        for (int i = 0; i < messageMatchers.length; i++) {
+            assertThat(failures[i]. at message.text(), messageMatchers[i])
+        }
+        this
+    }
+
+    TestClassExecutionResult assertTestSkipped(String name) {
+        throw new UnsupportedOperationException()
+    }
+
+    TestClassExecutionResult assertTestsSkipped(String... testNames) {
+        Map<String, Node> testMethods = findIgnoredTests()
+        assertThat(testMethods.keySet(), equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertConfigMethodPassed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertConfigMethodFailed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+        def stdout = testClassNode.'system-out'[0].text();
+        assertThat(stdout, matcher)
+        this
+    }
+
+    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+        def stderr = testClassNode.'system-err'[0].text();
+        assertThat(stderr, matcher)
+        this
+    }
+
+    private def findTests() {
+        if (!checked) {
+            assertThat(testClassNode.name(), equalTo('testsuite'))
+            assertThat(testClassNode. at name.text(), equalTo(testClassName))
+            assertThat(testClassNode. at tests.text(), not(equalTo('')))
+            assertThat(testClassNode. at failures.text(), not(equalTo('')))
+            assertThat(testClassNode. at errors.text(), not(equalTo('')))
+            assertThat(testClassNode. at time.text(), not(equalTo('')))
+            assertThat(testClassNode. at timestamp.text(), not(equalTo('')))
+            assertThat(testClassNode. at hostname.text(), not(equalTo('')))
+            assertThat(testClassNode.properties.size(), equalTo(1))
+            testClassNode.testcase.each { node ->
+                assertThat(node. at classname.text(), equalTo(testClassName))
+                assertThat(node. at name.text(), not(equalTo('')))
+                assertThat(node. at time.text(), not(equalTo('')))
+                node.failure.each { failure ->
+                    assertThat(failure. at message.size(), equalTo(1))
+                    assertThat(failure. at type.text(), not(equalTo('')))
+                    assertThat(failure.text(), not(equalTo('')))
+                }
+            }
+            assertThat(testClassNode.'system-out'.size(), equalTo(1))
+            assertThat(testClassNode.'system-err'.size(), equalTo(1))
+            checked = true
+        }
+        Map testMethods = [:]
+        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
+        return testMethods
+    }
+
+    private def findIgnoredTests() {
+        Map testMethods = [:]
+        testClassNode."ignored-testcase".each { testMethods[it. at name.text()] = it }
+        return testMethods
+    }
+}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProviderFinder.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProviderFinder.groovy
deleted file mode 100644
index 204b76c..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/test/fixtures/file/TestDirectoryProviderFinder.groovy
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2009 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.test.fixtures.file
-
-import org.gradle.util.RuleHelper
-
-class TestDirectoryProviderFinder {
-
-    @SuppressWarnings("GrMethodMayBeStatic")
-    TestDirectoryProvider findFor(Object test) {
-        if (test instanceof TestDirectoryProvider) {
-            test
-        } else {
-            TestDirectoryProvider testDirectoryProvider = RuleHelper.findField(test, TestDirectoryProvider)
-            if (testDirectoryProvider == null) {
-                throw new IllegalStateException("Test ${test.class.name} does not implement TestDirectoryProvider and has no field of that type")
-            }
-            testDirectoryProvider
-        }
-    }
-
-}
diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/RuleHelper.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/RuleHelper.java
deleted file mode 100644
index 607e40d..0000000
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/RuleHelper.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2010 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.util;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.List;
-
-public class RuleHelper {
-    public static <T> T getField(Object target, Class<T> type) {
-        T value = findField(target, type);
-        if (value != null) {
-            return value;
-        }
-        throw new RuntimeException(String.format("Cannot find a field of type %s for test class %s.",
-                type.getSimpleName(), target.getClass().getSimpleName()));
-    }
-
-    public static <T> T findField(Object target, Class<T> type) {
-        List<T> matches = new ArrayList<T>();
-        for (Class<?> cl = target.getClass(); cl != Object.class; cl = cl.getSuperclass()) {
-            for (Field field : cl.getDeclaredFields()) {
-                if (!Modifier.isStatic(field.getModifiers()) && type.isAssignableFrom(field.getType())) {
-                    field.setAccessible(true);
-                    try {
-                        matches.add(type.cast(field.get(target)));
-                    } catch (IllegalAccessException e) {
-                        throw new RuntimeException(e);
-                    }
-                }
-            }
-        }
-        if (matches.isEmpty()) {
-            return null;
-        }
-        if (matches.size() > 1) {
-            throw new RuntimeException(String.format("Multiple %s fields found for test class %s.",
-                    type.getSimpleName(), target.getClass().getSimpleName()));
-        }
-        return matches.get(0);
-    }
-}
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 ea278b2..56fbc3a 100755
--- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
+++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy
@@ -93,6 +93,10 @@ enum TestPrecondition {
     NOT_JDK7({
         !JDK7.fulfilled
     }),
+    CAN_RESOLVE_UNICODE_POM({
+        // Ivy parsing Maven POM files with unicode characters is broken in JDK1.6 on Linux (& Unknown OS)
+        !(JDK6.fulfilled && (LINUX.fulfilled || UNKNOWN_OS.fulfilled))
+    }),
     JDK7_POSIX({
         JDK7.fulfilled && NOT_WINDOWS.fulfilled
     });
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/AutoTestedSamplesIvyIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/AutoTestedSamplesIvyIntegrationTest.groovy
new file mode 100644
index 0000000..3e19465
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/AutoTestedSamplesIvyIntegrationTest.groovy
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api
+
+import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
+import org.junit.Test
+
+class AutoTestedSamplesIvyIntegrationTest extends AbstractAutoTestedSamplesTest {
+
+    @Test
+    void runSamples() {
+        runSamplesFrom("subprojects/ivy/src/main")
+    }
+
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AbstractIvyPublishIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AbstractIvyPublishIntegTest.groovy
new file mode 100644
index 0000000..b55f53b
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AbstractIvyPublishIntegTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.ivy.IvyFileModule
+
+class AbstractIvyPublishIntegTest extends AbstractIntegrationSpec {
+
+    protected def resolveArtifacts(IvyFileModule module) {
+        doResolveArtifacts("group: '${sq(module.organisation)}', name: '${sq(module.module)}', version: '${sq(module.revision)}'")
+    }
+
+    protected def resolveArtifacts(IvyFileModule module, def configuration) {
+        doResolveArtifacts("group: '${sq(module.organisation)}', name: '${sq(module.module)}', version: '${sq(module.revision)}', configuration: '${sq(configuration)}'")
+    }
+
+    private def doResolveArtifacts(def dependency) {
+        // Replace the existing buildfile with one for resolving the published module
+        // TODO:DAZ Use a separate directory for resolving
+        settingsFile.text = "rootProject.name = 'resolve'"
+        buildFile.text = """
+            configurations {
+                resolve
+            }
+            repositories {
+                ivy { url "${ivyRepo.uri}" }
+                mavenCentral()
+            }
+            dependencies {
+                resolve $dependency
+            }
+
+            task resolveArtifacts(type: Sync) {
+                from configurations.resolve
+                into "artifacts"
+            }
+
+"""
+
+        run "resolveArtifacts"
+        def artifactsList = file("artifacts").exists() ? file("artifacts").list() : []
+        return artifactsList.sort()
+    }
+
+    String sq(String input) {
+        return escapeForSingleQuoting(input)
+    }
+
+    String escapeForSingleQuoting(String input) {
+        return input.replace('\\', '\\\\').replace('\'', '\\\'')
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AutoTestedSamplesIvyIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AutoTestedSamplesIvyIntegrationTest.groovy
deleted file mode 100644
index 3483d6f..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/AutoTestedSamplesIvyIntegrationTest.groovy
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractAutoTestedSamplesTest
-import org.junit.Test
-
-class AutoTestedSamplesIvyIntegrationTest extends AbstractAutoTestedSamplesTest {
-
-    @Test
-    void runSamples() {
-        runSamplesFrom("subprojects/ivy/src/main")
-    }
-
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy
deleted file mode 100644
index 9e2244d..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyCustomPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.test.fixtures.file.TestFile
-
-class IvyCustomPublishIntegrationTest extends AbstractIntegrationSpec {
-
-    org.gradle.test.fixtures.ivy.IvyFileModule module
-    TestFile artifact
-
-    def setup() {
-        module = ivyRepo.module("org.gradle", "publish", "2")
-        artifact = file("artifact.txt").createFile()
-        settingsFile << 'rootProject.name = "publish"'
-    }
-
-    public void "can publish custom configurations with java plugin"() {
-        given:
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            configurations { custom }
-            artifacts {
-                custom file("${artifact.name}"), {
-                    name "foo"
-                    extension "txt"
-                }
-            }
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "${ivyRepo.uri}"
-                    }
-                }
-            }
-        """
-
-        when:
-        succeeds 'publish'
-
-        then:
-        module.ivyFile.assertIsFile()
-        module.assertArtifactsPublished("ivy-2.xml", "foo-2.txt", "publish-2.jar")
-        with(module.ivy.artifacts.foo) {
-            name == "foo"
-            ext == "txt"
-            "custom" in conf
-        }
-    }
-
-    public void "can publish custom configurations"() {
-        given:
-        buildFile << """
-            apply plugin: 'base'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            configurations { custom }
-            artifacts {
-                custom file("${artifact.name}"), {
-                    name "foo"
-                    extension "txt"
-                }
-            }
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "${ivyRepo.uri}"
-                    }
-                }
-            }
-        """
-
-        when:
-        succeeds 'publish'
-
-        then:
-        module.ivyFile.assertIsFile()
-        module.assertArtifactsPublished("ivy-2.xml", "foo-2.txt")
-        with(module.ivy.artifacts.foo) {
-            name == "foo"
-            ext == "txt"
-            "custom" in conf
-        }
-    }
-
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
deleted file mode 100644
index cf935d8..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyEarProjectPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-class IvyEarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
-    public void "publishes EAR only for mixed java and WAR and EAR project"() {
-        given:
-        file("settings.gradle") << "rootProject.name = 'publishTest' "
-
-        and:
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'war'
-            apply plugin: 'ear'
-            apply plugin: 'ivy-publish'
-
-            group = 'org.gradle.test'
-            version = '1.9'
-
-            repositories {
-                mavenCentral()
-            }
-
-            dependencies {
-                compile "commons-collections:commons-collections:3.2.1"
-                runtime "commons-io:commons-io:1.4"
-            }
-
-            publishing {
-                repositories {
-                    ivy {
-                        url '${ivyRepo.uri}'
-                    }
-                }
-            }
-        """
-
-        when:
-        run "publish"
-
-        then:
-        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
-        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.ear", "publishTest-1.9.jar")
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy
deleted file mode 100644
index 8733309..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyHttpPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,349 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
-import org.gradle.test.fixtures.file.TestFile
-import org.gradle.test.fixtures.ivy.IvyFileModule
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.gradle.util.GradleVersion
-import org.gradle.util.Jvm
-import org.gradle.util.TextUtil
-import org.hamcrest.Matchers
-import org.junit.Rule
-import org.mortbay.jetty.HttpStatus
-import spock.lang.Unroll
-
-import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
-
-public class IvyHttpPublishIntegrationTest extends AbstractIntegrationSpec {
-    private static final String BAD_CREDENTIALS = '''
-credentials {
-    username 'testuser'
-    password 'bad'
-}
-'''
-    @Rule
-    public final HttpServer server = new HttpServer()
-
-    @Rule ProgressLoggingFixture progressLogging
-
-    private IvyFileModule module
-
-    def setup() {
-        module = ivyRepo.module("org.gradle", "publish", "2")
-        module.moduleDir.mkdirs()
-        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
-    }
-
-    public void canPublishToUnauthenticatedHttpRepository() {
-        given:
-        server.start()
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "http://localhost:${server.port}"
-                    }
-                }
-            }
-        """
-
-        when:
-        expectUpload('/org.gradle/publish/2/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
-        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, HttpStatus.ORDINAL_201_Created)
-
-        and:
-        succeeds 'publish'
-
-        then:
-        module.ivyFile.assertIsFile()
-        module.assertChecksumPublishedFor(module.ivyFile)
-
-        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
-        module.assertChecksumPublishedFor(module.jarFile)
-        and:
-        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/ivy-2.xml")
-        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/publish-2.jar")
-    }
-
-
-    @Unroll
-    def "can publish to authenticated repository using #authScheme auth"() {
-        given:
-        server.start()
-
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            publishing {
-                repositories {
-                    ivy {
-                        credentials {
-                            username 'testuser'
-                            password 'password'
-                        }
-                        url "http://localhost:${server.port}"
-                    }
-                }
-            }
-        """
-
-        when:
-        server.authenticationScheme = authScheme
-        expectUpload('/org.gradle/publish/2/publish-2.jar', module, module.jarFile, 'testuser', 'password')
-        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, 'testuser', 'password')
-
-        then:
-        succeeds 'publish'
-
-        and:
-        module.ivyFile.assertIsFile()
-        module.assertChecksumPublishedFor(module.ivyFile)
-        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
-        module.assertChecksumPublishedFor(module.jarFile)
-
-        and:
-        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/ivy-2.xml")
-        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/publish-2.jar")
-
-        where:
-        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
-    }
-
-    @Unroll
-    def "reports failure publishing with #credsName credentials to authenticated repository using #authScheme auth"() {
-        given:
-        server.start()
-
-        when:
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-            version = '2'
-            group = 'org.gradle'
-            publishing {
-                repositories {
-                    ivy {
-                        $creds
-                        url "http://localhost:${server.port}"
-                    }
-                }
-            }
-        """
-
-        and:
-        server.authenticationScheme = authScheme
-        server.allowPut('/org.gradle/publish/2/publish-2.jar', 'testuser', 'password')
-
-        then:
-        fails 'publish'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
-        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
-        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
-
-        where:
-        authScheme                   | credsName | creds
-        HttpServer.AuthScheme.BASIC  | 'empty'   | ''
-        HttpServer.AuthScheme.DIGEST | 'empty'   | ''
-        HttpServer.AuthScheme.BASIC  | 'bad'     | BAD_CREDENTIALS
-        HttpServer.AuthScheme.DIGEST | 'bad'     | BAD_CREDENTIALS
-    }
-
-    public void reportsFailedPublishToHttpRepository() {
-        given:
-        server.start()
-        def repositoryUrl = "http://localhost:${server.port}"
-
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "${repositoryUrl}"
-                    }
-                }
-            }
-        """
-
-        when:
-        server.addBroken("/")
-
-        then:
-        fails 'publish'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
-        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
-        failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
-
-        when:
-        server.stop()
-
-        then:
-        fails 'publish'
-
-        and:
-        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
-        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
-        failure.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
-    }
-
-    public void usesFirstConfiguredPatternForPublication() {
-        given:
-        server.start()
-
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-            publishing {
-                repositories {
-                    ivy {
-                        artifactPattern "http://localhost:${server.port}/primary/[module]/[artifact]-[revision].[ext]"
-                        artifactPattern "http://localhost:${server.port}/alternative/[module]/[artifact]-[revision].[ext]"
-                        ivyPattern "http://localhost:${server.port}/primary-ivy/[module]/ivy-[revision].xml"
-                        ivyPattern "http://localhost:${server.port}/secondary-ivy/[module]/ivy-[revision].xml"
-                    }
-                }
-            }
-        """
-
-        when:
-        expectUpload('/primary/publish/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
-        expectUpload('/primary-ivy/publish/ivy-2.xml', module, module.ivyFile)
-
-        then:
-        succeeds 'publish'
-
-        and:
-        module.ivyFile.assertIsFile()
-        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
-    }
-
-    public void "can publish large artifact (tools.jar) to authenticated repository"() {
-        given:
-        server.start()
-        def toolsJar = TextUtil.escapeString(Jvm.current().toolsJar)
-
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'base'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            configurations {
-                archives
-            }
-
-            artifacts {
-                archives(file('$toolsJar')) {
-                    name 'tools'
-                }
-            }
-
-            publishing {
-                repositories {
-                    ivy {
-                        credentials {
-                            username 'testuser'
-                            password 'password'
-                        }
-                        url "http://localhost:${server.port}"
-                    }
-                }
-            }
-        """
-
-        when:
-        def uploadedToolsJar = module.moduleDir.file('toolsJar')
-        expectUpload('/org.gradle/publish/2/tools-2.jar', module, uploadedToolsJar, 'testuser', 'password')
-        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, 'testuser', 'password')
-
-        then:
-        succeeds 'publish'
-
-        and:
-        module.ivyFile.assertIsFile()
-        uploadedToolsJar.assertIsCopyOf(new TestFile(toolsJar));
-
-    }
-
-    public void "does not upload meta-data file if artifact upload fails"() {
-        given:
-        server.start()
-
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "http://localhost:${server.port}"
-                    }
-                }
-            }
-        """
-        when:
-        server.expectPut("/org.gradle/publish/2/publish-2.jar", module.jarFile, HttpStatus.ORDINAL_500_Internal_Server_Error)
-
-        then:
-        fails ':publish'
-
-        and:
-        module.jarFile.assertExists()
-        module.ivyFile.assertDoesNotExist()
-    }
-
-    private void expectUpload(String path, IvyFileModule module, TestFile file, int statusCode = HttpStatus.ORDINAL_200_OK) {
-        server.expectPut(path, file, statusCode)
-        server.expectPut("${path}.sha1", module.sha1File(file), statusCode)
-    }
-
-    private void expectUpload(String path, IvyFileModule module, TestFile file, String username, String password) {
-        server.expectPut(path, username, password, file)
-        server.expectPut("${path}.sha1", username, password, module.sha1File(file))
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
deleted file mode 100644
index da10db4..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyJavaProjectPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-class IvyJavaProjectPublishIntegrationTest extends AbstractIntegrationSpec {
-    public void "can publish jar and meta-data to ivy repository"() {
-        given:
-        file("settings.gradle") << "rootProject.name = 'publishTest' "
-
-        and:
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            group = 'org.gradle.test'
-            version = '1.9'
-
-            repositories {
-                mavenCentral()
-            }
-
-            dependencies {
-                compile "commons-collections:commons-collections:3.2.1"
-                runtime "commons-io:commons-io:1.4"
-            }
-
-            publishing {
-                repositories {
-                    ivy {
-                        url '${ivyRepo.uri}'
-                    }
-                }
-            }
-        """
-
-        when:
-        run "publish"
-
-        then:
-        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
-        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.jar")
-        ivyModule.ivy.dependencies.compile.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
-        ivyModule.ivy.dependencies.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy
deleted file mode 100644
index a0f25cb..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyLocalPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.spockframework.util.TextUtil
-import spock.lang.Issue
-import org.gradle.test.fixtures.ivy.IvyDescriptor
-
-public class IvyLocalPublishIntegrationTest extends AbstractIntegrationSpec {
-    public void canPublishToLocalFileRepository() {
-        given:
-        def module = ivyRepo.module("org.gradle", "publish", "2")
-
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "${ivyRepo.uri}"
-                    }
-                }
-            }
-        """
-
-        when:
-        succeeds 'publish'
-
-        then:
-        module.ivyFile.assertIsFile()
-        module.assertChecksumPublishedFor(module.ivyFile)
-
-        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
-        module.assertChecksumPublishedFor(module.jarFile)
-    }
-
-    @Issue("GRADLE-2456")
-    public void generatesSHA1FileWithLeadingZeros() {
-        given:
-        def module = ivyRepo.module("org.gradle", "publish", "2")
-        byte[] jarBytes = [0, 0, 0, 5]
-        def artifactFile = file("testfile.bin")
-        artifactFile << jarBytes
-        def artifactPath = TextUtil.escape(artifactFile.path)
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            group = "org.gradle"
-            version = '2'
-
-            artifacts {
-                archives file: file("${artifactPath}"), name: 'testfile', type: 'bin'
-            }
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "${ivyRepo.uri}"
-                    }
-                }
-            }
-        """
-        when:
-        succeeds 'publish'
-
-        then:
-        def shaOneFile = module.moduleDir.file("testfile-2.bin.sha1")
-        shaOneFile.exists()
-        shaOneFile.text == "00e14c6ef59816760e2c9b5a57157e8ac9de4012"
-    }
-
-    @Issue("GRADLE-1811")
-    public void canGenerateTheIvyXmlWithoutPublishing() {
-        given:
-        def module = ivyRepo.module("org.gradle", "generateIvy", "2")
-
-        settingsFile << "rootProject.name = 'generateIvy'"
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "${ivyRepo.uri}"
-                    }
-                }
-            }
-
-            generateIvyModuleDescriptor {
-                destination = 'generated-ivy.xml'
-            }
-        """
-
-        when:
-        succeeds 'generateIvyModuleDescriptor'
-
-        then:
-        file('generated-ivy.xml').assertIsFile()
-        IvyDescriptor ivy = new IvyDescriptor(file('generated-ivy.xml'))
-        with (ivy.artifacts['generateIvy']) {
-            name == 'generateIvy'
-            ext == 'jar'
-            conf == ['archives', 'runtime']
-        }
-
-        and:
-        module.ivyFile.assertDoesNotExist()
-    }
-
-    def "can publish with non-ascii characters"() {
-        def organisation = 'group-√æず'
-        def moduleName = 'artifact-∫ʙぴ'
-        def version = 'version-₦ガき∆'
-        def description = 'description-ç√∫'
-
-        given:
-        settingsFile << "rootProject.name = '${moduleName}'"
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            group = '${organisation}'
-            version = '${version}'
-
-            publishing {
-                repositories {
-                    ivy { url "${ivyRepo.uri}" }
-                }
-                publications {
-                    ivy {
-                        descriptor.withXml {
-                            asNode().info[0].appendNode('description', "${description}")
-                        }
-                    }
-                }
-            }
-        """
-        when:
-        succeeds 'publish'
-
-        then:
-        def ivy = ivyRepo.module(organisation, moduleName, version).ivy
-        ivy.organisation == organisation
-        ivy.module == moduleName
-        ivy.revision == version
-        ivy.description == description
-    }
-
-
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomisationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomisationIntegTest.groovy
new file mode 100644
index 0000000..f1b645c
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishArtifactCustomisationIntegTest.groovy
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.test.fixtures.ivy.IvyDescriptorArtifact
+
+class IvyPublishArtifactCustomisationIntegTest extends AbstractIvyPublishIntegTest {
+
+    def module = ivyRepo.module("org.gradle.test", "ivyPublish", "2.4")
+
+    public void "can publish custom artifacts"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    artifact "customFile.txt"
+                    artifact customDocsTask.outputFile
+                    artifact customJar
+                }
+            }
+""", """
+        publishing {
+            publishIvyPublicationToIvyRepository.dependsOn(customDocsTask)
+        }
+""")
+
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4.txt", "ivyPublish-2.4.html", "ivyPublish-2.4.jar")
+
+        and:
+        def ivy = module.ivy
+        ivy.expectArtifact('ivyPublish', 'txt').hasType("txt").hasConf(null)
+        ivy.expectArtifact('ivyPublish', 'html').hasType("html").hasConf(null)
+        ivy.expectArtifact('ivyPublish', 'jar').hasType("jar").hasConf(null)
+
+        and:
+        resolveArtifacts(module) == ["ivyPublish-2.4.html", "ivyPublish-2.4.jar", "ivyPublish-2.4.txt"]
+    }
+
+    def "can configure custom artifacts when creating"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    configurations {
+                        foo
+                        bar
+                        "default" {
+                            extend "foo"
+                        }
+                    }
+                    artifact("customFile.txt") {
+                        name "customFile"
+                        classifier "classified"
+                        conf "foo,bar"
+                    }
+                    artifact(customDocsTask.outputFile) {
+                        name "docs"
+                        extension "htm"
+                        builtBy customDocsTask
+                    }
+                    artifact(customJar) {
+                        extension "war"
+                        type "web-archive"
+                        conf "*"
+                    }
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "docs-2.4.htm", "customFile-2.4-classified.txt", "ivyPublish-2.4.war")
+
+        and:
+        def ivy = module.ivy
+        ivy.expectArtifact("ivyPublish", "war").hasType("web-archive").hasConf(["*"])
+        ivy.expectArtifact("docs", "htm").hasType("html").hasConf(null)
+        ivy.expectArtifact("customFile", "txt", "classified").hasType("txt").hasConf(["foo", "bar"])
+
+        and:
+        resolveArtifacts(module) == ["customFile-2.4-classified.txt", "docs-2.4.htm", "ivyPublish-2.4.war"]
+    }
+
+    def "can publish custom file artifacts with map notation"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    configurations {
+                        foo
+                        bar
+                        "default" {
+                            extend "foo"
+                        }
+                    }
+                    artifact source: "customFile.txt", name: "customFile", classifier: "classified", conf: "foo,bar"
+                    artifact source: customDocsTask.outputFile, name: "docs", extension: "htm", builtBy: customDocsTask
+                    artifact source: customJar, extension: "war", type: "web-archive", conf: "*"
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "docs-2.4.htm", "customFile-2.4-classified.txt", "ivyPublish-2.4.war")
+
+        and:
+        def ivy = module.ivy
+        ivy.expectArtifact("ivyPublish", "war").hasType("web-archive").hasConf(["*"])
+        ivy.expectArtifact("docs", "htm").hasType("html").hasConf(null)
+        ivy.expectArtifact("customFile", "txt", "classified").hasType("txt").hasConf(["foo", "bar"])
+
+        and:
+        resolveArtifacts(module) == ["customFile-2.4-classified.txt", "docs-2.4.htm", "ivyPublish-2.4.war"]
+    }
+
+    def "can set custom artifacts to override component artifacts"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    from components.java
+                    artifacts = ["customFile.txt", customDocsTask.outputFile, customJar]
+                }
+            }
+""", """
+            publishing {
+                publishIvyPublicationToIvyRepository.dependsOn(customDocsTask)
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4.txt", "ivyPublish-2.4.html", "ivyPublish-2.4.jar")
+        module.ivy.artifacts.collect({"${it.name}.${it.ext}"}) as Set == ["ivyPublish.txt", "ivyPublish.html", "ivyPublish.jar"] as Set
+    }
+
+    def "can configure custom artifacts post creation"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    configurations {
+                        mod_conf {}
+                        other {}
+                    }
+                    artifact source: "customFile.txt", name: "customFile"
+                    artifact source: customDocsTask.outputFile, name: "docs", builtBy: customDocsTask
+                    artifact source: customJar
+                }
+            }
+""", """
+            publishing.publications.ivy.artifacts.each {
+                it.extension = "mod"
+                it.conf = "mod_conf"
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "customFile-2.4.mod", "docs-2.4.mod", "ivyPublish-2.4.mod")
+
+        for (IvyDescriptorArtifact artifact : module.ivy.artifacts) {
+            artifact.ext == "mod"
+            artifact.conf == "mod-conf"
+        }
+    }
+
+    def "can publish artifact with no extension"() {
+        given:
+        file("no-extension") << "some content"
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    artifact source: 'no-extension', name: 'no-extension', type: 'ext-less'
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "no-extension-2.4")
+        module.ivy.expectArtifact("no-extension").hasAttributes("", "ext-less", null)
+
+        // TODO:DAZ Fix publication with empty extension so it can be resolved
+//        and:
+//        resolveArtifacts(module) == ["no-extension-2.4"]
+    }
+
+    def "can publish artifact with classifier"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    artifact source: customJar, classifier: "classy"
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-2.4.xml", "ivyPublish-2.4-classy.jar")
+        module.ivy.expectArtifact("ivyPublish").hasAttributes("jar", "jar", null, "classy")
+
+        and:
+        resolveArtifacts(module) == ["ivyPublish-2.4-classy.jar"]
+    }
+
+    def "can add custom configurations"() {
+        given:
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    configurations {
+                        runtime
+                        base {}
+                        custom {
+                            extend "runtime"
+                            extend "base"
+                        }
+                    }
+                }
+            }
+""")
+
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        def ivy = module.ivy
+        ivy.configurations.keySet() == ["base", "custom", "runtime"] as Set
+        ivy.configurations["runtime"].extend == null
+        ivy.configurations["base"].extend == null
+        ivy.configurations["custom"].extend == ["runtime", "base"] as Set
+    }
+
+    def "reports failure publishing when validation fails"() {
+        given:
+        file("a-directory.dir").createDir()
+
+        createBuildScripts("""
+            publications {
+                ivy(IvyPublication) {
+                    artifact "a-directory.dir"
+                }
+            }
+""")
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishIvyPublicationToIvyRepository'.")
+        failure.assertHasCause("Failed to publish publication 'ivy' to repository 'ivy'")
+        failure.assertHasCause("Invalid publication 'ivy': artifact file is a directory")
+    }
+
+    private createBuildScripts(def publications, def append = "") {
+        file("customFile.txt") << "some content"
+        settingsFile << "rootProject.name = 'ivyPublish'"
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            group = 'org.gradle.test'
+            version = '2.4'
+
+            task customDocsTask {
+                ext.outputFile = file('customDocs.html')
+                doLast {
+                    outputFile << '<html/>'
+                }
+            }
+
+            task customJar(type: Jar) {
+                from file("customFile.txt")
+                baseName "customJar"
+            }
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                $publications
+            }
+
+            $append
+        """
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishBasicIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishBasicIntegTest.groovy
new file mode 100644
index 0000000..dee5167
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishBasicIntegTest.groovy
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api.publish.ivy
+
+public class IvyPublishBasicIntegTest extends AbstractIvyPublishIntegTest {
+
+    def "publishes nothing without defined publication"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'ivy-publish'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        ivyRepo.module('group', 'root', '1.0').assertNotPublished()
+    }
+
+    def "publishes empty module when publication has no added component"() {
+        given:
+        settingsFile << "rootProject.name = 'empty-project'"
+        buildFile << """
+            apply plugin: 'ivy-publish'
+
+            group = 'org.gradle.test'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication)
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = ivyRepo.module('org.gradle.test', 'empty-project', '1.0')
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-1.0.xml")
+
+        and:
+        with (module.ivy) {
+            configurations.isEmpty()
+            artifacts.isEmpty()
+            dependencies.isEmpty()
+            status == "release"
+        }
+
+        and:
+        resolveArtifacts(module) == []
+    }
+
+    def "can publish simple jar"() {
+        given:
+        def module = ivyRepo.module('group', 'root', '1.0')
+
+        and:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'ivy-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+            status = 'integration'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds 'assemble'
+
+        then: "jar is built but not published"
+        module.assertNotPublished()
+        file('build/libs/root-1.0.jar').assertExists()
+
+        when:
+        succeeds 'publish'
+
+        then: "jar is published to defined ivy repository"
+        module.assertPublishedAsJavaModule()
+        module.ivy.status == 'integration'
+        module.moduleDir.file('root-1.0.jar').assertIsCopyOf(file('build/libs/root-1.0.jar'))
+
+        and:
+        resolveArtifacts(module) == ['root-1.0.jar']
+    }
+
+    def "reports failure publishing when model validation fails"() {
+        given:
+        settingsFile << "rootProject.name = 'bad-project'"
+        buildFile << """
+            apply plugin: 'ivy-publish'
+            apply plugin: 'war'
+
+            group = 'org.gradle.test'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                        from components.web
+                    }
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("A problem occurred configuring the 'publishing' extension")
+        failure.assertHasCause("Ivy publication 'ivy' cannot include multiple components")
+    }
+
+
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCrossVersionIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCrossVersionIntegrationTest.groovy
new file mode 100644
index 0000000..7ae9c64
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishCrossVersionIntegrationTest.groovy
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.ivy
+import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetVersions
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.ivy.IvyFileRepository
+import org.gradle.util.TextUtil
+
+ at TargetVersions('0.9+')
+class IvyPublishCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
+
+    final TestFile repoDir = file("ivy-repo")
+    final IvyFileRepository repo = new IvyFileRepository(repoDir)
+
+    def "ivy java publication generated by ivy-publish plugin can be consumed by previous versions of Gradle"() {
+        given:
+        projectPublishedUsingMavenPublishPlugin('java')
+
+        expect:
+        consumePublicationWithPreviousVersion('')
+
+        file('build/resolved').assertHasDescendants("published-${publishedVersion}.jar", 'commons-collections-3.0.jar')
+    }
+
+    def "ivy war publication generated by ivy-publish plugin can be consumed by previous versions of Gradle"() {
+        given:
+        projectPublishedUsingMavenPublishPlugin('web')
+
+        expect:
+        consumePublicationWithPreviousVersion('@war')
+
+        file('build/resolved').assertHasDescendants("published-${publishedVersion}.war")
+    }
+
+    def projectPublishedUsingMavenPublishPlugin(def componentToPublish) {
+        settingsFile.text = "rootProject.name = 'published'"
+
+        buildFile.text = """
+apply plugin: 'war'
+apply plugin: 'ivy-publish'
+
+group = 'org.gradle.crossversion'
+version = '${publishedVersion}'
+
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile "commons-collections:commons-collections:3.0"
+}
+publishing {
+    repositories {
+        ivy { url "${repo.uri}" }
+    }
+    publications {
+        ivy(IvyPublication) {
+            from components['${componentToPublish}']
+        }
+    }
+}
+"""
+
+        version current withTasks 'publish' run()
+    }
+
+    def consumePublicationWithPreviousVersion(def artifact) {
+        settingsFile.text = "rootProject.name = 'consumer'"
+
+        def repositoryDefinition
+        if (previous.fullySupportsIvyRepository) {
+            repositoryDefinition = """
+                ivy {
+                    url "${repo.uri}"
+                }
+"""
+        } else {
+            def repoPath = TextUtil.normaliseFileSeparators(repoDir.absolutePath)
+            repositoryDefinition = """
+                println "Adding resolver directly due to no 'ivy' repository support"
+                add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+                    name = 'repo'
+                    addIvyPattern("${repoPath}/[organisation]/[module]/[revision]/ivy-[revision].xml")
+                    addArtifactPattern("${repoPath}/[organisation]/[module]/[revision]/[artifact]-[revision].[ext]")
+                    descriptor = 'required'
+                    checkmodified = true
+                }
+"""
+        }
+
+        buildFile.text = """
+configurations {
+    lib
+}
+repositories {
+    mavenCentral()
+
+    $repositoryDefinition
+}
+dependencies {
+    lib 'org.gradle.crossversion:published:${publishedVersion}${artifact}'
+}
+task retrieve(type: Sync) {
+    into 'build/resolved'
+    from configurations.lib
+}
+"""
+
+        version previous withDeprecationChecksDisabled() withTasks 'retrieve' run()
+    }
+
+    def getPublishedVersion() {
+        "1.9"
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomisationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomisationIntegTest.groovy
new file mode 100644
index 0000000..dc59e1e
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorCustomisationIntegTest.groovy
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.ivy.IvyDescriptor
+
+class IvyPublishDescriptorCustomisationIntegTest extends AbstractIntegrationSpec {
+
+    def module = ivyRepo.module("org.gradle", "publish", "2")
+
+    def setup() {
+        settingsFile << """
+            rootProject.name = "${module.module}"
+        """
+
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '${module.revision}'
+            group = '${module.organisation}'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+    }
+
+    def "can customise descriptor xml during publication"() {
+        when:
+        succeeds 'publish'
+
+        then:
+        ":jar" in executedTasks
+
+        and:
+        module.ivy.revision == "2"
+
+        when:
+        buildFile << """
+            publishing {
+                publications {
+                    ivy {
+                        descriptor {
+                            withXml {
+                                asNode().info[0].appendNode('description', 'Customized descriptor')
+                            }
+                        }
+                    }
+                }
+            }
+        """
+        succeeds 'publish'
+
+
+        then:
+        ":jar" in skippedTasks
+
+        and:
+        module.ivy.description == "Customized descriptor"
+    }
+
+    def "can generate ivy.xml without publishing"() {
+        given:
+        def moduleName = module.module
+        buildFile << """
+            publishing {
+                generateIvyModuleDescriptor {
+                    destination = 'generated-ivy.xml'
+                }
+            }
+        """
+
+        when:
+        succeeds 'generateIvyModuleDescriptor'
+
+        then:
+        file('generated-ivy.xml').assertIsFile()
+        IvyDescriptor ivy = new IvyDescriptor(file('generated-ivy.xml'))
+        ivy.expectArtifact(moduleName).hasAttributes("jar", "jar", ["runtime"])
+        module.ivyFile.assertDoesNotExist()
+    }
+
+    def "produces sensible error when withXML fails"() {
+        when:
+        buildFile << """
+            publishing {
+                publications {
+                    ivy {
+                        descriptor.withXml {
+                            asNode().foo = "3"
+                        }
+                    }
+                }
+            }
+        """
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':generateIvyModuleDescriptor'")
+        failure.assertHasCause("Could not apply withXml() to Ivy module descriptor")
+        failure.assertHasCause("No such property: foo for class: groovy.util.Node")
+    }
+
+    def "produces sensible error when withXML modifies publication coordinates"() {
+        when:
+        buildFile << """
+            publishing {
+                publications {
+                    ivy {
+                        descriptor.withXml {
+                            asNode().info[0]. at revision = "2.1"
+                        }
+                    }
+                }
+            }
+        """
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishIvyPublicationToIvyRepository'")
+        failure.assertHasCause("Failed to publish publication 'ivy' to repository 'ivy'")
+        failure.assertHasCause("Invalid publication 'ivy': supplied revision does not match ivy descriptor (cannot edit revision directly in the ivy descriptor file).")
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy
deleted file mode 100644
index 1c8f0eb..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishDescriptorModificationIntegTest.groovy
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-class IvyPublishDescriptorModificationIntegTest extends AbstractIntegrationSpec {
-
-    def module = ivyRepo.module("org.gradle", "publish", "2")
-
-    def setup() {
-        settingsFile << """
-            rootProject.name = "${module.module}"
-        """
-
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '${module.revision}'
-            group = '${module.organisation}'
-
-            publishing {
-                repositories {
-                    ivy { url "${ivyRepo.uri}" }
-                }
-            }
-        """
-    }
-
-    def "can modify descriptor during publication"() {
-        when:
-        succeeds 'publish'
-
-        then:
-        ":jar" in executedTasks
-
-        and:
-        module.ivy.revision == "2"
-
-        when:
-        buildFile << """
-            publishing {
-                publications {
-                    ivy {
-                        descriptor {
-                            withXml {
-                                asNode().info[0]. at revision = "3"
-                            }
-                        }
-                    }
-                }
-            }
-        """
-        succeeds 'publish'
-
-
-        then:
-        ":jar" in skippedTasks
-
-        and:
-        // Note that the modified “coordinates” do not affect how the module is published
-        // This is not the desired behaviour and will be fixed in the future so that XML modification changes the publication model consistently.
-        module.ivy.revision == "3"
-    }
-
-    def "produces sensible error when withXML fails"() {
-        when:
-        buildFile << """
-            publishing {
-                publications {
-                    ivy {
-                        descriptor {
-                            withXml {
-                                asNode().foo = "3"
-                            }
-                        }
-                    }
-                }
-            }
-        """
-        fails 'publish'
-
-        then:
-        failure.assertHasDescription("Execution failed for task ':generateIvyModuleDescriptor'")
-        failure.assertHasCause("Could not apply withXml() to Ivy module descriptor")
-        failure.assertHasCause("No such property: foo for class: groovy.util.Node")
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishEarIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishEarIntegTest.groovy
new file mode 100644
index 0000000..4547a3f
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishEarIntegTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+class IvyPublishEarIntegTest extends AbstractIvyPublishIntegTest {
+    public void "can publish EAR only for mixed java and WAR and EAR project"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishEar' "
+
+        and:
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'war'
+            apply plugin: 'ear'
+            apply plugin: 'ivy-publish'
+
+            group = 'org.gradle.test'
+            version = '1.9'
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "commons-collections:commons-collections:3.2.1"
+                runtime "commons-io:commons-io:1.4"
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url '${ivyRepo.uri}'
+                    }
+                }
+                publications {
+                    ivyEar(IvyPublication) {
+                        configurations {
+                            master {}
+                            "default" {
+                                extend "master"
+                            }
+                        }
+                        artifact source: ear, conf: "master"
+                    }
+                }
+            }
+        """
+
+        when:
+        run "publish"
+
+        then: "module is published with artifacts"
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishEar", "1.9")
+        ivyModule.assertPublishedAsEarModule()
+
+        and: "correct configurations and dependencies declared"
+        with (ivyModule.ivy) {
+            configurations.keySet() == ["default", "master"] as Set
+            configurations.default.extend == ["master"] as Set
+            configurations.master.extend == null
+
+            dependencies.isEmpty()
+        }
+
+        and: "can resolve ear module"
+        resolveArtifacts(ivyModule) == ["publishEar-1.9.ear"]
+    }
+}
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
new file mode 100644
index 0000000..054fd5c
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishHttpIntegTest.groovy
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
+import org.gradle.internal.jvm.Jvm
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.ivy.IvyFileModule
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.gradle.util.GradleVersion
+import org.gradle.util.TextUtil
+import org.hamcrest.Matchers
+import org.junit.Rule
+import org.mortbay.jetty.HttpStatus
+import spock.lang.Unroll
+
+import static org.gradle.test.matchers.UserAgentMatcher.matchesNameAndVersion
+
+public class IvyPublishHttpIntegTest extends AbstractIntegrationSpec {
+    private static final String BAD_CREDENTIALS = '''
+credentials {
+    username 'testuser'
+    password 'bad'
+}
+'''
+    @Rule
+    public final HttpServer server = new HttpServer()
+
+    @Rule ProgressLoggingFixture progressLogging = new ProgressLoggingFixture(executer, temporaryFolder)
+
+    private IvyFileModule module
+
+    def setup() {
+        module = ivyRepo.module("org.gradle", "publish", "2")
+        module.moduleDir.mkdirs()
+        server.expectUserAgent(matchesNameAndVersion("Gradle", GradleVersion.current().getVersion()))
+    }
+
+    def "can publish to unauthenticated HTTP repository"() {
+        given:
+        server.start()
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                repositories {
+                    ivy { url "http://localhost:${server.port}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+
+        when:
+        expectUpload('/org.gradle/publish/2/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, HttpStatus.ORDINAL_201_Created)
+
+        and:
+        succeeds 'publish'
+
+        then:
+        module.ivyFile.assertIsFile()
+        module.assertChecksumPublishedFor(module.ivyFile)
+
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+        module.assertChecksumPublishedFor(module.jarFile)
+        and:
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/ivy-2.xml")
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/publish-2.jar")
+    }
+
+
+    @Unroll
+    def "can publish to authenticated repository using #authScheme auth"() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                repositories {
+                    ivy {
+                        credentials {
+                            username 'testuser'
+                            password 'password'
+                        }
+                        url "http://localhost:${server.port}"
+                    }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+
+        when:
+        server.authenticationScheme = authScheme
+        expectUpload('/org.gradle/publish/2/publish-2.jar', module, module.jarFile, 'testuser', 'password')
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, 'testuser', 'password')
+
+        then:
+        succeeds 'publish'
+
+        and:
+        module.ivyFile.assertIsFile()
+        module.assertChecksumPublishedFor(module.ivyFile)
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+        module.assertChecksumPublishedFor(module.jarFile)
+
+        and:
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/ivy-2.xml")
+        progressLogging.uploadProgressLogged("http://localhost:${server.port}/org.gradle/publish/2/publish-2.jar")
+
+        where:
+        authScheme << [HttpServer.AuthScheme.BASIC, HttpServer.AuthScheme.DIGEST]
+    }
+
+    @Unroll
+    def "reports failure publishing with #credsName credentials to authenticated repository using #authScheme auth"() {
+        given:
+        server.start()
+
+        when:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+            version = '2'
+            group = 'org.gradle'
+            publishing {
+                repositories {
+                    ivy {
+                        $creds
+                        url "http://localhost:${server.port}"
+                    }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+
+        and:
+        server.authenticationScheme = authScheme
+        server.allowPut('/org.gradle/publish/2/publish-2.jar', 'testuser', 'password')
+
+        then:
+        fails 'publish'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
+        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
+        failure.assertThatCause(Matchers.containsString('Received status code 401 from server: Unauthorized'))
+
+        where:
+        authScheme                   | credsName | creds
+        HttpServer.AuthScheme.BASIC  | 'empty'   | ''
+        HttpServer.AuthScheme.DIGEST | 'empty'   | ''
+        HttpServer.AuthScheme.BASIC  | 'bad'     | BAD_CREDENTIALS
+        HttpServer.AuthScheme.DIGEST | 'bad'     | BAD_CREDENTIALS
+    }
+
+    def "reports failure publishing to HTTP repository"() {
+        given:
+        server.start()
+        def repositoryUrl = "http://localhost:${server.port}"
+
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+            version = '2'
+            group = 'org.gradle'
+            publishing {
+                repositories {
+                    ivy {
+                        url "${repositoryUrl}"
+                    }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+
+        when:
+        server.addBroken("/")
+
+        then:
+        fails 'publish'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
+        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
+        failure.assertThatCause(Matchers.containsString('Received status code 500 from server: broken'))
+
+        when:
+        server.stop()
+
+        then:
+        fails 'publish'
+
+        and:
+        failure.assertHasDescription('Execution failed for task \':publishIvyPublicationToIvyRepository\'.')
+        failure.assertHasCause('Failed to publish publication \'ivy\' to repository \'ivy\'')
+        failure.assertHasCause("org.apache.http.conn.HttpHostConnectException: Connection to ${repositoryUrl} refused")
+    }
+
+    def "uses first configured pattern for publication"() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+            publishing {
+                repositories {
+                    ivy {
+                        artifactPattern "http://localhost:${server.port}/primary/[module]/[artifact]-[revision].[ext]"
+                        artifactPattern "http://localhost:${server.port}/alternative/[module]/[artifact]-[revision].[ext]"
+                        ivyPattern "http://localhost:${server.port}/primary-ivy/[module]/ivy-[revision].xml"
+                        ivyPattern "http://localhost:${server.port}/secondary-ivy/[module]/ivy-[revision].xml"
+                    }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+
+        when:
+        expectUpload('/primary/publish/publish-2.jar', module, module.jarFile, HttpStatus.ORDINAL_200_OK)
+        expectUpload('/primary-ivy/publish/ivy-2.xml', module, module.ivyFile)
+
+        then:
+        succeeds 'publish'
+
+        and:
+        module.ivyFile.assertIsFile()
+        module.jarFile.assertIsCopyOf(file('build/libs/publish-2.jar'))
+    }
+
+    public void "can publish large artifact (tools.jar) to authenticated repository"() {
+        given:
+        server.start()
+        def toolsJar = TextUtil.escapeString(Jvm.current().toolsJar)
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                repositories {
+                    ivy {
+                        credentials {
+                            username 'testuser'
+                            password 'password'
+                        }
+                        url "http://localhost:${server.port}"
+                    }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        configurations {
+                            runtime {
+                                artifact('$toolsJar') {
+                                    name 'tools'
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        """
+
+        when:
+        def uploadedToolsJar = module.moduleDir.file('toolsJar')
+        expectUpload('/org.gradle/publish/2/tools-2.jar', module, uploadedToolsJar, 'testuser', 'password')
+        expectUpload('/org.gradle/publish/2/ivy-2.xml', module, module.ivyFile, 'testuser', 'password')
+
+        then:
+        succeeds 'publish'
+
+        and:
+        module.ivyFile.assertIsFile()
+        uploadedToolsJar.assertIsCopyOf(new TestFile(toolsJar));
+
+    }
+
+    public void "does not upload meta-data file if artifact upload fails"() {
+        given:
+        server.start()
+
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "http://localhost:${server.port}"
+                    }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+        """
+        when:
+        server.expectPut("/org.gradle/publish/2/publish-2.jar", module.jarFile, HttpStatus.ORDINAL_500_Internal_Server_Error)
+
+        then:
+        fails ':publish'
+
+        and:
+        module.jarFile.assertExists()
+        module.ivyFile.assertDoesNotExist()
+    }
+
+    private void expectUpload(String path, IvyFileModule module, TestFile file, int statusCode = HttpStatus.ORDINAL_200_OK) {
+        server.expectPut(path, file, statusCode)
+        server.expectPut("${path}.sha1", module.sha1File(file), statusCode)
+    }
+
+    private void expectUpload(String path, IvyFileModule module, TestFile file, String username, String password) {
+        server.expectPut(path, username, password, file)
+        server.expectPut("${path}.sha1", username, password, module.sha1File(file))
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIdentifierValidationIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIdentifierValidationIntegTest.groovy
new file mode 100644
index 0000000..4a7f501
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIdentifierValidationIntegTest.groovy
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+import org.gradle.test.fixtures.publish.Identifier
+import spock.lang.Unroll
+
+class IvyPublishIdentifierValidationIntegTest extends AbstractIvyPublishIntegTest {
+
+    @Unroll
+    def "can publish with project coordinates containing #title characters"() {
+        given:
+        file("content-file") << "some content"
+        def organisation = identifier.safeForFileName().decorate("org")
+        def moduleName = identifier.safeForFileName().decorate("module")
+        def version = identifier.safeForFileName().decorate("revision")
+        def description = identifier.decorate("description")
+        def module = ivyRepo.module(organisation, moduleName, version)
+
+        settingsFile.text = "rootProject.name = '${sq(moduleName)}'"
+        buildFile.text = """
+            apply plugin: 'ivy-publish'
+            apply plugin: 'java'
+
+            group = '${sq(organisation)}'
+            version = '${sq(version)}'
+
+            println project.version
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                        descriptor.withXml {
+                            asNode().info[0].appendNode('description', '${sq(description)}')
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.ivy.description == description.toString()
+
+        and:
+        resolveArtifacts(module) == [moduleName + '-' + version + '.jar']
+
+        where:
+        title        | identifier
+        "punctuation"| Identifier.punctuation
+        "non-ascii"  | Identifier.nonAscii
+        "whitespace" | Identifier.whiteSpace
+        "filesystem" | Identifier.fileSystemReserved
+        "xml markup" | Identifier.xmlMarkup
+    }
+
+    @Unroll
+    def "can publish artifacts with attributes containing #title characters"() {
+        given:
+        file("content-file") << "some content"
+
+        def organisation = identifier.safeForFileName().decorate("org")
+        def moduleName = identifier.safeForFileName().decorate("module")
+        def version = identifier.safeForFileName().decorate("revision")
+        def module = ivyRepo.module(organisation, moduleName, version)
+
+        def artifact = identifier.safeForFileName().decorate("artifact")
+        def extension = identifier.safeForFileName().decorate("extension")
+        def type = identifier.safeForFileName().decorate("type")
+        def conf = identifier.safeForFileName().decorate("conf").replace(",", "")
+        def classifier = identifier.safeForFileName().decorate("classifier")
+
+        settingsFile.text = "rootProject.name = '${sq(moduleName)}'"
+        buildFile.text = """
+            apply plugin: 'ivy-publish'
+
+            group = '${sq(organisation)}'
+            version = '${sq(version)}'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        configurations.create('${sq(conf)}')
+                        artifact source: 'content-file', name: '${sq(artifact)}', extension: '${sq(extension)}', type: '${sq(type)}', conf: '${sq(conf)}', classifier: '${sq(classifier)}'
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("ivy-${version}.xml", "${artifact}-${version}-${classifier}.${extension}")
+
+        and:
+        resolveArtifacts(module, conf) == ["${artifact}-${version}-${classifier}.${extension}"]
+
+        where:
+        title        | identifier
+        "punctuation"| Identifier.punctuation
+        "non-ascii"  | Identifier.nonAscii
+        "whitespace" | Identifier.whiteSpace
+        "filesystem" | Identifier.fileSystemReserved
+        "xml markup" | Identifier.xmlMarkup
+    }
+
+    def "fails with reasonable error message for invalid identifier value"() {
+        buildFile << """
+            apply plugin: 'ivy-publish'
+
+            group = ''
+            version = ''
+
+            publishing {
+                repositories {
+                    ivy { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    ivy(IvyPublication)
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription "Execution failed for task ':publishIvyPublicationToIvyRepository'"
+        failure.assertHasCause "Failed to publish publication 'ivy' to repository 'ivy'"
+        failure.assertHasCause "Invalid publication 'ivy': organisation cannot be empty."
+    }
+
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIssuesIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIssuesIntegTest.groovy
new file mode 100644
index 0000000..8a29dd3
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishIssuesIntegTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy;
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec;
+import org.spockframework.util.TextUtil;
+import spock.lang.Issue;
+
+public class IvyPublishIssuesIntegTest extends AbstractIntegrationSpec {
+
+    @Issue("GRADLE-2456")
+    def "generates SHA1 file with leading zeros"() {
+        given:
+        def module = ivyRepo.module("org.gradle", "publish", "2")
+        byte[] jarBytes = [0, 0, 0, 5]
+        def artifactFile = file("testfile.bin")
+        artifactFile << jarBytes
+        def artifactPath = TextUtil.escape(artifactFile.path)
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'ivy-publish'
+
+            group = "org.gradle"
+            version = '2'
+
+            publishing {
+                repositories {
+                    ivy {
+                        url "${ivyRepo.uri}"
+                    }
+                }
+                publications {
+                    ivy(IvyPublication) {
+                        configurations {
+                            custom {}
+                        }
+                        artifact source: file("${artifactPath}"), name: 'testfile', type: 'bin'
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def shaOneFile = module.moduleDir.file("testfile-2.bin.sha1")
+        shaOneFile.exists()
+        shaOneFile.text == "00e14c6ef59816760e2c9b5a57157e8ac9de4012"
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy
new file mode 100644
index 0000000..b507d5b
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api.publish.ivy
+
+class IvyPublishJavaIntegTest extends AbstractIvyPublishIntegTest {
+    def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
+
+    public void "can publish jar and descriptor to ivy repository"() {
+        given:
+        createBuildScripts("""
+            publishing {
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+""")
+
+        when:
+        run "publish"
+
+        then:
+        ivyModule.assertPublishedAsJavaModule()
+
+        with (ivyModule.ivy) {
+            configurations.keySet() == ["default", "runtime"] as Set
+            configurations["default"].extend == ["runtime"] as Set
+            configurations["runtime"].extend == null
+
+            expectArtifact("publishTest").hasAttributes("jar", "jar", ["runtime"])
+        }
+        // TODO:DAZ For some reason this doesn't work inside the with block. Investigate.
+        ivyModule.ivy.assertDependsOn("commons-collections:commons-collections:3.2.1 at runtime", "commons-io:commons-io:1.4 at runtime")
+
+        and:
+        resolveArtifacts(ivyModule) == ["commons-collections-3.2.1.jar", "commons-io-1.4.jar", "publishTest-1.9.jar"]
+    }
+
+    public void "ignores extra artifacts added to configurations"() {
+        given:
+        createBuildScripts("""
+            task extraJar(type: Jar) {
+                from sourceSets.main.allJava
+                baseName "publishTest-extra"
+            }
+
+            artifacts {
+                runtime extraJar
+                archives extraJar
+                it."default" extraJar
+            }
+
+            publishing {
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                    }
+                }
+            }
+""")
+
+        when:
+        run "publish"
+
+        then:
+        ivyModule.assertPublishedAsJavaModule()
+    }
+
+    public void "can publish additional artifacts for java project"() {
+        given:
+        createBuildScripts("""
+            task sourceJar(type: Jar) {
+                from sourceSets.main.allJava
+                baseName "publishTest-source"
+            }
+
+            publishing {
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                        artifact(sourceJar) {
+                            classifier "source"
+                            type "sources"
+                            conf "runtime"
+                        }
+                    }
+                }
+            }
+""")
+
+        when:
+        run "publish"
+
+        then:
+        ivyModule.assertPublished()
+        ivyModule.assertArtifactsPublished("publishTest-1.9.jar", "publishTest-1.9-source.jar", "ivy-1.9.xml")
+
+        ivyModule.ivy.expectArtifact("publishTest", "jar", "source").hasAttributes("jar", "sources", ["runtime"], "source")
+
+        and:
+        resolveArtifacts(ivyModule) == ["commons-collections-3.2.1.jar", "commons-io-1.4.jar", "publishTest-1.9-source.jar", "publishTest-1.9.jar"]
+    }
+
+    def createBuildScripts(def append) {
+        settingsFile << "rootProject.name = 'publishTest' "
+
+        buildFile << """
+            apply plugin: 'ivy-publish'
+            apply plugin: 'java'
+
+            publishing {
+                repositories {
+                    ivy { url "${ivyRepo.uri}" }
+                }
+            }
+
+$append
+
+            group = 'org.gradle.test'
+            version = '1.9'
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "commons-collections:commons-collections:3.2.1"
+                runtime "commons-io:commons-io:1.4"
+                testCompile "junit:junit:4.11"
+            }
+"""
+
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultiProjectIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultiProjectIntegTest.groovy
new file mode 100644
index 0000000..0bb29d9
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultiProjectIntegTest.groovy
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+class IvyPublishMultiProjectIntegTest extends AbstractIvyPublishIntegTest {
+    def project1 = ivyRepo.module("org.gradle.test", "project1", "1.0")
+    def project2 = ivyRepo.module("org.gradle.test", "project2", "2.0")
+    def project3 = ivyRepo.module("org.gradle.test", "project3", "3.0")
+
+    def "project dependencies are correctly bound to published project"() {
+        createBuildScripts("")
+
+        when:
+        run "publish"
+
+        then:
+        project1.assertPublishedAsJavaModule()
+        project1.ivy.assertDependsOn("org.gradle.test:project2:2.0 at runtime", "org.gradle.test:project3:3.0 at runtime")
+
+        project2.assertPublishedAsJavaModule()
+        project2.ivy.assertDependsOn("org.gradle.test:project3:3.0 at runtime")
+
+        project3.assertPublishedAsJavaModule()
+        project3.ivy.dependencies.isEmpty()
+
+        and:
+        resolveArtifacts(project1) == ['project1-1.0.jar', 'project2-2.0.jar', 'project3-3.0.jar']
+    }
+
+    def "ivy-publish plugin does not take archivesBaseName into account"() {
+        createBuildScripts("""
+project(":project2") {
+    archivesBaseName = "changed"
+}
+        """)
+
+        when:
+        run "publish"
+
+        then:
+        project1.assertPublishedAsJavaModule()
+        project1.ivy.assertDependsOn("org.gradle.test:project2:2.0 at runtime", "org.gradle.test:project3:3.0 at runtime")
+
+        // published with the correct coordinates, even though artifact has different name
+        project2.assertPublishedAsJavaModule()
+        project2.ivy.assertDependsOn("org.gradle.test:project3:3.0 at runtime")
+
+        project3.assertPublishedAsJavaModule()
+        project3.ivy.dependencies.isEmpty()
+    }
+
+    def "ivy-publish plugin uses target project name for project dependency when target project does not have ivy-publish plugin applied"() {
+        given:
+        settingsFile << """
+include "project1", "project2"
+        """
+
+        buildFile << """
+allprojects {
+    group = "org.gradle.test"
+    version = "1.0"
+}
+
+project(":project1") {
+    apply plugin: "java"
+    apply plugin: "ivy-publish"
+
+    dependencies {
+        compile project(":project2")
+    }
+
+    publishing {
+        repositories {
+            ivy { url "${ivyRepo.uri}" }
+        }
+        publications {
+            ivy(IvyPublication) {
+                from components.java
+            }
+        }
+    }
+}
+project(":project2") {
+    apply plugin: 'java'
+    archivesBaseName = "changed"
+}
+        """
+
+        when:
+        run "publish"
+
+        then:
+        project1.assertPublishedAsJavaModule()
+        project1.ivy.assertDependsOn("org.gradle.test:project2:1.0 at runtime")
+    }
+
+
+
+    private void createBuildScripts(String append = "") {
+        settingsFile << """
+include "project1", "project2", "project3"
+        """
+
+        buildFile << """
+allprojects {
+    group = "org.gradle.test"
+    version = "3.0"
+}
+
+subprojects {
+    apply plugin: "java"
+    apply plugin: "ivy-publish"
+
+    publishing {
+        repositories {
+            ivy { url "${ivyRepo.uri}" }
+        }
+        publications {
+            ivy(IvyPublication) {
+                from components.java
+            }
+        }
+    }
+}
+
+project(":project1") {
+    version = "1.0"
+    dependencies {
+        compile project(":project2")
+        compile project(":project3")
+    }
+}
+project(":project2") {
+    version = "2.0"
+    dependencies {
+        compile project(":project3")
+    }
+}
+
+$append
+        """
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy
deleted file mode 100644
index f12a35b..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleReposIntegrationTest.groovy
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
-import org.gradle.test.fixtures.ivy.IvyFileRepository
-import org.gradle.test.fixtures.ivy.IvyModule
-import org.gradle.test.fixtures.server.http.HttpServer
-import org.junit.Rule
-
-class IvyPublishMultipleReposIntegrationTest extends AbstractIntegrationSpec {
-
-    @Rule HttpServer server
-    @Rule ProgressLoggingFixture progressLogging
-
-    String moduleName = "publish"
-    String org = "org.gradle"
-    String rev = "2"
-
-    IvyFileRepository repo1 = new IvyFileRepository(file("repo1"))
-    IvyModule repo1Module = repo1.module(org, moduleName, rev)
-
-    IvyFileRepository repo2 = new IvyFileRepository(file("repo2"))
-    IvyModule repo2Module = repo2.module(org, moduleName, rev)
-
-    def "can publish to different repositories"() {
-        given:
-        settingsFile << 'rootProject.name = "publish"'
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'ivy-publish'
-
-            version = '2'
-            group = 'org.gradle'
-
-            publishing {
-                publications {
-                    ivy.descriptor.withXml {
-                        asNode().info[0].appendNode('description', 'test module')
-                    }
-                }
-                repositories {
-                    ivy {
-                        url "${repo1.uri}"
-                    }
-                    ivy {
-                        name "repo2"
-                        url "${repo2.uri}"
-                    }
-                }
-            }
-        """
-
-        when:
-        succeeds "publish"
-
-        then:
-        ":publishIvyPublicationToIvyRepository" in executedTasks
-        ":publishIvyPublicationToRepo2Repository" in executedTasks
-
-        and:
-        repo1Module.ivyFile.exists()
-        repo1Module.jarFile.exists()
-        repo2Module.ivyFile.exists()
-        repo2Module.jarFile.exists()
-
-        and: // Modification applied to both
-        repo1Module.ivy.description == "test module"
-        repo2Module.ivy.description == "test module"
-    }
-
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleRepositoriesIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleRepositoriesIntegTest.groovy
new file mode 100644
index 0000000..f587aab
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishMultipleRepositoriesIntegTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.ivy.IvyFileRepository
+import org.gradle.test.fixtures.ivy.IvyModule
+import org.gradle.test.fixtures.server.http.HttpServer
+import org.junit.Rule
+
+class IvyPublishMultipleRepositoriesIntegTest extends AbstractIntegrationSpec {
+
+    @Rule HttpServer server
+
+    String moduleName = "publish"
+    String org = "org.gradle"
+    String rev = "2"
+
+    IvyFileRepository repo1 = new IvyFileRepository(file("repo1"))
+    IvyModule repo1Module = repo1.module(org, moduleName, rev)
+
+    IvyFileRepository repo2 = new IvyFileRepository(file("repo2"))
+    IvyModule repo2Module = repo2.module(org, moduleName, rev)
+
+    def "can publish to different repositories"() {
+        given:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'ivy-publish'
+
+            version = '2'
+            group = 'org.gradle'
+
+            publishing {
+                publications {
+                    ivy(IvyPublication) {
+                        from components.java
+                        descriptor.withXml {
+                            asNode().info[0].appendNode('description', 'test module')
+                        }
+                    }
+                }
+                repositories {
+                    ivy {
+                        url "${repo1.uri}"
+                    }
+                    ivy {
+                        name "repo2"
+                        url "${repo2.uri}"
+                    }
+                }
+            }
+        """
+
+        when:
+        succeeds "publish"
+
+        then:
+        ":publishIvyPublicationToIvyRepository" in executedTasks
+        ":publishIvyPublicationToRepo2Repository" in executedTasks
+
+        and:
+        repo1Module.ivyFile.exists()
+        repo1Module.jarFile.exists()
+        repo2Module.ivyFile.exists()
+        repo2Module.jarFile.exists()
+
+        and: // Modification applied to both
+        repo1Module.ivy.description == "test module"
+        repo2Module.ivy.description == "test module"
+    }
+
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishPluginIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishPluginIntegTest.groovy
deleted file mode 100644
index e7a9052..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishPluginIntegTest.groovy
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.WellBehavedPluginTest
-
-class IvyPublishPluginIntegTest extends WellBehavedPluginTest {
-
-    @Override
-    String getPluginId() {
-        "ivy-publish"
-    }
-
-    @Override
-    String getMainTask() {
-        "publish"
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishWarIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishWarIntegTest.groovy
new file mode 100644
index 0000000..2994619
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishWarIntegTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.ivy
+
+class IvyPublishWarIntegTest extends AbstractIvyPublishIntegTest {
+
+    public void "can publish WAR only for mixed java and WAR project"() {
+        given:
+        file("settings.gradle") << "rootProject.name = 'publishTest' "
+
+        and:
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'war'
+            apply plugin: 'ivy-publish'
+
+            group = 'org.gradle.test'
+            version = '1.9'
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "commons-collections:commons-collections:3.2.1"
+                runtime "commons-io:commons-io:1.4"
+                providedCompile "commons-lang:commons-lang:2.6"
+                providedRuntime "commons-cli:commons-cli:1.2"
+                testCompile "junit:junit:4.11"
+            }
+
+            publishing {
+                repositories {
+                    ivy {
+                        url '${ivyRepo.uri}'
+                    }
+                }
+                publications {
+                    ivyWeb(IvyPublication) {
+                        from components.web
+                    }
+                }
+            }
+        """
+
+        when:
+        run "publish"
+
+        then: "module is published with artifacts"
+        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
+        ivyModule.assertPublishedAsWebModule()
+
+        and: "correct configurations and depdendencies declared"
+        with (ivyModule.ivy) {
+            configurations.keySet() == ["default", "master"] as Set
+            configurations.default.extend == ["master"] as Set
+            configurations.master.extend == null
+
+            dependencies.isEmpty()
+        }
+
+        and: "can resolve warfile"
+        resolveArtifacts(ivyModule) == ["publishTest-1.9.war"]
+    }
+}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
deleted file mode 100644
index 59260d4..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvySingleProjectPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-class IvySingleProjectPublishIntegrationTest extends AbstractIntegrationSpec {
-
-    def "publish multiple artifacts in single configuration"() {
-        settingsFile << "rootProject.name = 'publishTest'"
-        file("file1") << "some content"
-        file("file2") << "other content"
-
-        buildFile << """
-            apply plugin: "base"
-            apply plugin: "ivy-publish"
-
-            group = "org.gradle.test"
-            version = 1.9
-
-            configurations {
-                toPublish.visible = false
-                archives.extendsFrom toPublish
-            }
-
-            task jar1(type: Jar) {
-                baseName = "jar1"
-                from "file1"
-            }
-
-            task jar2(type: Jar) {
-                baseName = "jar2"
-                from "file2"
-            }
-
-            artifacts {
-                toPublish jar1, jar2
-            }
-
-            publishing {
-                repositories {
-                    ivy {
-                        url "${ivyRepo.uri}"
-                    }
-                }
-            }
-        """
-
-        when:
-        run "publish"
-
-        then:
-        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
-        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "jar1-1.9.jar", "jar2-1.9.jar")
-        ivyModule.moduleDir.file("jar1-1.9.jar").bytes == file("build/libs/jar1-1.9.jar").bytes
-        ivyModule.moduleDir.file("jar2-1.9.jar").bytes == file("build/libs/jar2-1.9.jar").bytes
-
-        and:
-        def ivyDescriptor = ivyModule.ivy
-        ivyDescriptor.expectArtifact("jar1").conf == ["archives", "toPublish"]
-        ivyDescriptor.expectArtifact("jar2").conf == ["toPublish"]
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
deleted file mode 100644
index 6ccd73d..0000000
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyWarProjectPublishIntegrationTest.groovy
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.api.publish.ivy
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-
-class IvyWarProjectPublishIntegrationTest extends AbstractIntegrationSpec {
-
-    public void "published WAR only for mixed java and WAR project"() {
-        given:
-        file("settings.gradle") << "rootProject.name = 'publishTest' "
-
-        and:
-        buildFile << """
-            apply plugin: 'java'
-            apply plugin: 'war'
-            apply plugin: 'ivy-publish'
-
-            group = 'org.gradle.test'
-            version = '1.9'
-
-            repositories {
-                mavenCentral()
-            }
-
-            dependencies {
-                compile "commons-collections:commons-collections:3.2.1"
-                runtime "commons-io:commons-io:1.4"
-            }
-
-            publishing {
-                repositories {
-                    ivy {
-                        url '${ivyRepo.uri}'
-                    }
-                }
-            }
-        """
-
-        when:
-        run "publish"
-
-        then:
-        def ivyModule = ivyRepo.module("org.gradle.test", "publishTest", "1.9")
-        ivyModule.assertArtifactsPublished("ivy-1.9.xml", "publishTest-1.9.war", "publishTest-1.9.jar")
-    }
-}
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
index f0be18b..45ace6b 100644
--- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/SamplesIvyPublishIntegrationTest.groovy
@@ -14,40 +14,80 @@
  * limitations under the License.
  */
 package org.gradle.api.publish.ivy
-
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
-import org.gradle.test.fixtures.ivy.IvyDescriptor
 import org.gradle.util.TextUtil
 import org.junit.Rule
 
 public class SamplesIvyPublishIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final Sample quickstart = new Sample(temporaryFolder, "ivy-publish/quickstart")
+    @Rule public final Sample javaProject = new Sample(temporaryFolder, "ivy-publish/java-multi-project")
+    @Rule public final Sample customization = new Sample(temporaryFolder, "ivy-publish/descriptor-customization")
+
+    def "quickstart sample"() {
+        given:
+        sample quickstart
 
-    @Rule Sample sample = new Sample("ivypublish-new")
+        and:
+        def fileRepo = ivy(quickstart.dir.file("build/repo"))
+        def module = fileRepo.module("org.gradle.sample", "quickstart", "1.0")
+
+        when:
+        succeeds "publish"
 
-    def sample() {
+        then:
+        module.assertPublishedAsJavaModule()
+    }
+
+    def "java-multi-project sample"() {
         given:
-        executer.inDirectory(sample.dir)
+        sample javaProject
 
         and:
-        def fileRepo = ivy(sample.dir.file("build/repo"))
-        def ivyModule = fileRepo.module("org.gradle.test", "ivypublish", "1.0")
+        def fileRepo = ivy(javaProject.dir.file("build/repo"))
+        def project1module = fileRepo.module("org.gradle.sample", "project1", "1.0")
+        def project2module = fileRepo.module("org.gradle.sample", "project2", "1.0")
 
         when:
         succeeds "publish"
 
         then:
-        IvyDescriptor ivy = ivyModule.ivy
-        ivy.artifacts.ivypublishSource.mavenAttributes.classifier == "src"
-        ivy.configurations.keySet() == ['archives', 'compile', 'default', 'runtime'] as Set
-        ivy.dependencies.compile.assertDependsOn('junit', 'junit', '4.10')
-        ivy.dependencies.compile.assertDependsOn('ivypublish', 'subproject', 'unspecified')
-
-        def actualIvyXmlText = ivyModule.ivyFile.text.replaceFirst('publication="\\d+"', 'publication="«PUBLICATION-TIME-STAMP»"').trim()
-        actualIvyXmlText == expectedIvyOutput
+        project1module.assertPublished()
+        project1module.assertArtifactsPublished("project1-1.0.jar", "project1-1.0-source.jar", "ivy-1.0.xml")
+
+        project1module.ivy.configurations.keySet() == ['default', 'runtime'] as Set
+        project1module.ivy.description == "The first project"
+        project1module.ivy.assertDependsOn("junit:junit:4.11 at runtime", "org.gradle.sample:project2:1.0 at runtime")
+
+        and:
+        project2module.assertPublished()
+        project2module.assertArtifactsPublished("project2-1.0.jar", "project2-1.0-source.jar", "ivy-1.0.xml")
+
+        project2module.ivy.configurations.keySet() == ['default', 'runtime'] as Set
+        project2module.ivy.description == "The second project"
+        project2module.ivy.assertDependsOn('commons-collections:commons-collections:3.1 at runtime')
+
+        def actualIvyXmlText = project1module.ivyFile.text.replaceFirst('publication="\\d+"', 'publication="«PUBLICATION-TIME-STAMP»"').trim()
+        actualIvyXmlText == getExpectedIvyOutput(javaProject.dir.file("output-ivy.xml"))
+    }
+
+    String getExpectedIvyOutput(def outputFile) {
+        outputFile.readLines()[1..-1].join(TextUtil.getPlatformLineSeparator()).trim()
     }
 
-    String getExpectedIvyOutput() {
-        sample.dir.file("output-ivy.xml").readLines()[1..-1].join(TextUtil.getPlatformLineSeparator()).trim()
+    def "descriptor-customization sample"() {
+        given:
+        sample customization
+
+        and:
+        def fileRepo = ivy(customization.dir.file("build/repo"))
+        def module = fileRepo.module("org.gradle.sample", "descriptor-customization", "1.0")
+
+        when:
+        succeeds "publish"
+
+        then:
+        module.assertPublished()
+        module.ivy.description == "A demonstration of ivy descriptor customization"
     }
 }
diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginIntegTest.groovy
new file mode 100644
index 0000000..afb301f
--- /dev/null
+++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginIntegTest.groovy
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+class IvyPublishPluginIntegTest extends WellBehavedPluginTest {
+
+    @Override
+    String getPluginId() {
+        "ivy-publish"
+    }
+
+    @Override
+    String getMainTask() {
+        "publish"
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/InvalidIvyPublicationException.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/InvalidIvyPublicationException.java
new file mode 100644
index 0000000..73e8cdf
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/InvalidIvyPublicationException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.InvalidUserDataException;
+
+/**
+ * Thrown when attempting to publish with an invalid {@link IvyPublication}.
+ *
+ * @since 1.5
+ */
+ at Incubating
+public class InvalidIvyPublicationException extends InvalidUserDataException {
+    public InvalidIvyPublicationException(String publicationName, String error) {
+        super(formatMessage(publicationName, error));
+    }
+
+    public InvalidIvyPublicationException(String publicationName, String error, Throwable cause) {
+        super(formatMessage(publicationName, error), cause);
+    }
+
+    private static String formatMessage(String publicationName, String error) {
+        return String.format("Invalid publication '%s': %s", publicationName, error);
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifact.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifact.java
new file mode 100644
index 0000000..1c0f784
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifact.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+
+import java.io.File;
+
+/**
+ * An artifact published as part of a {@link IvyPublication}.
+ */
+ at Incubating
+public interface IvyArtifact extends Buildable {
+    /**
+     * The name used to publish the artifact file, never <code>null</code>.
+     * Defaults to the name of the module that this artifact belongs to.
+     */
+    String getName();
+
+    /**
+     * Sets the name used to publish the artifact file.
+     * @param name The name.
+     */
+    void setName(String name);
+
+    /**
+     * The type used to publish the artifact file, never <code>null</code>.
+     */
+    String getType();
+
+    /**
+     * Sets the type used to publish the artifact file.
+     * @param type The type.
+     */
+    void setType(String type);
+
+    /**
+     * The extension used to publish the artifact file, never <code>null</code>.
+     * For an artifact without an extension, this value will be an empty String.
+     */
+    String getExtension();
+
+    /**
+     * Sets the extension used to publish the artifact file.
+     * @param extension The extension.
+     */
+    void setExtension(String extension);
+
+    /**
+     * The classifier used to publish the artifact file.
+     * A <code>null</code> value (the default) indicates that this artifact will be published without a classifier.
+     */
+    @Nullable
+    String getClassifier();
+
+    /**
+     * Sets the classifier used to publish the artifact file.
+     * @param classifier The classifier.
+     */
+    void setClassifier(@Nullable String classifier);
+
+    /**
+     * A comma separated list of public configurations in which this artifact is published.
+     * The '*' wildcard is used to designate that the artifact is published in all public configurations.
+     * A <code>null</code> value (the default) indicates that this artifact will be published without a conf attribute.
+     * @return The value of 'conf' for this artifact.
+     */
+    @Nullable
+    String getConf();
+
+    /**
+     * Sets a comma separated list of public configurations in which this artifact is published.
+     * The '*' wildcard can be used to designate that the artifact is published in all public configurations.
+     * @param conf The value of 'conf' for this artifact.
+     */
+    void setConf(@Nullable String conf);
+
+    /**
+     * The actual file contents to publish.
+     */
+    File getFile();
+
+    /**
+     * Registers some tasks which build this artifact.
+     *
+     * @param tasks The tasks. These are evaluated as per {@link org.gradle.api.Task#dependsOn(Object...)}.
+     */
+    void builtBy(Object... tasks);
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifactSet.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifactSet.java
new file mode 100644
index 0000000..170d649
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyArtifactSet.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.ivy;
+
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Incubating;
+
+/**
+ * A Collection of {@link IvyArtifact}s to be included in an {@link IvyPublication}.
+ *
+ * Being a {@link DomainObjectSet}, a {@code IvyArtifactSet} provides convenient methods for querying, filtering, and applying actions to the set of {@link IvyArtifact}s.
+ *
+ * <pre autoTested="true">
+ * apply plugin: 'ivy-publish'
+ *
+ * def publication = publishing.publications.add("my-pub", IvyPublication)
+ * def artifacts = publication.artifacts
+ *
+ * artifacts.matching({
+ *     it.type == "source"
+ * }).all({
+ *     it.extension = "src.jar"
+ * })
+ * </pre>
+ *
+ * @see DomainObjectSet
+ */
+ at Incubating
+public interface IvyArtifactSet extends DomainObjectSet<IvyArtifact> {
+    /**
+     * Creates and adds a {@link IvyArtifact} to the set.
+     *
+     * The semantics of this method are the same as {@link IvyPublication#artifact(Object)}.
+     *
+     * @param source The source of the artifact content.
+     */
+    IvyArtifact artifact(Object source);
+
+    /**
+     * Creates and adds a {@link IvyArtifact} to the set, which is configured by the associated action.
+     *
+     * The semantics of this method are the same as {@link IvyPublication#artifact(Object, Action)}.
+     *
+     * @param source The source of the artifact.
+     * @param config An action to configure the values of the constructed {@link IvyArtifact}.
+     */
+     IvyArtifact artifact(Object source, Action<? super IvyArtifact> config);
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfiguration.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfiguration.java
new file mode 100644
index 0000000..35d885d
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfiguration.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+import java.util.Set;
+
+/**
+ * A configuration included in an {@link IvyPublication}, which will be published in the ivy descriptor file generated.
+ */
+ at Incubating
+public interface IvyConfiguration extends Named {
+
+    /**
+     * Add the name of a configuration that this configuration extends.
+     * The extend value can use the following wildcards:
+     * <ul>
+     *     <li>* - all other configurations</li>
+     *     <li>*(public) - all other public configurations</li>
+     *     <li>*(private) - all other private configurations</li>
+     * </ul>
+     * @param configuration The extended configuration name
+     */
+    void extend(String configuration);
+
+    /**
+     * The set of names of extended configurations, added via {@link #extend(String)}.
+     *
+     * @return The names of extended configurations.
+     */
+    Set<String> getExtends();
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfigurationContainer.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfigurationContainer.java
new file mode 100644
index 0000000..af5db87
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyConfigurationContainer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectContainer;
+
+/**
+ * The set of {@link IvyConfiguration}s that will be included in the {@link IvyPublication}.
+ *
+ * Being a {@link org.gradle.api.NamedDomainObjectContainer}, a {@code IvyConfigurationContainer} provides
+ * convenient methods for adding, querying, filtering, and applying actions to the set of {@link IvyConfiguration}s.
+ *
+ * <pre autoTested="true">
+ * apply plugin: 'ivy-publish'
+ *
+ * def publication = publishing.publications.add("my-pub", IvyPublication)
+ * def configurations = publication.configurations
+ *
+ * configurations.create("extended", { extend "default"})
+ * configurations.all {
+ *     extend "base"
+ * }
+ * </pre>
+ */
+ at Incubating
+public interface IvyConfigurationContainer extends NamedDomainObjectContainer<IvyConfiguration> {
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyDependency.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyDependency.java
new file mode 100644
index 0000000..cfa641c
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyDependency.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.internal.HasInternalProtocol;
+
+/**
+ * A module dependency declared in an ivy dependency descriptor published as part of an {@link IvyPublication}.
+ */
+ at Incubating
+ at HasInternalProtocol
+public interface IvyDependency {
+
+    /**
+     * The configuration mapping string that will be output for this dependency.
+     * A null value indicates that no "conf" attribute will be written for this dependency.
+     *
+     * @return the configuration mapping
+     */
+    String getConfMapping();
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java
index 0775fad..f7022fa 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyModuleDescriptor.java
@@ -42,7 +42,7 @@ public interface IvyModuleDescriptor {
      *
      * publishing {
      *   publications {
-     *     ivy {
+     *     ivy(IvyPublication) {
      *       descriptor {
      *         withXml {
      *           asNode().dependencies.dependency.find { it. at org == "junit" }. at rev = "4.10"
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java
index f3cbc00..b517987 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/IvyPublication.java
@@ -18,79 +18,72 @@ package org.gradle.api.publish.ivy;
 
 import org.gradle.api.Action;
 import org.gradle.api.Incubating;
+import org.gradle.api.component.SoftwareComponent;
 import org.gradle.api.internal.HasInternalProtocol;
 import org.gradle.api.publish.Publication;
 
 /**
- * An {@code IvyPublication} is the representation/configuration of how Gradle should publish something in Ivy format.
+ * A {@code IvyPublication} is the representation/configuration of how Gradle should publish something in Ivy format, to an Ivy repository.
  *
- * <h3>The “{@code ivy-publish}” plugin and the default publication</h3>
+ * You directly add a named Ivy Publication the project's {@code publishing.publications} container by providing {@link IvyPublication} as the type.
+ * <pre>
+ * publishing {
+ *   publications {
+ *     myPublicationName(IvyPublication)
+ *   }
+ * }
+ * </pre>
  *
- * The “{@code ivy-publish}” plugin creates one {@code IvyPublication} named “{@code ivy}” in the project's
- * {@code publishing.publications} container. This publication is configured to publish all of the project's
- * <i>visible</i> configurations (i.e. {@link org.gradle.api.Project#getConfigurations()}).
  * <p>
- * The Ivy module identifying attributes of the publication are mapped to:
+ * The Ivy module identifying attributes of the publication are mapped as follows:
  * <ul>
  * <li>{@code module} - {@code project.name}</li>
  * <li>{@code organisation} - {@code project.group}</li>
  * <li>{@code revision} - {@code project.version}</li>
  * <li>{@code status} - {@code project.status}</li>
  * </ul>
- * <p>
- * The ability to add multiple publications and finely configure publications will be added in future Gradle versions.
+ * </p>
  *
- * <h3>Publishing the publication</h3>
- *
- * The “{@code ivy-publish}” plugin will automatically create a {@link org.gradle.api.publish.ivy.tasks.PublishToIvyRepository} task
- * for each {@code IvyPublication} and {@link org.gradle.api.artifacts.repositories.IvyArtifactRepository} combination in
- * {@code publishing.publications} and {@code publishing.repositories} respectively.
  * <p>
- * Given the following…
+ * For certain common use cases, it's often sufficient to specify the component to publish, using ({@link #from(org.gradle.api.component.SoftwareComponent)}.
+ * The published component is used to determine which artifacts to publish, and which configurations and dependencies should be listed in the generated ivy descriptor file.
+ * </p><p>
+ * You can add configurations to the generated ivy descriptor file, by supplying a Closure to the {@link #configurations(org.gradle.api.Action)} method.
+ * </p><p>
+ * To add additional artifacts to the set published, use the {@link #artifact(Object)} and {@link #artifact(Object, org.gradle.api.Action)} methods.
+ * You can also completely replace the set of published artifacts using {@link #setArtifacts(Iterable)}.
+ * Together, these methods give you full control over the artifacts to be published.
+ * </p><p>
+ * For any other tweaks to the publication, it is possible to modify the generated Ivy descriptor file prior to publication. This is done using
+ * the {@link IvyModuleDescriptor#withXml(org.gradle.api.Action)} method, normally via a Closure passed to the {@link #descriptor(org.gradle.api.Action)} method.
+ * </p>
+ * <h4>Example of publishing a java component with an added source jar and custom module description</h4>
+ *
  * <pre autoTested="true">
- * apply plugin: 'ivy-publish'
+ * apply plugin: "java"
+ * apply plugin: "ivy-publish"
+ *
+ * task sourceJar(type: Jar) {
+ *   from sourceSets.main.allJava
+ * }
  *
  * publishing {
- *   repositories {
- *     ivy { url "http://my.org/repo1" }
- *     ivy {
- *       name "other"
- *       url "http://my.org/repo2"
+ *   publications {
+ *     myPublication(IvyPublication) {
+ *       from components.java
+ *       artifact(sourceJar) {
+ *         type "source"
+ *         extension "src.jar"
+ *         conf "runtime"
+ *       }
+ *       descriptor.withXml {
+ *         asNode().info[0].appendNode("description", "custom-description")
+ *       }
  *     }
  *   }
  * }
  * </pre>
  *
- * The following tasks will be created automatically by the plugin:
- *
- * <ul>
- * <li>{@code publishIvyPublicationToIvyRepository} - publishes to the first repository (repository default name is “{@code ivy}”) defined</li>
- * <li>{@code publishIvyPublicationToOtherRepository} - publishes to the second repository defined</li>
- * </ul>
- *
- * These tasks are of type {@link org.gradle.api.publish.ivy.tasks.PublishToIvyRepository}. Executing the task will publish the publication
- * to the associated repository.
- *
- * <h4>The “{@code publish}” task</h4>
- *
- * The “{@code publish}” plugin (that the “{@code ivy-publish}” plugin implicitly applies) adds a lifecycle task named “{@code publish}”.
- * All {@link org.gradle.api.publish.ivy.tasks.PublishToIvyRepository} tasks added by this plugin automatically become dependencies of this
- * lifecycle task, which means that often the most convenient way to publish your project is to just run the “{@code publish}” task.
- *
- * <h4>Generating the ivy module descriptor</h4>
- *
- * A {@link org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor} task will be created for each {@code IvyPublication} in {@code publishing.publications}.
- * Each {@code GenerateIvyTask} is automatically a dependency of the respective {@code PublishToIvyRepository} task, so this task is only required for
- * generating the ivy.xml file without also publishing your module.
- *
- * <pre autoTested="true">
- * apply plugin: 'ivy-publish'
-
- * generateIvyModuleDescriptor {
- *     destination = file('generated-ivy.xml') // Override the default file that will contain the descriptor
- * }
- * </pre>
- *
  * @since 1.3
  */
 @Incubating
@@ -114,4 +107,180 @@ public interface IvyPublication extends Publication {
      */
     void descriptor(Action<? super IvyModuleDescriptor> configure);
 
+    /**
+     * Provides the software component that should be published.
+     *
+     * <ul>
+     *     <li>Any artifacts declared by the component will be included in the publication.</li>
+     *     <li>The dependencies declared by the component will be included in the published meta-data.</li>
+     * </ul>
+     *
+     * Currently 2 types of component are supported: 'components.java' (added by the JavaPlugin) and 'components.web' (added by the WarPlugin).
+     * For any individual IvyPublication, only a single component can be provided in this way.
+     *
+     * The following example demonstrates how to publish the 'java' component to a ivy repository.
+     * <pre autoTested="true">
+     * apply plugin: "java"
+     * apply plugin: "ivy-publish"
+     *
+     * publishing {
+     *   publications {
+     *     ivy(IvyPublication) {
+     *       from components.java
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param component The software component to publish.
+     */
+    void from(SoftwareComponent component);
+
+    /**
+     * Defines some {@link IvyConfiguration}s that should be included in the published ivy module descriptor file.
+     *
+     * The following example demonstrates how to add a "testCompile" configuration, and a "testRuntime" configuration that extends it.
+     * <pre autoTested="true">
+     * apply plugin: "java"
+     * apply plugin: "ivy-publish"
+     *
+     * publishing {
+     *   publications {
+     *     ivy(IvyPublication) {
+     *       configurations {
+     *           testCompile
+     *           testRuntime {
+     *               extend "testCompile"
+     *           }
+     *       }
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param config An action or closure to configure the values of the constructed {@link IvyConfiguration}.
+     */
+    void configurations(Action<? super IvyConfigurationContainer> config);
+
+    /**
+     * Returns the complete set of configurations for this publication.
+     * @return the configurations
+     */
+    IvyConfigurationContainer getConfigurations();
+
+    /**
+     * Creates a custom {@link IvyArtifact} to be included in the publication.
+     *
+     * The <code>artifact</code> method can take a variety of input:
+     * <ul>
+     *     <li>A {@link org.gradle.api.artifacts.PublishArtifact} instance. Name, type, extension and classifier values are taken from the supplied instance.</li>
+     *     <li>An {@link org.gradle.api.tasks.bundling.AbstractArchiveTask} instance. Name, type, extension and classifier values are taken from the supplied instance.</li>
+     *     <li>Anything that can be resolved to a {@link java.io.File} via the {@link org.gradle.api.Project#file(Object)} method.
+     *          Name, extension and classifier values are interpolated from the file name.</li>
+     *     <li>A {@link java.util.Map} that contains a 'source' entry that can be resolved as any of the other input types, including file.
+     *         This map can contain additional attributes to further configure the constructed artifact.</li>
+     * </ul>
+     *
+     * The following example demonstrates the addition of various custom artifacts.
+     * <pre autoTested="true">
+     * apply plugin: "ivy-publish"
+     *
+     * task sourceJar(type: Jar) {
+     *   classifier "source"
+     * }
+     *
+     * task genDocs << {
+     *     // Generate 'my-docs-file.htm'
+     * }
+     *
+     * publishing {
+     *   publications {
+     *     ivy(IvyPublication) {
+     *       artifact sourceJar // Publish the output of the sourceJar task
+     *       artifact 'my-file-name.jar' // Publish a file created outside of the build
+     *       artifact source: 'my-docs-file.htm', classifier: 'docs', extension: 'html', builtBy: genDocs // Publish a file generated by the 'genDocs' task
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param source The source of the artifact content.
+     */
+    IvyArtifact artifact(Object source);
+
+    /**
+     * Creates an {@link IvyArtifact} to be included in the publication, which is configured by the associated action.
+     *
+     * The first parameter is used to create a custom artifact and add it to the publication, as per {@link #artifact(Object)}.
+     * The created {@link IvyArtifact} is then configured using the supplied action.
+     * This method also accepts the configure action as a closure argument, by type coercion.
+     *
+     * <pre autoTested="true">
+     * apply plugin: "ivy-publish"
+     *
+     * task sourceJar(type: Jar) {
+     *   classifier "source"
+     * }
+
+     * task genDocs << {
+     *     // Generate 'my-docs-file.htm'
+     * }
+     *
+     * publishing {
+     *   publications {
+     *     ivy(IvyPublication) {
+     *       artifact(sourceJar) {
+     *         // These values will be used instead of the values from the task. The task values will not be updated.
+     *         classifier "src"
+     *         extension "zip"
+     *         conf "runtime->default"
+     *       }
+     *       artifact("my-docs-file.htm") {
+     *         type "documentation"
+     *         extension "html"
+     *         builtBy genDocs
+     *       }
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param source The source of the artifact.
+     * @param config An action to configure the values of the constructed {@link IvyArtifact}.
+     */
+    IvyArtifact artifact(Object source, Action<? super IvyArtifact> config);
+
+    /**
+     * Clears any previously added artifacts from {@link #getArtifacts} and creates artifacts from the specified sources.
+     * Each supplied source is interpreted as per {@link #artifact(Object)}.
+     *
+     * For example, to exclude the dependencies declared by a component and instead use a custom set of artifacts:
+     * <pre autoTested="true">
+     * apply plugin: "java"
+     * apply plugin: "ivy-publish"
+     *
+     * task sourceJar(type: Jar) {
+     *   classifier "source"
+     * }
+     *
+     * publishing {
+     *   publications {
+     *     ivy(IvyPublication) {
+     *       from components.java
+     *       artifacts = ["my-custom-jar.jar", sourceJar]
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param sources The set of artifacts for this publication.
+     */
+    void setArtifacts(Iterable<?> sources);
+
+    /**
+     * Returns the complete set of artifacts for this publication.
+     * @return the artifacts.
+     */
+    IvyArtifactSet getArtifacts();
+
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java
deleted file mode 100644
index 923701e..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyModuleDescriptor.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.internal.UserCodeAction;
-import org.gradle.api.XmlProvider;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.listener.ActionBroadcast;
-
-import java.util.Set;
-
-public class DefaultIvyModuleDescriptor implements IvyModuleDescriptorInternal {
-
-    private final ActionBroadcast<XmlProvider> xmlActions = new ActionBroadcast<XmlProvider>();
-    private final IvyPublicationInternal ivyPublication;
-
-    public DefaultIvyModuleDescriptor(IvyPublicationInternal ivyPublication) {
-        this.ivyPublication = ivyPublication;
-    }
-
-    public Module getModule() {
-        return ivyPublication.getModule();
-    }
-
-    public Set<? extends Configuration> getConfigurations() {
-        return ivyPublication.asNormalisedPublication().getConfigurations();
-    }
-
-    public void withXml(Action<? super XmlProvider> action) {
-        xmlActions.add(new UserCodeAction<XmlProvider>("Could not apply withXml() to Ivy module descriptor", action));
-    }
-
-    public Action<XmlProvider> getXmlAction() {
-        return xmlActions;
-    }
-
-}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java
deleted file mode 100644
index 4accafd..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/DefaultIvyPublication.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.Transformer;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.file.ConfigurableFileCollection;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
-import org.gradle.api.internal.tasks.TaskResolver;
-import org.gradle.api.publish.ivy.IvyModuleDescriptor;
-import org.gradle.internal.reflect.Instantiator;
-
-import java.io.File;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.Callable;
-
-import static org.gradle.util.CollectionUtils.*;
-
-public class DefaultIvyPublication implements IvyPublicationInternal {
-
-    private final String name;
-    private final IvyModuleDescriptorInternal descriptor;
-    private final DependencyMetaDataProvider dependencyMetaDataProvider;
-    private final Set<? extends Configuration> configurations;
-    private final FileResolver fileResolver;
-    private final TaskResolver taskResolver;
-    private PublishArtifact descriptorArtifact;
-
-    public DefaultIvyPublication(
-            String name, Instantiator instantiator, Set<? extends Configuration> configurations,
-            DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver, TaskResolver taskResolver
-    ) {
-        this.name = name;
-        this.configurations = configurations;
-        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
-        this.fileResolver = fileResolver;
-        this.taskResolver = taskResolver;
-        this.descriptor = instantiator.newInstance(DefaultIvyModuleDescriptor.class, this);
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public IvyModuleDescriptorInternal getDescriptor() {
-        return descriptor;
-    }
-
-    public void descriptor(Action<? super IvyModuleDescriptor> configure) {
-        configure.execute(descriptor);
-    }
-
-    public FileCollection getPublishableFiles() {
-        ConfigurableFileCollection files = new DefaultConfigurableFileCollection("publication artifacts", fileResolver, taskResolver);
-        files.from(new Callable<Set<FileCollection>>() {
-            public Set<FileCollection> call() throws Exception {
-                return collect(getConfigurations(), new Transformer<FileCollection, Configuration>() {
-                    public FileCollection transform(Configuration configuration) {
-                        return configuration.getAllArtifacts().getFiles();
-                    }
-                });
-            }
-        });
-        if (descriptorArtifact != null) {
-            files.from(new Callable<File>() {
-                public File call() throws Exception {
-                    return getDescriptorFile();
-                }
-            });
-            files.builtBy(descriptorArtifact);
-        }
-        return files;
-    }
-
-    public IvyNormalizedPublication asNormalisedPublication() {
-        return new IvyNormalizedPublication(getModule(), getFlattenedConfigurations(), getDescriptorFile());
-    }
-
-    public Module getModule() {
-        return dependencyMetaDataProvider.getModule();
-    }
-
-    public Class<IvyNormalizedPublication> getNormalisedPublicationType() {
-        return IvyNormalizedPublication.class;
-    }
-
-    public Set<? extends Configuration> getConfigurations() {
-        return configurations;
-    }
-
-    public void setDescriptorArtifact(PublishArtifact descriptorArtifact) {
-        this.descriptorArtifact = descriptorArtifact;
-    }
-
-    private File getDescriptorFile() {
-        return descriptorArtifact.getFile();
-    }
-
-    // Flattens each of the given configurations to include any parents, visible or not.
-    private Set<Configuration> getFlattenedConfigurations() {
-        Set<Configuration> flattenedConfigurations = new TreeSet<Configuration>(new Namer.Comparator<Configuration>(new Configuration.Namer()));
-        return inject(flattenedConfigurations, configurations, new Action<InjectionStep<Set<Configuration>, Configuration>>() {
-            public void execute(InjectionStep<Set<Configuration>, Configuration> step) {
-                step.getTarget().addAll(step.getItem().getHierarchy());
-            }
-        });
-    }
-
-}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java
deleted file mode 100644
index 050a564..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyModuleDescriptorInternal.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.XmlProvider;
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.publish.ivy.IvyModuleDescriptor;
-
-import java.util.Set;
-
-public interface IvyModuleDescriptorInternal extends IvyModuleDescriptor {
-
-    public Module getModule();
-
-    public Set<? extends Configuration> getConfigurations();
-
-    Action<XmlProvider> getXmlAction();
-
-}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java
deleted file mode 100644
index 0fca0a4..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyNormalizedPublication.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-
-import java.io.File;
-import java.util.Set;
-
-public class IvyNormalizedPublication {
-
-    private final Module module;
-    private final File descriptorFile;
-    private final Set<? extends Configuration> configurations;
-
-    public IvyNormalizedPublication(Module module, Set<? extends Configuration> configurations, File descriptorFile) {
-        this.module = module;
-        this.configurations = configurations;
-        this.descriptorFile = descriptorFile;
-    }
-
-    public Module getModule() {
-        return module;
-    }
-
-    public Set<? extends Configuration> getConfigurations() {
-        return configurations;
-    }
-
-    public File getDescriptorFile() {
-        return descriptorFile;
-    }
-
-}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java
deleted file mode 100644
index dd0eb78..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublicationInternal.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal;
-
-import org.gradle.api.artifacts.Configuration;
-import org.gradle.api.artifacts.Module;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.publish.ivy.IvyPublication;
-
-import java.util.Set;
-
-public interface IvyPublicationInternal extends IvyPublication {
-
-    IvyModuleDescriptorInternal getDescriptor();
-
-    FileCollection getPublishableFiles();
-
-    Module getModule();
-
-    IvyNormalizedPublication asNormalisedPublication();
-
-    Set<? extends Configuration> getConfigurations();
-
-    void setDescriptorArtifact(PublishArtifact descriptorArtifact);
-}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublisher.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublisher.java
deleted file mode 100644
index f1394d9..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/IvyPublisher.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.internal.Cast;
-import org.gradle.api.internal.artifacts.ArtifactPublisher;
-import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
-
-import java.util.Collections;
-
-public class IvyPublisher {
-
-    private final ArtifactPublisher artifactPublisher;
-
-    public IvyPublisher(ArtifactPublisher artifactPublisher) {
-        this.artifactPublisher = artifactPublisher;
-    }
-
-    public void publish(IvyNormalizedPublication publication, IvyArtifactRepository repository) {
-        DependencyResolver dependencyResolver = Cast.cast(ArtifactRepositoryInternal.class, repository).createResolver();
-        artifactPublisher.publish(Collections.singleton(dependencyResolver), publication.getModule(), publication.getConfigurations(), publication.getDescriptorFile());
-    }
-
-}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifact.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifact.java
new file mode 100644
index 0000000..8b17fea
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifact.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.artifact;
+
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+
+public class DefaultIvyArtifact implements IvyArtifact {
+    private final DefaultTaskDependency buildDependencies = new DefaultTaskDependency();
+    private final File file;
+    private String name;
+    private String extension;
+    private String type;
+    private String classifier;
+    private String conf;
+
+    public DefaultIvyArtifact(File file, String name, String extension, String type, String classifier) {
+        this.file = file;
+        this.name = name;
+
+        this.extension = extension;
+        this.type = type;
+        // Handle empty classifiers that come from PublishArtifact and AbstractArchiveTask
+        this.classifier = GUtil.elvis(classifier, null);
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+    
+    public String getExtension() {
+        return extension;
+    }
+
+    public void setExtension(String extension) {
+        this.extension = extension;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public void setClassifier(String classifier) {
+        this.classifier = classifier;
+    }
+
+    public String getConf() {
+        return conf;
+    }
+
+    public void setConf(String conf) {
+        this.conf = conf;
+    }
+
+    public void builtBy(Object... tasks) {
+        buildDependencies.add(tasks);
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return buildDependencies;
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifactSet.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifactSet.java
new file mode 100644
index 0000000..9dd4c6b
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/DefaultIvyArtifactSet.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.artifact;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyInternal;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.api.publish.ivy.IvyArtifactSet;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultIvyArtifactSet extends DefaultDomainObjectSet<IvyArtifact> implements IvyArtifactSet {
+    private final String publicationName;
+    private final TaskDependencyInternal builtBy = new ArtifactsTaskDependency();
+    private final ArtifactsFileCollection files = new ArtifactsFileCollection();
+    private final NotationParser<IvyArtifact> ivyArtifactParser;
+
+    public DefaultIvyArtifactSet(String publicationName, NotationParser<IvyArtifact> ivyArtifactParser) {
+        super(IvyArtifact.class);
+        this.publicationName = publicationName;
+        this.ivyArtifactParser = ivyArtifactParser;
+    }
+
+    public IvyArtifact artifact(Object source) {
+        IvyArtifact artifact = ivyArtifactParser.parseNotation(source);
+        add(artifact);
+        return artifact;
+    }
+
+    public IvyArtifact artifact(Object source, Action<? super IvyArtifact> config) {
+        IvyArtifact artifact = artifact(source);
+        config.execute(artifact);
+        return artifact;
+    }
+
+    public FileCollection getFiles() {
+        return files;
+    }
+
+    private class ArtifactsFileCollection extends AbstractFileCollection {
+
+        public String getDisplayName() {
+            return String.format("artifacts for ivy publication '%s'", publicationName);
+        }
+
+        @Override
+        public TaskDependency getBuildDependencies() {
+            return builtBy;
+        }
+
+        public Set<File> getFiles() {
+            Set<File> files = new LinkedHashSet<File>();
+            for (IvyArtifact artifact : DefaultIvyArtifactSet.this) {
+                files.add(artifact.getFile());
+            }
+            return files;
+        }
+    }
+
+    private class ArtifactsTaskDependency extends AbstractTaskDependency {
+        public void resolve(TaskDependencyResolveContext context) {
+            for (IvyArtifact ivyArtifact : DefaultIvyArtifactSet.this) {
+                context.add(ivyArtifact);
+            }
+        }
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactory.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactory.java
new file mode 100644
index 0000000..0f99230
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactory.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.artifact;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.notations.NotationParserBuilder;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.File;
+import java.util.Collection;
+
+public class IvyArtifactNotationParserFactory implements Factory<NotationParser<IvyArtifact>> {
+    private final Instantiator instantiator;
+    private final FileResolver fileResolver;
+    private final String defaultArtifactName;
+
+    public IvyArtifactNotationParserFactory(Instantiator instantiator, FileResolver fileResolver, IvyPublicationIdentity publicationIdentity) {
+        this.instantiator = instantiator;
+        this.fileResolver = fileResolver;
+
+        // TODO - Need to handle name being modified after addition of artifacts, once we have that functionality.
+        this.defaultArtifactName = publicationIdentity.getModule();
+    }
+
+    public NotationParser<IvyArtifact> create() {
+        FileNotationParser fileNotationParser = new FileNotationParser(fileResolver);
+        ArchiveTaskNotationParser archiveTaskNotationParser = new ArchiveTaskNotationParser();
+        PublishArtifactNotationParser publishArtifactNotationParser = new PublishArtifactNotationParser();
+
+        NotationParser<IvyArtifact> sourceNotationParser = new NotationParserBuilder<IvyArtifact>()
+                .resultingType(IvyArtifact.class)
+                .parser(archiveTaskNotationParser)
+                .parser(publishArtifactNotationParser)
+                .parser(fileNotationParser)
+                .toComposite();
+
+        IvyArtifactMapNotationParser ivyArtifactMapNotationParser = new IvyArtifactMapNotationParser(sourceNotationParser);
+
+        NotationParserBuilder<IvyArtifact> parserBuilder = new NotationParserBuilder<IvyArtifact>()
+                .resultingType(IvyArtifact.class)
+                .parser(archiveTaskNotationParser)
+                .parser(publishArtifactNotationParser)
+                .parser(ivyArtifactMapNotationParser)
+                .parser(fileNotationParser);
+
+        return parserBuilder.toComposite();
+    }
+
+    private class ArchiveTaskNotationParser extends TypedNotationParser<AbstractArchiveTask, IvyArtifact> {
+        private ArchiveTaskNotationParser() {
+            super(AbstractArchiveTask.class);
+        }
+
+        @Override
+        protected IvyArtifact parseType(AbstractArchiveTask archiveTask) {
+            DefaultIvyArtifact ivyArtifact = instantiator.newInstance(
+                    DefaultIvyArtifact.class,
+                    archiveTask.getArchivePath(), defaultArtifactName, archiveTask.getExtension(), archiveTask.getExtension(), archiveTask.getClassifier()
+            );
+            ivyArtifact.builtBy(archiveTask);
+            return ivyArtifact;
+        }
+    }
+
+    private class PublishArtifactNotationParser extends TypedNotationParser<PublishArtifact, IvyArtifact> {
+        private PublishArtifactNotationParser() {
+            super(PublishArtifact.class);
+        }
+
+        @Override
+        protected IvyArtifact parseType(PublishArtifact publishArtifact) {
+            DefaultIvyArtifact ivyArtifact = instantiator.newInstance(
+                    DefaultIvyArtifact.class,
+                    publishArtifact.getFile(), defaultArtifactName, publishArtifact.getExtension(), publishArtifact.getType(), publishArtifact.getClassifier()
+            );
+            ivyArtifact.builtBy(publishArtifact.getBuildDependencies());
+            return ivyArtifact;
+        }
+    }
+
+    private class FileNotationParser implements NotationParser<IvyArtifact> {
+        private final NotationParser<File> fileResolverNotationParser;
+
+        private FileNotationParser(FileResolver fileResolver) {
+            this.fileResolverNotationParser = fileResolver.asNotationParser();
+        }
+
+        public IvyArtifact parseNotation(Object notation) throws UnsupportedNotationException {
+            File file = fileResolverNotationParser.parseNotation(notation);
+            return parseFile(file);
+        }
+
+        protected IvyArtifact parseFile(File file) {
+            String extension = StringUtils.substringAfterLast(file.getName(), ".");
+            return instantiator.newInstance(
+                    DefaultIvyArtifact.class,
+                    file, defaultArtifactName, extension, extension, null
+            );
+        }
+
+        public void describe(Collection<String> candidateFormats) {
+            fileResolverNotationParser.describe(candidateFormats);
+        }
+    }
+
+    private class IvyArtifactMapNotationParser extends MapNotationParser<IvyArtifact> {
+        private final NotationParser<IvyArtifact> sourceNotationParser;
+
+        private IvyArtifactMapNotationParser(NotationParser<IvyArtifact> sourceNotationParser) {
+            this.sourceNotationParser = sourceNotationParser;
+        }
+
+        protected IvyArtifact parseMap(@MapKey("source") Object source) {
+            return sourceNotationParser.parseNotation(source);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Maps containing a 'source' entry, e.g. [source: '/path/to/file', extension: 'zip'].");
+        }
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependency.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependency.java
new file mode 100644
index 0000000..8fcfa5d
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependency.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.dependency;
+
+import org.gradle.api.artifacts.ModuleDependency;
+
+public class DefaultIvyDependency implements IvyDependencyInternal {
+    private ModuleDependency dependency;
+    private String confMapping;
+
+    public DefaultIvyDependency(ModuleDependency dependency, String confMapping) {
+        this.dependency = dependency;
+        this.confMapping = confMapping;
+    }
+
+    public ModuleDependency getModuleDependency() {
+        return dependency;
+    }
+
+    public String getConfMapping() {
+        return confMapping;
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependencySet.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependencySet.java
new file mode 100644
index 0000000..c283513
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/DefaultIvyDependencySet.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.dependency;
+
+import org.gradle.api.internal.DefaultDomainObjectSet;
+
+public class DefaultIvyDependencySet extends DefaultDomainObjectSet<IvyDependencyInternal> {
+    public DefaultIvyDependencySet() {
+        super(IvyDependencyInternal.class);
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/IvyDependencyInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/IvyDependencyInternal.java
new file mode 100644
index 0000000..b1a5a48
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/dependency/IvyDependencyInternal.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.dependency;
+
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.publish.ivy.IvyDependency;
+
+public interface IvyDependencyInternal extends IvyDependency {
+    ModuleDependency getModuleDependency();
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreator.java
new file mode 100644
index 0000000..d5567c9
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreator.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.plugins;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal;
+import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class IvyPublicationDynamicDescriptorGenerationTaskCreator {
+
+    private final Project project;
+
+    public IvyPublicationDynamicDescriptorGenerationTaskCreator(Project project) {
+        this.project = project;
+    }
+
+    public void monitor(PublicationContainer publications) {
+        publications.withType(IvyPublicationInternal.class).all(new Action<IvyPublicationInternal>() {
+            public void execute(IvyPublicationInternal publication) {
+                create(publication);
+            }
+        });
+    }
+
+    private void create(final IvyPublicationInternal publication) {
+        String publicationName = publication.getName();
+
+        String descriptorTaskName = calculateDescriptorTaskName(publicationName);
+        GenerateIvyDescriptor descriptorTask = project.getTasks().add(descriptorTaskName, GenerateIvyDescriptor.class);
+        descriptorTask.setDescription(String.format("Generates the Ivy Module Descriptor XML file for publication '%s'.", publication.getName()));
+        descriptorTask.setDescriptor(publication.getDescriptor());
+
+        ConventionMapping descriptorTaskConventionMapping = new DslObject(descriptorTask).getConventionMapping();
+        descriptorTaskConventionMapping.map("destination", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/ivy.xml");
+            }
+        });
+
+        publication.setDescriptorFile(descriptorTask.getOutputs().getFiles());
+    }
+
+    private String calculateDescriptorTaskName(String publicationName) {
+        return String.format(
+                "generate%sIvyModuleDescriptor",
+                publicationName.toLowerCase().equals("ivy") ? "" : capitalize(publicationName)
+        );
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreator.java
new file mode 100644
index 0000000..119fd8f
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.plugins;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectList;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.ArtifactRepositoryContainer;
+import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal;
+import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository;
+import org.gradle.api.tasks.TaskContainer;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+/**
+ * Dynamically creates tasks for each Ivy publication/repository pair in a publication set and repository set.
+ */
+public class IvyPublishDynamicTaskCreator {
+
+    final private TaskContainer tasks;
+    private final Task publishLifecycleTask;
+
+    public IvyPublishDynamicTaskCreator(TaskContainer tasks, Task publishLifecycleTask) {
+        this.tasks = tasks;
+        this.publishLifecycleTask = publishLifecycleTask;
+    }
+
+    public void monitor(final PublicationContainer publications, final ArtifactRepositoryContainer repositories) {
+        final NamedDomainObjectSet<IvyPublicationInternal> ivyPublications = publications.withType(IvyPublicationInternal.class);
+        final NamedDomainObjectList<IvyArtifactRepository> ivyRepositories = repositories.withType(IvyArtifactRepository.class);
+
+        ivyPublications.all(new Action<IvyPublicationInternal>() {
+            public void execute(IvyPublicationInternal publication) {
+                for (IvyArtifactRepository repository : ivyRepositories) {
+                    maybeCreate(publication, repository);
+                }
+            }
+        });
+
+        ivyRepositories.all(new Action<IvyArtifactRepository>() {
+            public void execute(IvyArtifactRepository repository) {
+                for (IvyPublicationInternal publication : ivyPublications) {
+                    maybeCreate(publication, repository);
+                }
+            }
+        });
+
+        // Note: we aren't supporting removal of repositories or publications
+        // Note: we also aren't considering that repos have a setName, so their name can change
+        //       (though this is a violation of the Named contract)
+    }
+
+    private void maybeCreate(IvyPublicationInternal publication, IvyArtifactRepository repository) {
+        String publicationName = publication.getName();
+        String repositoryName = repository.getName();
+
+        String publishTaskName = calculatePublishTaskName(publicationName, repositoryName);
+        if (tasks.findByName(publishTaskName) == null) {
+            PublishToIvyRepository publishTask = tasks.add(publishTaskName, PublishToIvyRepository.class);
+            publishTask.setPublication(publication);
+            publishTask.setRepository(repository);
+            publishTask.setGroup("publishing");
+            publishTask.setDescription(String.format("Publishes Ivy publication '%s' to Ivy repository '%s'.", publicationName, repositoryName));
+
+            publishLifecycleTask.dependsOn(publishTask);
+        }
+    }
+
+    private String calculatePublishTaskName(String publicationName, String repositoryName) {
+        return String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyConfiguration.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyConfiguration.java
new file mode 100644
index 0000000..f96f65f
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyConfiguration.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publication;
+
+import org.gradle.api.publish.ivy.IvyConfiguration;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultIvyConfiguration implements IvyConfiguration {
+    private final String name;
+    private final Set<String> extendsFrom = new LinkedHashSet<String>();
+
+    public DefaultIvyConfiguration(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void extend(String configuration) {
+        extendsFrom.add(configuration);
+    }
+
+    public Set<String> getExtends() {
+        return extendsFrom;
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyConfigurationContainer.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyConfigurationContainer.java
new file mode 100644
index 0000000..93b4b7d
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyConfigurationContainer.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publication;
+
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.api.publish.ivy.IvyConfiguration;
+import org.gradle.api.publish.ivy.IvyConfigurationContainer;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultIvyConfigurationContainer extends AbstractNamedDomainObjectContainer<IvyConfiguration> implements IvyConfigurationContainer {
+
+    public DefaultIvyConfigurationContainer(Instantiator instantiator) {
+        super(IvyConfiguration.class, instantiator);
+    }
+
+    @Override
+    protected IvyConfiguration doCreate(String name) {
+        return getInstantiator().newInstance(DefaultIvyConfiguration.class, name);
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyModuleDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyModuleDescriptor.java
new file mode 100644
index 0000000..3b736eb
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyModuleDescriptor.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publication;
+
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.internal.UserCodeAction;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.api.publish.ivy.IvyConfiguration;
+import org.gradle.api.publish.ivy.internal.dependency.IvyDependencyInternal;
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
+import org.gradle.listener.ActionBroadcast;
+
+import java.util.Set;
+
+public class DefaultIvyModuleDescriptor implements IvyModuleDescriptorInternal {
+
+    private final ActionBroadcast<XmlProvider> xmlActions = new ActionBroadcast<XmlProvider>();
+    private final IvyPublicationInternal ivyPublication;
+    private String status;
+
+    public DefaultIvyModuleDescriptor(IvyPublicationInternal ivyPublication) {
+        this.ivyPublication = ivyPublication;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public IvyPublicationIdentity getProjectIdentity() {
+        return ivyPublication.getIdentity();
+    }
+
+    public void withXml(Action<? super XmlProvider> action) {
+        xmlActions.add(new UserCodeAction<XmlProvider>("Could not apply withXml() to Ivy module descriptor", action));
+    }
+
+    public Action<XmlProvider> getXmlAction() {
+        return xmlActions;
+    }
+
+    public Set<IvyConfiguration> getConfigurations() {
+        return ivyPublication.getConfigurations();
+    }
+
+    public Set<IvyArtifact> getArtifacts() {
+        return ivyPublication.getArtifacts();
+    }
+
+    public Set<IvyDependencyInternal> getDependencies() {
+        return ivyPublication.getDependencies();
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java
new file mode 100644
index 0000000..10967f0
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publication;
+
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.component.SoftwareComponent;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.component.SoftwareComponentInternal;
+import org.gradle.api.internal.component.Usage;
+import org.gradle.api.internal.file.UnionFileCollection;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.api.publish.ivy.IvyConfigurationContainer;
+import org.gradle.api.publish.ivy.IvyModuleDescriptor;
+import org.gradle.api.publish.ivy.internal.artifact.DefaultIvyArtifactSet;
+import org.gradle.api.publish.ivy.internal.dependency.DefaultIvyDependency;
+import org.gradle.api.publish.ivy.internal.dependency.DefaultIvyDependencySet;
+import org.gradle.api.publish.ivy.internal.dependency.IvyDependencyInternal;
+import org.gradle.api.publish.ivy.internal.publisher.IvyNormalizedPublication;
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.File;
+import java.util.Set;
+
+public class DefaultIvyPublication implements IvyPublicationInternal {
+
+    private final String name;
+    private final IvyModuleDescriptorInternal descriptor;
+    private final IvyPublicationIdentity publicationIdentity;
+    private final IvyConfigurationContainer configurations;
+    private final DefaultIvyArtifactSet ivyArtifacts;
+    private final DefaultIvyDependencySet ivyDependencies;
+    private FileCollection descriptorFile;
+    private SoftwareComponentInternal component;
+
+    public DefaultIvyPublication(
+            String name, Instantiator instantiator, IvyPublicationIdentity publicationIdentity, NotationParser<IvyArtifact> ivyArtifactNotationParser
+    ) {
+        this.name = name;
+        this.publicationIdentity = publicationIdentity;
+        configurations = instantiator.newInstance(DefaultIvyConfigurationContainer.class, instantiator);
+        ivyArtifacts = instantiator.newInstance(DefaultIvyArtifactSet.class, name, ivyArtifactNotationParser);
+        ivyDependencies = instantiator.newInstance(DefaultIvyDependencySet.class);
+        descriptor = instantiator.newInstance(DefaultIvyModuleDescriptor.class, this);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public IvyModuleDescriptorInternal getDescriptor() {
+        return descriptor;
+    }
+
+    public void setDescriptorFile(FileCollection descriptorFile) {
+        this.descriptorFile = descriptorFile;
+    }
+
+    public void descriptor(Action<? super IvyModuleDescriptor> configure) {
+        configure.execute(descriptor);
+    }
+
+    public void from(SoftwareComponent component) {
+        if (this.component != null) {
+            throw new InvalidUserDataException(String.format("Ivy publication '%s' cannot include multiple components", name));
+        }
+        this.component = (SoftwareComponentInternal) component;
+
+        configurations.maybeCreate("default");
+
+        for (Usage usage : this.component.getUsages()) {
+            String conf = usage.getName();
+            configurations.maybeCreate(conf);
+            configurations.getByName("default").extend(conf);
+
+            for (PublishArtifact publishArtifact : usage.getArtifacts()) {
+                artifact(publishArtifact).setConf(conf);
+            }
+
+            for (ModuleDependency dependency : usage.getDependencies()) {
+                // TODO: When we support multiple components or configurable dependencies, we'll need to merge the confs of multiple dependencies with same id.
+                String confMapping = String.format("%s->%s", conf, dependency.getConfiguration());
+                ivyDependencies.add(new DefaultIvyDependency(dependency, confMapping));
+            }
+        }
+    }
+
+    public void configurations(Action<? super IvyConfigurationContainer> config) {
+        config.execute(configurations);
+    }
+
+    public IvyConfigurationContainer getConfigurations() {
+        return configurations;
+    }
+
+    public IvyArtifact artifact(Object source) {
+        return ivyArtifacts.artifact(source);
+    }
+
+    public IvyArtifact artifact(Object source, Action<? super IvyArtifact> config) {
+        return ivyArtifacts.artifact(source, config);
+    }
+
+    public void setArtifacts(Iterable<?> sources) {
+        ivyArtifacts.clear();
+        for (Object source : sources) {
+            artifact(source);
+        }
+    }
+
+    public DefaultIvyArtifactSet getArtifacts() {
+        return ivyArtifacts;
+    }
+
+    public FileCollection getPublishableFiles() {
+        return new UnionFileCollection(ivyArtifacts.getFiles(), descriptorFile);
+    }
+
+    public IvyPublicationIdentity getIdentity() {
+        return publicationIdentity;
+    }
+
+    public Set<IvyDependencyInternal> getDependencies() {
+        return ivyDependencies;
+    }
+
+    public IvyNormalizedPublication asNormalisedPublication() {
+        return new IvyNormalizedPublication(name, getIdentity(), getDescriptorFile(), ivyArtifacts);
+    }
+
+    private File getDescriptorFile() {
+        if (descriptorFile == null) {
+            throw new IllegalStateException("descriptorFile not set for publication");
+        }
+        return descriptorFile.getSingleFile();
+    }
+
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationIdentity.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationIdentity.java
new file mode 100644
index 0000000..d0bd722
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationIdentity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publication;
+
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
+
+public class DefaultIvyPublicationIdentity implements IvyPublicationIdentity {
+    private String organisation;
+    private String module;
+    private String revision;
+
+    public DefaultIvyPublicationIdentity(String organisation, String module, String revision) {
+        this.organisation = organisation;
+        this.module = module;
+        this.revision = revision;
+    }
+
+    public String getOrganisation() {
+        return organisation;
+    }
+
+    public void setOrganisation(String organisation) {
+        this.organisation = organisation;
+    }
+
+    public String getModule() {
+        return module;
+    }
+
+    public void setModule(String module) {
+        this.module = module;
+    }
+
+    public String getRevision() {
+        return revision;
+    }
+
+    public void setRevision(String revision) {
+        this.revision = revision;
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyModuleDescriptorInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyModuleDescriptorInternal.java
new file mode 100644
index 0000000..3279c7b
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyModuleDescriptorInternal.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publication;
+
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.api.publish.ivy.IvyConfiguration;
+import org.gradle.api.publish.ivy.IvyModuleDescriptor;
+import org.gradle.api.publish.ivy.internal.dependency.IvyDependencyInternal;
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
+
+import java.util.Set;
+
+public interface IvyModuleDescriptorInternal extends IvyModuleDescriptor {
+
+    IvyPublicationIdentity getProjectIdentity();
+
+    Set<IvyConfiguration> getConfigurations();
+
+    Set<IvyArtifact> getArtifacts();
+
+    Set<IvyDependencyInternal> getDependencies();
+
+    Action<XmlProvider> getXmlAction();
+
+    String getStatus();
+
+    void setStatus(String status);
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyPublicationInternal.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyPublicationInternal.java
new file mode 100644
index 0000000..8552d14
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/IvyPublicationInternal.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publication;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.publish.ivy.IvyPublication;
+import org.gradle.api.publish.ivy.internal.dependency.IvyDependencyInternal;
+import org.gradle.api.publish.ivy.internal.publisher.IvyNormalizedPublication;
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
+
+import java.util.Set;
+
+public interface IvyPublicationInternal extends IvyPublication {
+
+    IvyPublicationIdentity getIdentity();
+
+    IvyModuleDescriptorInternal getDescriptor();
+
+    void setDescriptorFile(FileCollection descriptorFile);
+
+    FileCollection getPublishableFiles();
+
+    Set<IvyDependencyInternal> getDependencies();
+
+    IvyNormalizedPublication asNormalisedPublication();
+}
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
new file mode 100644
index 0000000..c53c8af
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/DependencyResolverIvyPublisher.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publisher;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.util.GUtil;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DependencyResolverIvyPublisher implements IvyPublisher {
+
+    public void publish(IvyNormalizedPublication publication, PublicationAwareRepository repository) {
+        DependencyResolver dependencyResolver = repository.createPublisher();
+        IvyPublicationIdentity projectIdentity = publication.getProjectIdentity();
+        Map<String, String> extraAttributes = Collections.emptyMap();
+        ModuleRevisionId moduleRevisionId = IvyUtil.createModuleRevisionId(projectIdentity.getOrganisation(), projectIdentity.getModule(), projectIdentity.getRevision(), extraAttributes);
+
+        try {
+
+            for (IvyArtifact publishArtifact : publication.getArtifacts()) {
+                Artifact ivyArtifact = createIvyArtifact(publishArtifact, moduleRevisionId);
+                dependencyResolver.publish(ivyArtifact, publishArtifact.getFile(), true);
+            }
+
+            Artifact artifact = DefaultArtifact.newIvyArtifact(moduleRevisionId, null);
+            dependencyResolver.publish(artifact, publication.getDescriptorFile(), true);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private Artifact createIvyArtifact(IvyArtifact ivyArtifact, ModuleRevisionId moduleRevisionId) {
+        Map<String, String> extraAttributes = new HashMap<String, String>();
+        if (GUtil.isTrue(ivyArtifact.getClassifier())) {
+            extraAttributes.put(Dependency.CLASSIFIER, ivyArtifact.getClassifier());
+        }
+        return new DefaultArtifact(
+                moduleRevisionId,
+                null,
+                GUtil.elvis(ivyArtifact.getName(), moduleRevisionId.getName()),
+                ivyArtifact.getType(),
+                ivyArtifact.getExtension(),
+                extraAttributes);
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGenerator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGenerator.java
new file mode 100644
index 0000000..277066b
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGenerator.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publisher;
+
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.xml.SimpleXmlWriter;
+import org.gradle.api.internal.xml.XmlTransformer;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.api.publish.ivy.IvyConfiguration;
+import org.gradle.api.publish.ivy.internal.dependency.IvyDependencyInternal;
+import org.gradle.util.CollectionUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class IvyDescriptorFileGenerator {
+    private static final String IVY_FILE_ENCODING = "UTF-8";
+    private static final String IVY_DATE_PATTERN = "yyyyMMddHHmmss";
+
+    private final SimpleDateFormat ivyDateFormat = new SimpleDateFormat(IVY_DATE_PATTERN);
+    private final IvyPublicationIdentity projectIdentity;
+    private String status;
+    private XmlTransformer xmlTransformer = new XmlTransformer();
+    private List<IvyConfiguration> configurations = new ArrayList<IvyConfiguration>();
+    private List<IvyArtifact> artifacts = new ArrayList<IvyArtifact>();
+    private List<IvyDependencyInternal> dependencies = new ArrayList<IvyDependencyInternal>();
+
+    public IvyDescriptorFileGenerator(IvyPublicationIdentity projectIdentity) {
+        this.projectIdentity = projectIdentity;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public IvyDescriptorFileGenerator addConfiguration(IvyConfiguration ivyConfiguration) {
+        configurations.add(ivyConfiguration);
+        return this;
+    }
+
+    public IvyDescriptorFileGenerator addArtifact(IvyArtifact ivyArtifact) {
+        artifacts.add(ivyArtifact);
+        return this;
+    }
+
+    public IvyDescriptorFileGenerator addDependency(IvyDependencyInternal ivyDependency) {
+        dependencies.add(ivyDependency);
+        return this;
+    }
+
+    public IvyDescriptorFileGenerator withXml(final Action<XmlProvider> action) {
+        xmlTransformer.addAction(action);
+        return this;
+    }
+
+    public IvyDescriptorFileGenerator writeTo(File file) {
+        xmlTransformer.transform(file, IVY_FILE_ENCODING, new Action<Writer>() {
+            public void execute(Writer writer) {
+                try {
+                    writeDescriptor(writer);
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        });
+        return this;
+    }
+
+    private void writeDescriptor(final Writer writer) throws IOException {
+        OptionalAttributeXmlWriter xmlWriter = new OptionalAttributeXmlWriter(writer, "  ", IVY_FILE_ENCODING);
+        xmlWriter.startElement("ivy-module").attribute("version", "2.0");
+        if (hasClassifier()) {
+            xmlWriter.attribute("xmlns:m", "http://ant.apache.org/ivy/maven");
+        }
+
+        xmlWriter.startElement("info")
+                .attribute("organisation", projectIdentity.getOrganisation())
+                .attribute("module", projectIdentity.getModule())
+                .attribute("revision", projectIdentity.getRevision())
+                .attribute("status", status)
+                .attribute("publication", ivyDateFormat.format(new Date()))
+                .endElement();
+
+        writeConfigurations(xmlWriter);
+        writePublications(xmlWriter);
+        writeDependencies(xmlWriter);
+        xmlWriter.endElement();
+    }
+    private boolean hasClassifier() {
+        for (IvyArtifact artifact : artifacts) {
+            if (artifact.getClassifier() != null) {
+                return true;
+            }
+        }
+        for (IvyDependencyInternal dependency : this.dependencies) {
+            for (DependencyArtifact dependencyArtifact : dependency.getModuleDependency().getArtifacts()) {
+                if (dependencyArtifact.getClassifier() != null) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void writeConfigurations(OptionalAttributeXmlWriter xmlWriter) throws IOException {
+        xmlWriter.startElement("configurations");
+        for (IvyConfiguration configuration : configurations) {
+            xmlWriter.startElement("conf")
+                    .attribute("name", configuration.getName())
+                    .attribute("visibility", "public");
+            if (configuration.getExtends().size() > 0) {
+                xmlWriter.attribute("extends", CollectionUtils.join(",", configuration.getExtends()));
+            }
+            xmlWriter.endElement();
+        }
+        xmlWriter.endElement();
+    }
+
+    private void writePublications(OptionalAttributeXmlWriter xmlWriter) throws IOException {
+        xmlWriter.startElement("publications");
+        for (IvyArtifact artifact : artifacts) {
+            xmlWriter.startElement("artifact")
+                    .attribute("name", artifact.getName())
+                    .attribute("type", artifact.getType())
+                    .attribute("ext", artifact.getExtension())
+                    .attribute("conf", artifact.getConf())
+                    .attribute("m:classifier", artifact.getClassifier())
+                    .endElement();
+        }
+        xmlWriter.endElement();
+    }
+
+    private void writeDependencies(OptionalAttributeXmlWriter xmlWriter) throws IOException {
+        xmlWriter.startElement("dependencies");
+        for (IvyDependencyInternal dependency : dependencies) {
+            ModuleDependency dep = dependency.getModuleDependency();
+            xmlWriter.startElement("dependency")
+                    .attribute("org", dep.getGroup())
+                    .attribute("name", getDependencyName(dep))
+                    .attribute("rev", dep.getVersion())
+                    .attribute("conf", dependency.getConfMapping());
+
+            for (DependencyArtifact dependencyArtifact : dep.getArtifacts()) {
+                printDependencyArtifact(dependencyArtifact, xmlWriter);
+            }
+            xmlWriter.endElement();
+        }
+        xmlWriter.endElement();
+    }
+
+    private String getDependencyName(ModuleDependency dep) {
+        return dep instanceof ProjectDependency ? ((ProjectDependency) dep).getDependencyProject().getName() : dep.getName();
+    }
+
+    private void printDependencyArtifact(DependencyArtifact dependencyArtifact, OptionalAttributeXmlWriter xmlWriter) throws IOException {
+        // TODO Use IvyArtifact here
+        xmlWriter.startElement("artifact")
+                .attribute("name", dependencyArtifact.getName())
+                .attribute("type", dependencyArtifact.getType())
+                .attribute("ext", dependencyArtifact.getExtension())
+                .attribute("m:classifier", dependencyArtifact.getClassifier())
+                .endElement();
+    }
+
+    private static class OptionalAttributeXmlWriter extends SimpleXmlWriter {
+        public OptionalAttributeXmlWriter(Writer writer, String indent, String encoding) throws IOException {
+            super(writer, indent, encoding);
+        }
+
+        @Override
+        public OptionalAttributeXmlWriter startElement(String name) throws IOException {
+            super.startElement(name);
+            return this;
+        }
+
+        @Override
+        public OptionalAttributeXmlWriter attribute(String name, String value) throws IOException {
+            if (value != null) {
+                super.attribute(name, value);
+            }
+            return this;
+        }
+
+        public OptionalAttributeXmlWriter attribute(String name, String value, String defaultValue) throws IOException {
+            super.attribute(name, value == null ? defaultValue : value);
+            return this;
+        }
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyNormalizedPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyNormalizedPublication.java
new file mode 100644
index 0000000..07ede6f
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyNormalizedPublication.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publisher;
+
+import org.gradle.api.publish.ivy.IvyArtifact;
+
+import java.io.File;
+import java.util.Set;
+
+public class IvyNormalizedPublication {
+
+    private final String name;
+    private final IvyPublicationIdentity projectIdentity;
+    private final File descriptorFile;
+    private final Set<IvyArtifact> artifacts;
+
+    public IvyNormalizedPublication(String name, IvyPublicationIdentity projectIdentity, File descriptorFile, Set<IvyArtifact> artifacts) {
+        this.name = name;
+        this.projectIdentity = projectIdentity;
+        this.artifacts = artifacts;
+        this.descriptorFile = descriptorFile;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public IvyPublicationIdentity getProjectIdentity() {
+        return projectIdentity;
+    }
+
+    public File getDescriptorFile() {
+        return descriptorFile;
+    }
+
+    public Set<IvyArtifact> getArtifacts() {
+        return artifacts;
+    }
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyPublicationIdentity.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyPublicationIdentity.java
new file mode 100644
index 0000000..e55f048
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyPublicationIdentity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publisher;
+
+public interface IvyPublicationIdentity {
+    String getOrganisation();
+
+    void setOrganisation(String organisation);
+
+    String getModule();
+
+    void setModule(String module);
+
+    String getRevision();
+
+    void setRevision(String revision);
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyPublisher.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyPublisher.java
new file mode 100644
index 0000000..de29248
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/IvyPublisher.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publisher;
+
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
+
+public interface IvyPublisher {
+    void publish(IvyNormalizedPublication publication, PublicationAwareRepository repository);
+}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java
new file mode 100644
index 0000000..0cd5202
--- /dev/null
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisher.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publisher;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.parser.ParserSettings;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.DisconnectedParserSettings;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
+import org.gradle.api.publish.internal.PublicationFieldValidator;
+import org.gradle.api.publish.ivy.InvalidIvyPublicationException;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.internal.UncheckedException;
+
+import java.io.File;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ValidatingIvyPublisher implements IvyPublisher {
+    private final ParserSettings parserSettings = new DisconnectedParserSettings();
+    private final IvyPublisher delegate;
+
+    public ValidatingIvyPublisher(IvyPublisher delegate) {
+        this.delegate = delegate;
+    }
+
+    public void publish(IvyNormalizedPublication publication, PublicationAwareRepository repository) {
+        validateIdentity(publication);
+        validateArtifacts(publication);
+        checkNoDuplicateArtifacts(publication);
+        delegate.publish(publication, repository);
+    }
+
+    private void validateIdentity(IvyNormalizedPublication publication) {
+        IvyPublicationIdentity identity = publication.getProjectIdentity();
+
+        IvyFieldValidator organisation = field(publication, "organisation", identity.getOrganisation())
+                .notEmpty()
+                .validInFileName();
+        IvyFieldValidator moduleName = field(publication, "module name", identity.getModule())
+                .notEmpty()
+                .validInFileName();
+        IvyFieldValidator revision = field(publication, "revision", identity.getRevision())
+                .notEmpty()
+                .validInFileName();
+
+        ModuleRevisionId moduleId = parseIvyFile(publication);
+        organisation.matches(moduleId.getOrganisation());
+        moduleName.matches(moduleId.getName());
+        revision.matches(moduleId.getRevision());
+    }
+
+    private ModuleRevisionId parseIvyFile(IvyNormalizedPublication publication) {
+
+        try {
+            URL ivyFileLocation = publication.getDescriptorFile().toURI().toURL();
+            return new IvyXmlModuleDescriptorParser().parseDescriptor(parserSettings, ivyFileLocation, true).getModuleRevisionId();
+        } catch (ParseException pe) {
+            throw new InvalidIvyPublicationException(publication.getName(), pe.getLocalizedMessage(), pe);
+        } catch (Exception ex) {
+            throw UncheckedException.throwAsUncheckedException(ex);
+        }
+    }
+
+    private void validateArtifacts(IvyNormalizedPublication publication) {
+        for (final IvyArtifact artifact : publication.getArtifacts()) {
+            field(publication, "artifact name", artifact.getName())
+                    .notEmpty().validInFileName();
+            field(publication, "artifact type", artifact.getType())
+                    .notEmpty().validInFileName();
+            field(publication, "artifact extension", artifact.getExtension())
+                    .notNull().validInFileName();
+            field(publication, "artifact classifier", artifact.getClassifier())
+                    .optionalNotEmpty().validInFileName();
+
+            checkCanPublish(publication.getName(), artifact);
+        }
+    }
+
+    private void checkNoDuplicateArtifacts(IvyNormalizedPublication publication) {
+        Set<IvyArtifact> verified = new HashSet<IvyArtifact>();
+
+        for (final IvyArtifact artifact : publication.getArtifacts()) {
+            checkNotDuplicate(publication, verified, artifact.getName(), artifact.getExtension(), artifact.getType(), artifact.getClassifier());
+            verified.add(artifact);
+        }
+
+        // Check that ivy.xml isn't duplicated
+        checkNotDuplicate(publication, verified, "ivy", "xml", "xml", null);
+    }
+
+    private void checkNotDuplicate(IvyNormalizedPublication publication, Set<IvyArtifact> verified, String name, String extension, String type, String classifier) {
+        for (IvyArtifact alreadyVerified : verified) {
+            if (hasCoordinates(alreadyVerified, name, extension, type, classifier)) {
+                String message = String.format(
+                        "multiple artifacts with the identical name, extension, type and classifier ('%s', %s', '%s', '%s').",
+                        name, extension, type, classifier
+                );
+                throw new InvalidIvyPublicationException(publication.getName(), message);
+            }
+        }
+    }
+
+    private boolean hasCoordinates(IvyArtifact one, String name, String extension, String type, String classifier) {
+        return ObjectUtils.equals(one.getName(), name)
+                && ObjectUtils.equals(one.getType(), type)
+                && ObjectUtils.equals(one.getExtension(), extension)
+                && ObjectUtils.equals(one.getClassifier(), classifier);
+    }
+
+    private void checkCanPublish(String name, IvyArtifact artifact) {
+        File artifactFile = artifact.getFile();
+        if (artifactFile == null || !artifactFile.exists()) {
+            throw new InvalidIvyPublicationException(name, String.format("artifact file does not exist: '%s'", artifactFile));
+        }
+        if (artifactFile.isDirectory()) {
+            throw new InvalidIvyPublicationException(name, String.format("artifact file is a directory: '%s'", artifactFile));
+        }
+    }
+
+    private IvyFieldValidator field(IvyNormalizedPublication publication, String name, String value) {
+        return new IvyFieldValidator(publication.getName(), name, value);
+    }
+
+    private static class IvyFieldValidator extends PublicationFieldValidator<IvyFieldValidator> {
+        private IvyFieldValidator(String publicationName, String name, String value) {
+            super(IvyFieldValidator.class, publicationName, name, value);
+        }
+
+        public IvyFieldValidator matches(String expectedValue) {
+            if (!value.equals(expectedValue)) {
+                throw new InvalidIvyPublicationException(publicationName,
+                        String.format("supplied %s does not match ivy descriptor (cannot edit %1$s directly in the ivy descriptor file).", name)
+                );
+            }
+            return this;
+        }
+
+        @Override
+        protected InvalidUserDataException failure(String message) {
+            throw new InvalidIvyPublicationException(publicationName, message);
+        }
+    }
+}
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 c6b732f..9f309fb 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
@@ -16,31 +16,31 @@
 
 package org.gradle.api.publish.ivy.plugins;
 
-import org.gradle.api.Incubating;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.*;
+import org.gradle.api.artifacts.Module;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
 import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.publish.Publication;
 import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.internal.PublicationContainerInternal;
+import org.gradle.api.publish.internal.PublicationFactory;
+import org.gradle.api.publish.ivy.IvyArtifact;
 import org.gradle.api.publish.ivy.IvyPublication;
-import org.gradle.api.publish.ivy.internal.DefaultIvyPublication;
-import org.gradle.api.publish.ivy.tasks.internal.IvyPublicationDynamicDescriptorGenerationTaskCreator;
-import org.gradle.api.publish.ivy.tasks.internal.IvyPublishDynamicTaskCreator;
+import org.gradle.api.publish.ivy.internal.artifact.IvyArtifactNotationParserFactory;
+import org.gradle.api.publish.ivy.internal.plugins.IvyPublicationDynamicDescriptorGenerationTaskCreator;
+import org.gradle.api.publish.ivy.internal.plugins.IvyPublishDynamicTaskCreator;
+import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublication;
+import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublicationIdentity;
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity;
 import org.gradle.api.publish.plugins.PublishingPlugin;
-import org.gradle.api.specs.Spec;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.internal.reflect.Instantiator;
 
 import javax.inject.Inject;
-import java.util.Set;
 
 /**
- * Configures the project to publish a “main” IvyPublication to a “main” IvyArtifactRepository.
- *
- * Creates an IvyPublication named "main" in project.publications, configured to publish all of the visible configurations of the project.
- * Creates an IvyArtifactRepository
+ * Adds the ability to publish in the Ivy format to Ivy repositories.
  *
  * @since 1.3
  */
@@ -52,42 +52,59 @@ public class IvyPublishPlugin implements Plugin<Project> {
     private final FileResolver fileResolver;
 
     @Inject
-    public IvyPublishPlugin(
-            Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver
-    ) {
+    public IvyPublishPlugin(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver) {
         this.instantiator = instantiator;
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
         this.fileResolver = fileResolver;
     }
 
-    public void apply(Project project) {
+    public void apply(final Project project) {
         project.getPlugins().apply(PublishingPlugin.class);
-        PublishingExtension extension = project.getExtensions().getByType(PublishingExtension.class);
 
         // Create the default publication
-        Set<Configuration> visibleConfigurations = project.getConfigurations().matching(new Spec<Configuration>() {
-            public boolean isSatisfiedBy(Configuration configuration) {
-                return configuration.isVisible();
+        project.getExtensions().configure(PublishingExtension.class, new Action<PublishingExtension>() {
+            public void execute(PublishingExtension publishingExtension) {
+                final PublishingExtension extension = project.getExtensions().getByType(PublishingExtension.class);
+
+                final PublicationContainerInternal publicationContainer = (PublicationContainerInternal) extension.getPublications();
+                publicationContainer.registerFactory(IvyPublication.class, new IvyPublicationFactory(dependencyMetaDataProvider, instantiator, fileResolver));
+
+                TaskContainer tasks = project.getTasks();
+
+                // Create generate descriptor tasks
+                IvyPublicationDynamicDescriptorGenerationTaskCreator descriptorGenerationTaskCreator = new IvyPublicationDynamicDescriptorGenerationTaskCreator(project);
+                descriptorGenerationTaskCreator.monitor(extension.getPublications());
+
+                // Create publish tasks automatically for any Ivy publication and repository combinations
+                Task publishLifecycleTask = tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
+                IvyPublishDynamicTaskCreator publishTaskCreator = new IvyPublishDynamicTaskCreator(tasks, publishLifecycleTask);
+                publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
             }
         });
-        extension.getPublications().add(createPublication("ivy", project, visibleConfigurations));
+    }
 
-        TaskContainer tasks = project.getTasks();
 
-        // Create generate descriptor tasks
-        IvyPublicationDynamicDescriptorGenerationTaskCreator descriptorGenerationTaskCreator = new IvyPublicationDynamicDescriptorGenerationTaskCreator(project);
-        descriptorGenerationTaskCreator.monitor(extension.getPublications());
+    private class IvyPublicationFactory implements PublicationFactory {
+        private final Instantiator instantiator;
+        private final DependencyMetaDataProvider dependencyMetaDataProvider;
+        private final FileResolver fileResolver;
 
-        // Create publish tasks automatically for any Ivy publication and repository combinations
-        Task publishLifecycleTask = tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
-        IvyPublishDynamicTaskCreator publishTaskCreator = new IvyPublishDynamicTaskCreator(tasks, publishLifecycleTask);
-        publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
-    }
+        private IvyPublicationFactory(DependencyMetaDataProvider dependencyMetaDataProvider, Instantiator instantiator, FileResolver fileResolver) {
+            this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+            this.instantiator = instantiator;
+            this.fileResolver = fileResolver;
+        }
 
-    private IvyPublication createPublication(String name, final Project project, Set<? extends Configuration> configurations) {
-        return instantiator.newInstance(
-                DefaultIvyPublication.class,
-                name, instantiator, configurations, dependencyMetaDataProvider, fileResolver, project.getTasks()
-        );
+        public Publication create(String name) {
+            Module module = dependencyMetaDataProvider.getModule();
+            IvyPublicationIdentity publicationIdentity = new DefaultIvyPublicationIdentity(module.getGroup(), module.getName(), module.getVersion());
+            NotationParser<IvyArtifact> notationParser = new IvyArtifactNotationParserFactory(instantiator, fileResolver, publicationIdentity).create();
+            DefaultIvyPublication ivyPublication = instantiator.newInstance(
+                    DefaultIvyPublication.class,
+                    name, instantiator, publicationIdentity, notationParser
+            );
+            ivyPublication.getDescriptor().setStatus(module.getStatus());
+            return ivyPublication;
+        }
     }
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/GenerateIvyDescriptor.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/GenerateIvyDescriptor.java
index 4bb85db..de374b8 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/GenerateIvyDescriptor.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/GenerateIvyDescriptor.java
@@ -16,25 +16,22 @@
 
 package org.gradle.api.publish.ivy.tasks;
 
-import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
-import org.gradle.api.*;
-import org.gradle.api.artifacts.PublishArtifact;
-import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
-import org.gradle.api.internal.artifacts.ivyservice.IvyModuleDescriptorWriter;
-import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Incubating;
+import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.internal.tasks.DefaultTaskDependency;
-import org.gradle.api.internal.xml.XmlTransformer;
+import org.gradle.api.publish.ivy.IvyArtifact;
+import org.gradle.api.publish.ivy.IvyConfiguration;
 import org.gradle.api.publish.ivy.IvyModuleDescriptor;
-import org.gradle.api.publish.ivy.internal.IvyModuleDescriptorInternal;
+import org.gradle.api.publish.ivy.internal.dependency.IvyDependencyInternal;
+import org.gradle.api.publish.ivy.internal.publication.IvyModuleDescriptorInternal;
+import org.gradle.api.publish.ivy.internal.publisher.IvyDescriptorFileGenerator;
 import org.gradle.api.specs.Specs;
 import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
-import org.gradle.api.tasks.TaskDependency;
 
 import javax.inject.Inject;
 import java.io.File;
-import java.util.Date;
 
 /**
  * Generates an Ivy XML Module Descriptor file.
@@ -45,23 +42,16 @@ import java.util.Date;
 public class GenerateIvyDescriptor extends DefaultTask {
 
     private IvyModuleDescriptor descriptor;
-    private final PublishArtifact descriptorArtifact;
-
-    private Action<? super XmlProvider> xmlAction;
     private Object destination;
 
     private final FileResolver fileResolver;
-    private final ArtifactPublicationServices publicationServices;
 
     @Inject
-    public GenerateIvyDescriptor(FileResolver fileResolver, ArtifactPublicationServices publicationServices) {
+    public GenerateIvyDescriptor(FileResolver fileResolver) {
         this.fileResolver = fileResolver;
-        this.publicationServices = publicationServices;
 
         // Never up to date; we don't understand the data structures.
         getOutputs().upToDateWhen(Specs.satisfyNone());
-
-        this.descriptorArtifact = new IvyDescriptorArtifact();
     }
 
     /**
@@ -98,34 +88,26 @@ public class GenerateIvyDescriptor extends DefaultTask {
         this.destination = destination;
     }
 
-    public Action<? super XmlProvider> getXmlAction() {
-        return xmlAction;
-    }
-
-    public void setXmlAction(Action<? super XmlProvider> xmlAction) {
-        this.xmlAction = xmlAction;
-    }
-
-    public PublishArtifact getDescriptorArtifact() {
-        return descriptorArtifact;
-    }
-
     @TaskAction
     public void doGenerate() {
-        XmlTransformer xmlTransformer = new XmlTransformer();
-        Action<? super XmlProvider> xmlAction = getXmlAction();
-        if (xmlAction != null) {
-            xmlTransformer.addAction(xmlAction);
+        IvyModuleDescriptorInternal descriptorInternal = toIvyModuleDescriptorInternal(getDescriptor());
+
+        IvyDescriptorFileGenerator ivyGenerator = new IvyDescriptorFileGenerator(descriptorInternal.getProjectIdentity());
+        ivyGenerator.setStatus(descriptorInternal.getStatus());
+        for (IvyConfiguration ivyConfiguration : descriptorInternal.getConfigurations()) {
+            ivyGenerator.addConfiguration(ivyConfiguration);
         }
 
-        IvyModuleDescriptorInternal descriptorInternal = toIvyModuleDescriptorInternal(getDescriptor());
+        for (IvyArtifact ivyArtifact : descriptorInternal.getArtifacts()) {
+            ivyGenerator.addArtifact(ivyArtifact);
+        }
 
-        ModuleDescriptorConverter moduleDescriptorConverter = publicationServices.getDescriptorFileModuleConverter();
-        ModuleDescriptor moduleDescriptor = moduleDescriptorConverter.convert(descriptorInternal.getConfigurations(), descriptorInternal.getModule());
-        IvyModuleDescriptorWriter ivyModuleDescriptorWriter = publicationServices.getIvyModuleDescriptorWriter();
-        ivyModuleDescriptorWriter.write(moduleDescriptor, getDestination(), xmlTransformer);
-    }
+        for (IvyDependencyInternal ivyDependency : descriptorInternal.getDependencies()) {
+            ivyGenerator.addDependency(ivyDependency);
+        }
 
+        ivyGenerator.withXml(descriptorInternal.getXmlAction()).writeTo(getDestination());
+    }
 
     private static IvyModuleDescriptorInternal toIvyModuleDescriptorInternal(IvyModuleDescriptor ivyModuleDescriptor) {
         if (ivyModuleDescriptor == null) {
@@ -143,40 +125,4 @@ public class GenerateIvyDescriptor extends DefaultTask {
         }
     }
 
-    private class IvyDescriptorArtifact implements PublishArtifact {
-        private final DefaultTaskDependency dependency;
-
-        public IvyDescriptorArtifact() {
-            this.dependency = new DefaultTaskDependency();
-            this.dependency.add(GenerateIvyDescriptor.this);
-        }
-
-        public String getName() {
-            return "ivy";
-        }
-
-        public String getExtension() {
-            return "xml";
-        }
-
-        public String getType() {
-            return "xml";
-        }
-
-        public String getClassifier() {
-            return null;
-        }
-
-        public File getFile() {
-            return getDestination();
-        }
-
-        public Date getDate() {
-            return null;
-        }
-
-        public TaskDependency getBuildDependencies() {
-            return dependency;
-        }
-    }
 }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java
index 58beabe..9eea44b 100644
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java
+++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/PublishToIvyRepository.java
@@ -21,13 +21,15 @@ import org.gradle.api.Incubating;
 import org.gradle.api.InvalidUserDataException;
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
-import org.gradle.api.internal.artifacts.ArtifactPublisher;
+import org.gradle.api.internal.Cast;
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository;
 import org.gradle.api.publish.internal.PublishOperation;
 import org.gradle.api.publish.ivy.IvyPublication;
-import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication;
-import org.gradle.api.publish.ivy.internal.IvyPublicationInternal;
-import org.gradle.api.publish.ivy.internal.IvyPublisher;
+import org.gradle.api.publish.ivy.internal.publisher.DependencyResolverIvyPublisher;
+import org.gradle.api.publish.ivy.internal.publisher.IvyNormalizedPublication;
+import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal;
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublisher;
+import org.gradle.api.publish.ivy.internal.publisher.ValidatingIvyPublisher;
 import org.gradle.api.tasks.TaskAction;
 
 import javax.inject.Inject;
@@ -44,11 +46,8 @@ public class PublishToIvyRepository extends DefaultTask {
     private IvyPublicationInternal publication;
     private IvyArtifactRepository repository;
 
-    private final ArtifactPublicationServices publicationServices;
-
     @Inject
-    public PublishToIvyRepository(ArtifactPublicationServices publicationServices) {
-        this.publicationServices = publicationServices;
+    public PublishToIvyRepository() {
 
         // Allow the publication to participate in incremental build
         getInputs().files(new Callable<FileCollection>() {
@@ -140,10 +139,10 @@ public class PublishToIvyRepository extends DefaultTask {
         new PublishOperation(publication, repository) {
             @Override
             protected void publish() throws Exception {
-                ArtifactPublisher artifactPublisher = publicationServices.createArtifactPublisher();
                 IvyNormalizedPublication normalizedPublication = publication.asNormalisedPublication();
-                IvyPublisher publisher = new IvyPublisher(artifactPublisher);
-                publisher.publish(normalizedPublication, repository);
+                IvyPublisher publisher = new DependencyResolverIvyPublisher();
+                publisher = new ValidatingIvyPublisher(publisher);
+                publisher.publish(normalizedPublication, Cast.cast(PublicationAwareRepository.class, repository));
             }
         }.run();
     }
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreator.java
deleted file mode 100644
index 9ac3469..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreator.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.tasks.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.Project;
-import org.gradle.api.internal.ConventionMapping;
-import org.gradle.api.internal.plugins.DslObject;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.ivy.internal.IvyPublicationInternal;
-import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor;
-
-import java.io.File;
-import java.util.concurrent.Callable;
-
-import static org.apache.commons.lang.StringUtils.capitalize;
-
-public class IvyPublicationDynamicDescriptorGenerationTaskCreator {
-
-    private final Project project;
-
-    public IvyPublicationDynamicDescriptorGenerationTaskCreator(Project project) {
-        this.project = project;
-    }
-
-    public void monitor(PublicationContainer publications) {
-        publications.withType(IvyPublicationInternal.class).all(new Action<IvyPublicationInternal>() {
-            public void execute(IvyPublicationInternal publication) {
-                create(publication);
-            }
-        });
-    }
-
-    private void create(final IvyPublicationInternal publication) {
-        String publicationName = publication.getName();
-
-        String descriptorTaskName = calculateDescriptorTaskName(publicationName);
-        GenerateIvyDescriptor descriptorTask = project.getTasks().add(descriptorTaskName, GenerateIvyDescriptor.class);
-        descriptorTask.setGroup("publishing");
-        descriptorTask.setDescription(String.format("Generates the Ivy Module Descriptor XML file for publication '%s'", publication.getName()));
-
-        ConventionMapping descriptorTaskConventionMapping = new DslObject(descriptorTask).getConventionMapping();
-        descriptorTaskConventionMapping.map("destination", new Callable<Object>() {
-            public Object call() throws Exception {
-                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/ivy.xml");
-            }
-        });
-        descriptorTaskConventionMapping.map("descriptor", new Callable<Object>() {
-            public Object call() throws Exception {
-                return publication.getDescriptor();
-            }
-        });
-        descriptorTaskConventionMapping.map("xmlAction", new Callable<Object>() {
-            public Object call() throws Exception {
-                return publication.getDescriptor().getXmlAction();
-            }
-        });
-
-        publication.setDescriptorArtifact(descriptorTask.getDescriptorArtifact());
-    }
-
-    private String calculateDescriptorTaskName(String publicationName) {
-        return String.format(
-                "generate%sIvyModuleDescriptor",
-                publicationName.toLowerCase().equals("ivy") ? "" : capitalize(publicationName)
-        );
-    }
-
-}
diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java
deleted file mode 100644
index 5d70cd3..0000000
--- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreator.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.tasks.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectList;
-import org.gradle.api.NamedDomainObjectSet;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.ArtifactRepositoryContainer;
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.ivy.internal.IvyPublicationInternal;
-import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository;
-import org.gradle.api.tasks.TaskContainer;
-
-import static org.apache.commons.lang.StringUtils.capitalize;
-
-/**
- * Dynamically creates tasks for each Ivy publication/repository pair in a publication set and repository set.
- */
-public class IvyPublishDynamicTaskCreator {
-
-    final private TaskContainer tasks;
-    private final Task publishLifecycleTask;
-
-    public IvyPublishDynamicTaskCreator(TaskContainer tasks, Task publishLifecycleTask) {
-        this.tasks = tasks;
-        this.publishLifecycleTask = publishLifecycleTask;
-    }
-
-    public void monitor(final PublicationContainer publications, final ArtifactRepositoryContainer repositories) {
-        final NamedDomainObjectSet<IvyPublicationInternal> ivyPublications = publications.withType(IvyPublicationInternal.class);
-        final NamedDomainObjectList<IvyArtifactRepository> ivyRepositories = repositories.withType(IvyArtifactRepository.class);
-
-        ivyPublications.all(new Action<IvyPublicationInternal>() {
-            public void execute(IvyPublicationInternal publication) {
-                for (IvyArtifactRepository repository : ivyRepositories) {
-                    maybeCreate(publication, repository);
-                }
-            }
-        });
-
-        ivyRepositories.all(new Action<IvyArtifactRepository>() {
-            public void execute(IvyArtifactRepository repository) {
-                for (IvyPublicationInternal publication : ivyPublications) {
-                    maybeCreate(publication, repository);
-                }
-            }
-        });
-
-        // Note: we aren't supporting removal of repositories or publications
-        // Note: we also aren't considering that repos have a setName, so their name can change
-        //       (though this is a violation of the Named contract)
-    }
-
-    private void maybeCreate(IvyPublicationInternal publication, IvyArtifactRepository repository) {
-        String publicationName = publication.getName();
-        String repositoryName = repository.getName();
-
-        String publishTaskName = calculatePublishTaskName(publicationName, repositoryName);
-        if (tasks.findByName(publishTaskName) == null) {
-            PublishToIvyRepository publishTask = tasks.add(publishTaskName, PublishToIvyRepository.class);
-            publishTask.setPublication(publication);
-            publishTask.setRepository(repository);
-            publishTask.setGroup("publishing");
-            publishTask.setDescription(String.format("Publishes Ivy publication '%s' to Ivy repository '%s'", publicationName, repositoryName));
-
-            publishLifecycleTask.dependsOn(publishTask);
-        }
-    }
-
-    private String calculatePublishTaskName(String publicationName, String repositoryName) {
-        return String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
-    }
-
-}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy
deleted file mode 100644
index cc193b4..0000000
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/DefaultIvyPublicationTest.groovy
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.internal
-import org.gradle.api.Task
-import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.PublishArtifact
-import org.gradle.api.internal.project.ProjectInternal
-import org.gradle.api.plugins.JavaBasePlugin
-import org.gradle.api.tasks.TaskDependency
-import org.gradle.api.tasks.bundling.Jar
-import org.gradle.internal.reflect.Instantiator
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class DefaultIvyPublicationTest extends Specification {
-
-    ProjectInternal project = HelperUtil.createRootProject()
-    def taskDependencies = Mock(TaskDependency)
-
-    DefaultIvyPublication publication(String name = "ivy", Configuration... configurations) {
-        Instantiator instantiator = project.getServices().get(Instantiator)
-        instantiator.newInstance(DefaultIvyPublication, name, instantiator, configurations as Set, project, project.fileResolver, project.tasks)
-    }
-
-    def "publishable files are the artifact files"() {
-        when:
-        def file1 = project.file("file1")
-        def descriptorFile1 = project.file("ivy1.xml")
-
-        project.configurations { conf1 }
-        project.artifacts { conf1 file1 }
-        def p = publication(project.configurations.conf1)
-        p.descriptorArtifact = descriptorArtifact(descriptorFile1)
-
-        then:
-        p.publishableFiles.files == [file1, descriptorFile1] as Set
-
-        when:
-        def file2 = project.file("file2")
-        def descriptorFile2 = project.file("ivy2.xml")
-        project.configurations { conf2 }
-        project.artifacts { conf2 file2 }
-        p = publication(project.configurations.conf1, project.configurations.conf2)
-        p.descriptorArtifact = descriptorArtifact(descriptorFile2)
-
-        then:
-        p.publishableFiles.files == [file1, file2, descriptorFile2] as Set
-    }
-
-    def "publication is built by what builds the artifacts and descriptor"() {
-        given:
-        project.plugins.apply(JavaBasePlugin)
-        Task dummyTask = project.task("dummyTask")
-
-        when:
-        def task1 = project.tasks.add("task1", Jar)
-        task1.baseName = "task1"
-        project.configurations { conf1 }
-        project.artifacts { conf1 task1 }
-        def p = publication(project.configurations.conf1)
-
-        then:
-        p.publishableFiles.buildDependencies.getDependencies(dummyTask) == [task1] as Set
-
-        when:
-        def task2 = project.tasks.add("task2", Jar)
-        project.configurations { conf2 }
-        project.artifacts { conf2 task2 }
-        p = publication(project.configurations.conf1, project.configurations.conf2)
-
-        then:
-        p.publishableFiles.buildDependencies.getDependencies(dummyTask) == [task1, task2] as Set
-
-        when:
-        def task3 = project.tasks.add("task3")
-        p.descriptorArtifact = descriptorArtifact(project.file("ivy.xml"))
-        1 * taskDependencies.getDependencies(_) >> [task3]
-
-        then:
-        p.publishableFiles.buildDependencies.getDependencies(dummyTask) == [task1, task2, task3] as Set
-    }
-
-    def "can get publishable files when no descriptor file set"() {
-        when:
-        def file1 = project.file("file1")
-        project.configurations { conf1 }
-        project.artifacts { conf1 file1 }
-        def p = publication(project.configurations.conf1)
-
-        then:
-        p.publishableFiles.files == [file1] as Set
-    }
-
-    PublishArtifact descriptorArtifact(File descriptorFile) {
-        Mock(PublishArtifact) {
-            getFile() >> descriptorFile
-            getBuildDependencies() >> taskDependencies
-        }
-    }
-}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactoryTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactoryTest.groovy
new file mode 100644
index 0000000..0f309cc
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/artifact/IvyArtifactNotationParserFactoryTest.groovy
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.artifact
+
+import org.gradle.api.Task
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.publish.ivy.IvyArtifact
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+public class IvyArtifactNotationParserFactoryTest extends Specification {
+    Instantiator instantiator = new DirectInstantiator()
+    def fileNotationParser = Mock(NotationParser)
+    def taskDependency = Mock(TaskDependency)
+    def publishArtifact = Stub(PublishArtifact) {
+        getName() >> 'name'
+        getExtension() >> 'extension'
+        getType() >> 'type'
+        getFile() >> new File('foo')
+        getBuildDependencies() >> taskDependency
+    }
+    def task = Mock(Task)
+    def dependencies = Collections.singleton(Mock(Task))
+
+    NotationParser<IvyArtifact> parser
+
+    def "setup"() {
+        def fileResolver = Stub(FileResolver) {
+            asNotationParser() >> fileNotationParser
+        }
+        def identity = Stub(IvyPublicationIdentity) {
+            getModule() >> 'pub-name'
+        }
+        parser = new IvyArtifactNotationParserFactory(instantiator, fileResolver, identity).create()
+    }
+
+    def "directly returns IvyArtifact input"() {
+        when:
+        def ivyArtifact = Mock(IvyArtifact)
+
+        then:
+        parser.parseNotation(ivyArtifact) == ivyArtifact
+    }
+
+    def "creates IvyArtifact for PublishArtifact"() {
+        when:
+        def ivyArtifact = parser.parseNotation(publishArtifact)
+
+        then:
+        ivyArtifact.name == 'pub-name'
+        ivyArtifact.extension == publishArtifact.extension
+        ivyArtifact.type == publishArtifact.type
+        ivyArtifact.file == publishArtifact.file
+
+        when:
+        taskDependency.getDependencies(task) >> dependencies
+
+        then:
+        ivyArtifact.buildDependencies.getDependencies(task) == dependencies
+    }
+
+    def "creates IvyArtifact for source map notation"() {
+        when:
+        IvyArtifact ivyArtifact = parser.parseNotation(source: publishArtifact)
+
+        then:
+        ivyArtifact.name == 'pub-name'
+        ivyArtifact.extension == publishArtifact.extension
+        ivyArtifact.type == publishArtifact.type
+        ivyArtifact.file == publishArtifact.file
+
+        when:
+        taskDependency.getDependencies(task) >> dependencies
+
+        then:
+        ivyArtifact.buildDependencies.getDependencies(task) == dependencies
+    }
+
+    def "creates IvyArtifact for source map notation with file"() {
+        given:
+        File file = new File('some-file-1.2.zip')
+
+        when:
+        def ivyArtifact = parser.parseNotation(source: 'some-file')
+
+        then:
+        fileNotationParser.parseNotation('some-file') >> file
+
+        and:
+        ivyArtifact.name == 'pub-name'
+        ivyArtifact.extension == "zip"
+        ivyArtifact.type == "zip"
+        ivyArtifact.file == file
+    }
+
+
+    def "creates and configures IvyArtifact for source map notation"() {
+        when:
+        IvyArtifact ivyArtifact = parser.parseNotation(source: publishArtifact, name: 'the-name', extension: "the-ext", type: "the-type")
+
+        then:
+        ivyArtifact.file == publishArtifact.file
+        ivyArtifact.name == "the-name"
+        ivyArtifact.extension == "the-ext"
+        ivyArtifact.type == "the-type"
+
+        when:
+        taskDependency.getDependencies(task) >> dependencies
+
+        then:
+        ivyArtifact.buildDependencies.getDependencies(task) == dependencies
+    }
+
+    def "creates IvyArtifact for ArchivePublishArtifact"() {
+        when:
+        def rootProject = HelperUtil.createRootProject()
+        def archive = rootProject.task(type: Jar, {})
+        archive.setBaseName("base-name")
+        archive.setExtension('extension')
+
+        IvyArtifact ivyArtifact = parser.parseNotation(archive)
+
+        then:
+        ivyArtifact.name == 'pub-name'
+        ivyArtifact.extension == "extension"
+        ivyArtifact.classifier == null
+        ivyArtifact.file == archive.archivePath
+        ivyArtifact.buildDependencies.getDependencies(null) == [archive] as Set
+    }
+
+    def "creates IvyArtifact for file notation"() {
+        given:
+        File file = new File(fileName)
+
+        when:
+        IvyArtifact ivyArtifact = parser.parseNotation('some-file')
+
+        then:
+        fileNotationParser.parseNotation('some-file') >> file
+
+        and:
+        ivyArtifact.name == 'pub-name'
+        ivyArtifact.extension == extension
+        ivyArtifact.type == type
+        ivyArtifact.classifier == null
+        ivyArtifact.file == file
+
+        where:
+        fileName                       | extension | type
+        "some-file-1.2.zip"            | "zip"     | "zip"
+        "some-file"                    | ""        | ""
+        "some-file-1.2-classifier.zip" | "zip"     | "zip"
+    }
+
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy
new file mode 100644
index 0000000..0c5113a
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.plugins
+import org.gradle.api.publish.Publication
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.ivy.IvyModuleDescriptor
+import org.gradle.api.publish.ivy.IvyPublication
+import org.gradle.api.publish.ivy.internal.publication.IvyModuleDescriptorInternal
+import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal
+import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor
+import org.gradle.api.publish.plugins.PublishingPlugin
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class IvyPublicationDynamicDescriptorGenerationTaskCreatorTest extends Specification {
+
+    def project = HelperUtil.createRootProject()
+    def creator = new IvyPublicationDynamicDescriptorGenerationTaskCreator(project)
+    PublishingExtension publishing
+
+    def setup() {
+        project.plugins.apply(PublishingPlugin)
+        publishing = project.extensions.getByType(PublishingExtension)
+        creator.monitor(publishing.publications)
+    }
+
+    def "creates tasks"() {
+        expect:
+        descriptorGeneratorTasks.size() == 0
+
+        when:
+        publishing.repositories.ivy { }
+        publishing.publications.add(publication("foo"))
+
+        then:
+        descriptorGeneratorTasks.size() == 0
+
+        when:
+        publishing.publications.add(ivyPublication("ivy"))
+
+        then:
+        descriptorGeneratorTasks.size() == 1
+        GenerateIvyDescriptor task = project.tasks.generateIvyModuleDescriptor
+        task.description != null
+
+        when:
+        publishing.publications.add(ivyPublication("other"))
+
+        then:
+        descriptorGeneratorTasks.size() == 2
+        def task2 = project.tasks.generateOtherIvyModuleDescriptor
+        task2
+    }
+
+    Publication publication(String name) {
+        Mock(Publication) {
+            getName() >> name
+        }
+    }
+
+    IvyPublication ivyPublication(String name) {
+        IvyModuleDescriptor moduleDescriptor = Mock(IvyModuleDescriptorInternal)
+        Mock(IvyPublicationInternal) {
+            getName() >> name
+            getDescriptor() >> moduleDescriptor
+            1 * setDescriptorFile({it.singleFile.path.contains name})
+        }
+    }
+
+    def getDescriptorGeneratorTasks() {
+        project.tasks.withType(GenerateIvyDescriptor)
+    }
+
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreatorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreatorTest.groovy
new file mode 100644
index 0000000..7d5df98
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/plugins/IvyPublishDynamicTaskCreatorTest.groovy
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.plugins
+import org.gradle.api.Task
+import org.gradle.api.publish.Publication
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.ivy.IvyPublication
+import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal
+import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository
+import org.gradle.api.publish.plugins.PublishingPlugin
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class IvyPublishDynamicTaskCreatorTest extends Specification {
+
+    def project = HelperUtil.createRootProject()
+    def lifecycleTask = project.task("pl")
+    def creator = new IvyPublishDynamicTaskCreator(project.tasks, lifecycleTask)
+
+    PublishingExtension publishing
+
+    def setup() {
+        project.plugins.apply(PublishingPlugin)
+        publishing = project.extensions.getByType(PublishingExtension)
+        creator.monitor(publishing.publications, publishing.repositories)
+    }
+
+    def "creates tasks"() {
+        expect:
+        ivyPublishTasks.size() == 0
+
+        when:
+        publishing.repositories.ivy { }
+        publishing.publications.add(publication("foo"))
+
+        then:
+        ivyPublishTasks.size() == 0
+        lifecycleTaskDependencies.empty
+
+        when:
+        publishing.publications.add(ivyPublication("ivy"))
+
+        then:
+        ivyPublishTasks.size() == 1
+        project.tasks["publishIvyPublicationToIvyRepository"] != null
+        PublishToIvyRepository task = project.tasks.publishIvyPublicationToIvyRepository
+        task.group == "publishing"
+        task.description != null
+
+        lifecycleTaskDependencies == [task] as Set
+
+        when:
+        publishing.publications.add(ivyPublication("ivy2"))
+
+        then:
+        ivyPublishTasks.size() == 2
+        project.tasks["publishIvy2PublicationToIvyRepository"] != null
+        lifecycleTaskDependencies.size() == 2
+
+        when:
+        publishing.repositories.ivy {}
+
+        then:
+        lifecycleTaskDependencies.size() == 4
+        ivyPublishTasks.size() == 4
+        project.tasks["publishIvyPublicationToIvy2Repository"] != null
+        project.tasks["publishIvy2PublicationToIvy2Repository"] != null
+    }
+
+    protected Set<? extends Task> getLifecycleTaskDependencies() {
+        lifecycleTask.taskDependencies.getDependencies(lifecycleTask)
+    }
+
+    def getIvyPublishTasks() {
+        project.tasks.withType(PublishToIvyRepository)
+    }
+
+    Publication publication(String name) {
+        Mock(Publication) {
+            getName() >> name
+        }
+    }
+
+    IvyPublication ivyPublication(String name) {
+        Mock(IvyPublicationInternal) {
+            getName() >> name
+        }
+    }
+
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationTest.groovy
new file mode 100644
index 0000000..a3cc01c
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublicationTest.groovy
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publication
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.DependencySet
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.artifacts.PublishArtifactSet
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.ClassGeneratorBackedInstantiator
+import org.gradle.api.internal.component.SoftwareComponentInternal
+import org.gradle.api.internal.component.Usage
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.publish.ivy.IvyArtifact
+import org.gradle.api.publish.ivy.internal.publisher.IvyPublicationIdentity
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import spock.lang.Shared
+import spock.lang.Specification
+
+class DefaultIvyPublicationTest extends Specification {
+
+    @Shared TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+    Instantiator instantiator = new ClassGeneratorBackedInstantiator(new AsmBackedClassGenerator(), new DirectInstantiator())
+    def projectIdentity = Mock(IvyPublicationIdentity)
+    def notationParser = Mock(NotationParser)
+    File descriptorFile
+    File artifactFile
+
+    def "setup"() {
+        descriptorFile = new File(testDirectoryProvider.testDirectory, "pom-file")
+        artifactFile = new File(testDirectoryProvider.testDirectory, "artifact-file")
+        artifactFile << "some content"
+    }
+
+    def "name property is passed through"() {
+        when:
+        def publication = createPublication()
+
+        then:
+        publication.name == "pub-name"
+    }
+
+    def "empty publishableFiles and artifacts when no component is added"() {
+        when:
+        def publication = createPublication()
+
+        then:
+        publication.artifacts.empty
+        publication.publishableFiles.files == [descriptorFile] as Set
+        publication.dependencies.empty
+    }
+
+    def "adopts configurations, artifacts and publishableFiles from added component"() {
+        given:
+        def publication = createPublication()
+
+        def component = Mock(SoftwareComponentInternal)
+        def usage1 = Mock(Usage)
+        def artifact = Mock(PublishArtifact)
+        def ivyArtifact = createArtifact()
+
+        when:
+        component.usages >> [usage1]
+        usage1.name >> "runtime"
+        usage1.artifacts >> [artifact]
+        usage1.dependencies >> []
+
+        notationParser.parseNotation(artifact) >> ivyArtifact
+        1 * ivyArtifact.setConf("runtime")
+
+        and:
+        publication.from(component)
+
+        then:
+        publication.publishableFiles.files == [descriptorFile, artifactFile] as Set
+        publication.artifacts == [ivyArtifact] as Set
+
+        and:
+        publication.configurations.size() == 2
+        publication.configurations.runtime.extends == [] as Set
+        publication.configurations."default".extends == ["runtime"] as Set
+        
+        publication.dependencies.empty
+    }
+
+    def "adopts dependencies from added component"() {
+        given:
+        def publication = createPublication()
+
+        def component = Mock(SoftwareComponentInternal)
+        def usage1 = Mock(Usage)
+        def moduleDependency = Mock(ModuleDependency)
+
+        when:
+        component.usages >> [usage1]
+        usage1.name >> "runtime"
+        usage1.artifacts >> []
+        usage1.dependencies >> [moduleDependency]
+
+        moduleDependency.configuration >> "dep-configuration"
+
+        and:
+        publication.from(component)
+
+        then:
+        publication.publishableFiles.files == [descriptorFile] as Set
+        publication.artifacts.empty
+        and:
+        publication.dependencies.size() == 1
+        def ivyDependency = publication.dependencies.asList().first()
+        ivyDependency.moduleDependency == moduleDependency
+        ivyDependency.confMapping == "runtime->dep-configuration"
+    }
+
+    def "cannot add multiple components"() {
+        given:
+        def publication = createPublication()
+        def component = Mock(SoftwareComponentInternal)
+        def usage = Mock(Usage)
+        def publishArtifactSet = Mock(PublishArtifactSet)
+        def dependencySet = Mock(DependencySet)
+
+        when:
+        publication.from(component)
+
+        then:
+        component.usages >> [usage]
+        usage.name >> "runtime"
+        usage.artifacts >> publishArtifactSet
+        publishArtifactSet.iterator() >> [].iterator()
+        usage.dependencies >> dependencySet
+        dependencySet.iterator() >> [].iterator()
+
+        when:
+        publication.from(Mock(SoftwareComponentInternal))
+
+        then:
+        def e = thrown(InvalidUserDataException)
+        e.message == "Ivy publication 'pub-name' cannot include multiple components"
+    }
+
+    def "creates configuration on first access"() {
+        def publication = createPublication()
+
+        when:
+        publication.configurations {
+            newConfiguration {}
+        };
+
+        then:
+        publication.configurations.size() == 1
+        publication.configurations.getByName("newConfiguration").name == "newConfiguration"
+    }
+
+    def "attaches artifacts parsed by notation parser to configuration"() {
+        given:
+        def publication = createPublication()
+        def notation = new Object();
+        def ivyArtifact = createArtifact()
+
+        when:
+        notationParser.parseNotation(notation) >> ivyArtifact
+
+        and:
+        publication.artifact notation
+
+        then:
+        publication.artifacts == [ivyArtifact] as Set
+        publication.publishableFiles.files == [descriptorFile, artifactFile] as Set
+    }
+
+    def "attaches and configures artifacts parsed by notation parser"() {
+        given:
+        def publication = createPublication()
+        def notation = new Object();
+        def ivyArtifact = createArtifact()
+
+        when:
+        notationParser.parseNotation(notation) >> ivyArtifact
+        1 * ivyArtifact.setExtension('changed')
+        0 * ivyArtifact._
+
+        and:
+        publication.artifact(notation) {
+            extension = 'changed'
+        }
+
+        then:
+        publication.artifacts == [ivyArtifact] as Set
+        publication.publishableFiles.files == [descriptorFile, artifactFile] as Set
+    }
+
+    def "can use setter to replace existing artifacts set on configuration"() {
+        given:
+        def publication = createPublication()
+        def ivyArtifact1 = createArtifact()
+        def ivyArtifact2 = createArtifact()
+
+        when:
+        publication.artifact "notation"
+
+        then:
+        notationParser.parseNotation("notation") >> Mock(IvyArtifact)
+
+        when:
+        publication.artifacts = ["notation1", "notation2"]
+
+        then:
+        notationParser.parseNotation("notation1") >> ivyArtifact1
+        notationParser.parseNotation("notation2") >> ivyArtifact2
+
+        and:
+        publication.artifacts == [ivyArtifact1, ivyArtifact2] as Set
+    }
+
+    def createPublication() {
+        def publication = instantiator.newInstance(DefaultIvyPublication, "pub-name", instantiator, projectIdentity, notationParser)
+        publication.setDescriptorFile(new SimpleFileCollection(descriptorFile))
+        return publication;
+    }
+
+    def createArtifact() {
+        IvyArtifact artifact = Mock(IvyArtifact) {
+            getFile() >> artifactFile
+        }
+        return artifact
+    }
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGeneratorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGeneratorTest.groovy
new file mode 100644
index 0000000..9b88a47
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/IvyDescriptorFileGeneratorTest.groovy
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publisher
+import org.gradle.api.Action
+import org.gradle.api.Project
+import org.gradle.api.XmlProvider
+import org.gradle.api.artifacts.DependencyArtifact
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.api.publish.ivy.internal.artifact.DefaultIvyArtifact
+import org.gradle.api.publish.ivy.internal.dependency.DefaultIvyDependency
+import org.gradle.api.publish.ivy.internal.publication.DefaultIvyConfiguration
+import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublicationIdentity
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.CollectionUtils
+import org.gradle.util.TextUtil
+import spock.lang.Specification
+
+class IvyDescriptorFileGeneratorTest extends Specification {
+    TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+    def projectIdentity = new DefaultIvyPublicationIdentity("my-org", "my-name", "my-version")
+    IvyDescriptorFileGenerator generator = new IvyDescriptorFileGenerator(projectIdentity)
+
+    def "writes correct prologue and schema declarations"() {
+        expect:
+        ivyFile.text.startsWith(TextUtil.toPlatformLineSeparators(
+"""<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0">
+"""))
+    }
+
+    def "writes empty descriptor with module values"() {
+        expect:
+        with (ivyXml) {
+            info. at organisation == "my-org"
+            info. at module == "my-name"
+            info. at revision == "my-version"
+            info. at status.isEmpty()
+            configurations.size() == 1
+            configurations.conf.isEmpty()
+            publications.size() == 1
+            publications.artifacts.isEmpty()
+            dependencies.size() == 1
+            dependencies.dependency.isEmpty()
+        }
+    }
+
+    def "encodes coordinates for XML and unicode"() {
+        when:
+        def projectIdentity = new DefaultIvyPublicationIdentity('org-ぴ₦ガき∆ç√∫', 'module-<tag attrib="value"/>-markup', 'version-&"')
+        generator = new IvyDescriptorFileGenerator(projectIdentity)
+
+
+        then:
+        with (ivyXml) {
+            info. at organisation == 'org-ぴ₦ガき∆ç√∫'
+            info. at module == 'module-<tag attrib="value"/>-markup'
+            info. at revision == 'version-&"'
+        }
+    }
+
+    def "writes supplied status"() {
+        when:
+        generator.setStatus("my-status")
+
+        then:
+        ivyXml.info. at status == "my-status"
+    }
+
+    def "writes supplied configurations"() {
+        when:
+        def config1 = new DefaultIvyConfiguration("config1")
+        def config2 = new DefaultIvyConfiguration("config2")
+        config1.extend("foo")
+        config1.extend("bar")
+        generator.addConfiguration(config1)
+        generator.addConfiguration(config2)
+
+        then:
+        with (ivyXml) {
+            configurations.conf.size() == 2
+            with (configurations[0].conf[0]) {
+                it. at name == "config1"
+                it. at extends == "foo,bar"
+            }
+            with (configurations[0].conf[1]) {
+                it. at name == "config2"
+                it. at extends.empty
+            }
+        }
+    }
+
+    def "writes supplied publication artifacts"() {
+        when:
+        def artifact1 = new DefaultIvyArtifact(null, "artifact1", "ext1", "type1", "classy")
+        def artifact2 = new DefaultIvyArtifact(null, null, "", null, null)
+        artifact2.setConf("runtime")
+        generator.addArtifact(artifact1)
+        generator.addArtifact(artifact2)
+
+        then:
+        includesMavenNamespace()
+        and:
+        with (ivyXml) {
+            publications.artifact.size() == 2
+            with (publications[0].artifact[0]) {
+                it. at name == "artifact1"
+                it. at type == "type1"
+                it. at ext == "ext1"
+                it. at classifier == "classy"
+                it. at conf.isEmpty()
+            }
+            with (publications[0].artifact[1]) {
+                it. at name.isEmpty()
+                it. at type.isEmpty()
+                it. at ext == ""
+                it. at classifier.isEmpty()
+                it. at conf == "runtime"
+            }
+        }
+    }
+    def "writes supplied dependencies"() {
+        def projectDependency = Mock(ProjectDependency)
+        def moduleDependency = Mock(ModuleDependency)
+        when:
+        projectDependency.artifacts >> new HashSet<DependencyArtifact>()
+        projectDependency.group >> "dep-group"
+        projectDependency.name >> "dep-name-1"
+        projectDependency.version >> "dep-version"
+        projectDependency.dependencyProject >> Stub(Project) {
+            getName() >> "project-name"
+        }
+
+        and:
+        moduleDependency.artifacts >> new HashSet<DependencyArtifact>()
+        moduleDependency.group >> "dep-group"
+        moduleDependency.name >> "dep-name-2"
+        moduleDependency.version >> "dep-version"
+
+        and:
+        generator.addDependency(new DefaultIvyDependency(projectDependency, "confMappingProject"))
+        generator.addDependency(new DefaultIvyDependency(moduleDependency, null))
+
+        then:
+        with (ivyXml) {
+            dependencies.dependency.size() == 2
+            with (dependencies[0].dependency[0]) {
+                it. at org == "dep-group"
+                it. at name == "project-name"
+                it. at rev == "dep-version"
+                it. at conf == "confMappingProject"
+            }
+            with (dependencies[0].dependency[1]) {
+                it. at org == "dep-group"
+                it. at name == "dep-name-2"
+                it. at rev == "dep-version"
+                it. at conf.isEmpty()
+            }
+        }
+    }
+
+    def "writes dependency with artifacts"() {
+        def dependency = Mock(ModuleDependency)
+        def artifact1 = Mock(DependencyArtifact)
+        def artifact2 = Mock(DependencyArtifact)
+
+        when:
+        dependency.artifacts >> CollectionUtils.toSet([artifact1, artifact2])
+        dependency.group >> "dep-group"
+        dependency.name >> "dep-name"
+        dependency.version >> "dep-version"
+        artifact1.name >> "artifact-1"
+        artifact1.type >> "type-1"
+        artifact1.extension >> "ext-1"
+        artifact1.classifier >> null
+        artifact2.name >> "artifact-2"
+        artifact2.type >> null
+        artifact2.classifier >> "classy"
+
+        and:
+        generator.addDependency(new DefaultIvyDependency(dependency, "confMapping"))
+
+        then:
+        includesMavenNamespace()
+
+        and:
+        with (ivyXml) {
+            dependencies.dependency.size() == 1
+            with (dependencies[0].dependency[0]) {
+                it. at org == "dep-group"
+                it. at name == "dep-name"
+                it. at rev == "dep-version"
+                it. at conf == "confMapping"
+
+                artifact.size() == 2
+                with (artifact[0]) {
+                    it. at name == "artifact-1"
+                    it. at type == "type-1"
+                    it. at ext == "ext-1"
+                    it. at classifier.isEmpty()
+                }
+                with (artifact[1]) {
+                    it. at name == "artifact-2"
+                    it. at type.isEmpty()
+                    it. at ext.isEmpty()
+                    it. at classifier == "classy"
+                }
+            }
+        }
+    }
+
+    def "applies withXml actions"() {
+        when:
+        generator.withXml(new Action<XmlProvider>() {
+            void execute(XmlProvider t) {
+                t.asNode().info[0]. at revision = "3"
+            }
+        })
+        generator.withXml(new Action<XmlProvider>() {
+            void execute(XmlProvider t) {
+                t.asNode().info[0].appendNode("description", "custom-description-ぴ₦ガき∆ç√∫")
+            }
+        })
+
+        then:
+        with (ivyXml) {
+            info. at organisation == "my-org"
+            info. at revision == "3"
+            info.description == "custom-description-ぴ₦ガき∆ç√∫"
+        }
+    }
+
+    private void includesMavenNamespace() {
+        assert ivyFile.text.startsWith(TextUtil.toPlatformLineSeparators(
+                """<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="2.0" xmlns:m="http://ant.apache.org/ivy/maven">
+"""))
+    }
+
+    private def getIvyXml() {
+        return new XmlSlurper().parse(ivyFile);
+    }
+
+    private TestFile getIvyFile() {
+        def ivyFile = testDirectoryProvider.testDirectory.file("ivy.xml")
+        generator.writeTo(ivyFile)
+        return ivyFile
+    }
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisherTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisherTest.groovy
new file mode 100644
index 0000000..ab19830
--- /dev/null
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/internal/publisher/ValidatingIvyPublisherTest.groovy
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.ivy.internal.publisher
+import org.gradle.api.Action
+import org.gradle.api.XmlProvider
+import org.gradle.api.internal.artifacts.repositories.PublicationAwareRepository
+import org.gradle.api.publish.ivy.InvalidIvyPublicationException
+import org.gradle.api.publish.ivy.IvyArtifact
+import org.gradle.api.publish.ivy.internal.artifact.DefaultIvyArtifact
+import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublicationIdentity
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static java.util.Collections.emptySet
+import static org.gradle.util.CollectionUtils.toSet
+
+public class ValidatingIvyPublisherTest extends Specification {
+    @Shared TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider()
+    def delegate = Mock(IvyPublisher)
+    def publisher = new ValidatingIvyPublisher(delegate)
+
+    def "delegates when publication is valid"() {
+        when:
+        def projectIdentity = this.projectIdentity("the-group", "the-artifact", "the-version")
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity, ivyFile("the-group", "the-artifact", "the-version"), emptySet())
+        def repository = Mock(PublicationAwareRepository)
+
+        and:
+        publisher.publish(publication, repository)
+
+        then:
+        delegate.publish(publication, repository)
+    }
+
+    def "does not attempt to resolve extended ivy descriptor when validating"() {
+        when:
+        def ivyFile = ivyFile("the-group", "the-artifact", "the-version", new Action<XmlProvider>() {
+            void execute(XmlProvider t) {
+                t.asNode().info[0].appendNode("extends", ["organisation": "parent-org", "module": "parent-module", "revision": "parent-revision"])
+            }
+        })
+
+        and:
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity("the-group", "the-artifact", "the-version"), ivyFile, emptySet())
+        def repository = Mock(PublicationAwareRepository)
+
+        and:
+        publisher.publish(publication, repository)
+
+        then:
+        delegate.publish(publication, repository)
+    }
+
+    def "validates project coordinates"() {
+        given:
+        def projectIdentity = projectIdentity(group, name, version)
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity, ivyFile(group, name, version), emptySet())
+        def repository = Mock(PublicationAwareRepository)
+
+        when:
+        publisher.publish(publication, repository)
+
+        then:
+        def e = thrown InvalidIvyPublicationException
+        e.message == "Invalid publication 'pub-name': $message."
+
+        where:
+        group          | name       | version         | message
+        ""             | "module"   | "version"       | "organisation cannot be empty"
+        "organisation" | ""         | "version"       | "module name cannot be empty"
+        "organisation" | "module"   | ""              | "revision cannot be empty"
+        null           | "module"   | "version"       | "organisation cannot be null"
+        "organisation" | null       | "version"       | "module name cannot be null"
+        "organisation" | "module"   | null            | "revision cannot be null"
+        "org\t"        | "module"   | "version"       | "organisation cannot contain ISO control character '\\u0009'"
+        "organisation" | "module\n" | "version"       | "module name cannot contain ISO control character '\\u000a'"
+        "organisation" | "module"   | "version\u0085" | "revision cannot contain ISO control character '\\u0085'"
+    }
+
+    def "project coordinates must match ivy descriptor file"() {
+        given:
+        def projectIdentity = projectIdentity("org", "module", "version")
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity, ivyFile(organisation, module, version), emptySet())
+        def repository = Mock(PublicationAwareRepository)
+
+        when:
+        publisher.publish(publication, repository)
+
+        then:
+        def e = thrown InvalidIvyPublicationException
+        e.message == "Invalid publication 'pub-name': $message"
+
+        where:
+        organisation | module       | version       | message
+        "org-mod"    | "module"     | "version"     | "supplied organisation does not match ivy descriptor (cannot edit organisation directly in the ivy descriptor file)."
+        "org"        | "module-mod" | "version"     | "supplied module name does not match ivy descriptor (cannot edit module name directly in the ivy descriptor file)."
+        "org"        | "module"     | "version-mod" | "supplied revision does not match ivy descriptor (cannot edit revision directly in the ivy descriptor file)."
+    }
+
+    def "reports and fails with invalid descriptor file"() {
+        given:
+        IvyDescriptorFileGenerator ivyFileGenerator = new IvyDescriptorFileGenerator(new DefaultIvyPublicationIdentity("the-group", "the-artifact", "the-version"))
+        final artifact = new DefaultIvyArtifact(null, "name", "ext", "type", "classifier")
+        artifact.setConf("unknown")
+        ivyFileGenerator.addArtifact(artifact)
+
+        def ivyFile = testDir.file("ivy")
+        ivyFileGenerator.writeTo(ivyFile)
+
+        and:
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity("the-group", "the-artifact", "the-version"), ivyFile, emptySet())
+        def repository = Mock(PublicationAwareRepository)
+
+        when:
+        publisher.publish(publication, repository)
+
+        then:
+        def e = thrown InvalidIvyPublicationException
+        e.message.startsWith "Invalid publication 'pub-name': Problem occurred while parsing ivy file: " +
+                "Cannot add artifact 'name.ext(type)' to configuration 'unknown' of module the-group#the-artifact;the-version because this configuration doesn't exist!"
+    }
+
+    def "validates artifact attributes"() {
+        given:
+
+        def ivyArtifact = Stub(IvyArtifact) {
+            getName() >> name
+            getType() >> type
+            getExtension() >> extension
+            getClassifier() >> classifier
+        }
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity("org", "module", "version"), ivyFile("org", "module", "version"), toSet([ivyArtifact]))
+
+        when:
+        publisher.publish(publication, Mock(PublicationAwareRepository))
+
+        then:
+        def t = thrown InvalidIvyPublicationException
+        t.message == "Invalid publication 'pub-name': artifact ${message}."
+
+        where:
+        name    | type    | extension | classifier   | message
+        null    | "type"  | "ext"     | "classifier" | "name cannot be null"
+        ""      | "type"  | "ext"     | "classifier" | "name cannot be empty"
+        "na/me" | "type"  | "ext"     | null         | "name cannot contain '/'"
+        "name"  | null    | "ext"     | "classifier" | "type cannot be null"
+        "name"  | ""      | "ext"     | "classifier" | "type cannot be empty"
+        "name"  | "ty/pe" | "ext"     | null         | "type cannot contain '/'"
+        "name"  | "type"  | null      | "classifier" | "extension cannot be null"
+        "name"  | "type"  | "ex/t"    | null         | "extension cannot contain '/'"
+        "name"  | "type"  | "ext"     | ""           | "classifier cannot be an empty string. Use null instead"
+        "name"  | "type"  | "ext"     | "class\\y"   | "classifier cannot contain '\\'"
+    }
+
+    @Unroll
+    def "cannot publish with file that #message"() {
+        def ivyArtifact = Mock(IvyArtifact)
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity("group", "artifact", "version"), ivyFile("group", "artifact", "version"), toSet([ivyArtifact]))
+
+        when:
+        publisher.publish(publication, Mock(PublicationAwareRepository))
+
+        then:
+        ivyArtifact.name >> "name"
+        ivyArtifact.type >> "type"
+        ivyArtifact.extension >> "ext"
+        ivyArtifact.file >> theFile
+
+        and:
+        def t = thrown InvalidIvyPublicationException
+        t.message == "Invalid publication 'pub-name': artifact file ${message}: '${theFile}'"
+
+        where:
+        theFile                                                         | message
+        new File(testDir.testDirectory, 'does-not-exist') | 'does not exist'
+        testDir.testDirectory.createDir('sub_directory')  | 'is a directory'
+    }
+
+    def "cannot publish with duplicate artifacts"() {
+        given:
+        IvyArtifact artifact1 = Stub() {
+            getName() >> "name"
+            getExtension() >> "ext1"
+            getType() >> "type"
+            getClassifier() >> "classified"
+            getFile() >> testDir.createFile('artifact1')
+        }
+        IvyArtifact artifact2 = Stub() {
+            getName() >> "name"
+            getExtension() >> "ext1"
+            getType() >> "type"
+            getClassifier() >> "classified"
+            getFile() >> testDir.createFile('artifact2')
+        }
+        def projectIdentity = projectIdentity("org", "module", "revision")
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity, ivyFile("org", "module", "revision"), toSet([artifact1, artifact2]))
+
+        when:
+        publisher.publish(publication, Mock(PublicationAwareRepository))
+
+        then:
+        def t = thrown InvalidIvyPublicationException
+        t.message == "Invalid publication 'pub-name': multiple artifacts with the identical name, extension, type and classifier ('name', ext1', 'type', 'classified')."
+    }
+
+    def "cannot publish artifact with same attributes as ivy.xml"() {
+        given:
+        IvyArtifact artifact1 = Stub() {
+            getName() >> "ivy"
+            getExtension() >> "xml"
+            getType() >> "xml"
+            getClassifier() >> null
+            getFile() >> testDir.createFile('artifact1')
+        }
+        def projectIdentity = projectIdentity("org", "module", "revision")
+        def publication = new IvyNormalizedPublication("pub-name", projectIdentity, ivyFile("org", "module", "revision"), toSet([artifact1]))
+
+        when:
+        publisher.publish(publication, Mock(PublicationAwareRepository))
+
+        then:
+        def t = thrown InvalidIvyPublicationException
+        t.message == "Invalid publication 'pub-name': multiple artifacts with the identical name, extension, type and classifier ('ivy', xml', 'xml', 'null')."
+    }
+
+    private def projectIdentity(def groupId, def artifactId, def version) {
+        return Stub(IvyPublicationIdentity) {
+            getOrganisation() >> groupId
+            getModule() >> artifactId
+            getRevision() >> version
+        }
+    }
+
+    private def ivyFile(def group, def moduleName, def version, Action<XmlProvider> action = null) {
+        def ivyXmlFile = testDir.file("ivy")
+        IvyDescriptorFileGenerator ivyFileGenerator = new IvyDescriptorFileGenerator(new DefaultIvyPublicationIdentity(group, moduleName, version))
+        if (action != null) {
+            ivyFileGenerator.withXml(action)
+        }
+        ivyFileGenerator.writeTo(ivyXmlFile)
+        return ivyXmlFile
+    }
+}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy
index 7770c93..2a27d4e 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/plugins/IvyPublishPluginTest.groovy
@@ -15,63 +15,82 @@
  */
 
 package org.gradle.api.publish.ivy.plugins
-
 import org.gradle.api.Project
 import org.gradle.api.internal.xml.XmlTransformer
 import org.gradle.api.publish.PublishingExtension
 import org.gradle.api.publish.ivy.IvyPublication
-import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication
-import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
+import org.gradle.api.publish.ivy.internal.publication.DefaultIvyPublication
+import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
 class IvyPublishPluginTest extends Specification {
 
     Project project = HelperUtil.createRootProject()
-    PublishingExtension extension
+    PublishingExtension publishing
 
     def setup() {
         project.plugins.apply(IvyPublishPlugin)
-        extension = project.extensions.getByType(PublishingExtension)
+        publishing = project.extensions.getByType(PublishingExtension)
     }
 
-    def "installs ivy publication"() {
+    def "no publication without component"() {
         expect:
-        extension.publications.size() == 1
-        extension.publications.toList().first() instanceof IvyPublication
+        publishing.publications.empty
+    }
 
-        IvyPublicationInternal publication = extension.publications.ivy
-        publication.name == "ivy"
+    def "publication can be added"() {
+        when:
+        publishing.publications.add("test", IvyPublication)
 
+        then:
+        publishing.publications.size() == 1
+        publishing.publications.test instanceof DefaultIvyPublication
+    }
+
+    def "creates publish task for publication and repository"() {
+        when:
+        publishing.publications.add("test", IvyPublication)
+        publishing.repositories { ivy { url = "http://foo.com" } }
+        def publishTask = project.tasks["publishTestPublicationToIvyRepository"]
+
+        then:
+        publishTask != null
+        project.tasks["publish"].dependsOn.contains publishTask
+    }
+
+    def "ivy publication coordinates are a snapshot of project identity"() {
         when:
         project.group = "foo"
         project.version = 1.0
         project.status = "integration"
 
         and:
-        IvyNormalizedPublication normalizedPublication = publication.asNormalisedPublication()
+        publishing.publications.add("test", IvyPublication)
 
         then:
-        normalizedPublication.module.name == project.name
-        normalizedPublication.module.group == project.group
-        normalizedPublication.module.version == project.version.toString()
-        normalizedPublication.module.status == project.status
+        with (publishing.publications.test) {
+            identity.module == project.name
+            identity.organisation == "foo"
+            identity.revision == "1.0"
+            descriptor.status == "integration"
+        }
 
         when:
-        project.group = "bar"
-        project.version = 2.0
-        project.status = "final"
-        normalizedPublication = publication.asNormalisedPublication()
+        project.group = "changed-group"
+        project.version = "changed-version"
 
         then:
-        normalizedPublication.module.group == project.group
-        normalizedPublication.module.version == project.version.toString()
-        normalizedPublication.module.status == project.status
+        with (publishing.publications.test) {
+            identity.organisation == "foo"
+            identity.revision == "1.0"
+        }
     }
 
     def "can configure descriptor"() {
         given:
-        IvyPublicationInternal publication = extension.publications.ivy
+        publishing.publications.add("ivy", IvyPublication)
+        IvyPublicationInternal publication = publishing.publications.ivy
 
         when:
         publication.descriptor {
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy
index e6acdb8..191f145 100644
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy
+++ b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/PublishToIvyRepositoryTest.groovy
@@ -19,8 +19,8 @@ import org.gradle.api.InvalidUserDataException
 import org.gradle.api.Project
 import org.gradle.api.artifacts.repositories.IvyArtifactRepository
 import org.gradle.api.publish.ivy.IvyPublication
-import org.gradle.api.publish.ivy.internal.IvyNormalizedPublication
-import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
+import org.gradle.api.publish.ivy.internal.publisher.IvyNormalizedPublication
+import org.gradle.api.publish.ivy.internal.publication.IvyPublicationInternal
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy
deleted file mode 100644
index 943a5ec..0000000
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublicationDynamicDescriptorGenerationTaskCreatorTest.groovy
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.tasks.internal
-import org.gradle.api.publish.Publication
-import org.gradle.api.publish.PublishingExtension
-import org.gradle.api.publish.ivy.IvyModuleDescriptor
-import org.gradle.api.publish.ivy.IvyPublication
-import org.gradle.api.publish.ivy.internal.IvyModuleDescriptorInternal
-import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
-import org.gradle.api.publish.ivy.tasks.GenerateIvyDescriptor
-import org.gradle.api.publish.plugins.PublishingPlugin
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class IvyPublicationDynamicDescriptorGenerationTaskCreatorTest extends Specification {
-
-    def project = HelperUtil.createRootProject()
-    def creator = new IvyPublicationDynamicDescriptorGenerationTaskCreator(project)
-    PublishingExtension publishing
-
-    def setup() {
-        project.plugins.apply(PublishingPlugin)
-        publishing = project.extensions.getByType(PublishingExtension)
-        creator.monitor(publishing.publications)
-    }
-
-    def "creates tasks"() {
-        expect:
-        descriptorGeneratorTasks.size() == 0
-
-        when:
-        publishing.repositories.ivy { }
-        publishing.publications.add(publication("foo"))
-
-        then:
-        descriptorGeneratorTasks.size() == 0
-
-        when:
-        publishing.publications.add(ivyPublication("ivy"))
-
-        then:
-        descriptorGeneratorTasks.size() == 1
-        GenerateIvyDescriptor task = project.tasks.generateIvyModuleDescriptor
-        task.group == "publishing"
-        task.description != null
-
-        when:
-        publishing.publications.add(ivyPublication("other"))
-
-        then:
-        descriptorGeneratorTasks.size() == 2
-        def task2 = project.tasks.generateOtherIvyModuleDescriptor
-        task2.group == "publishing"
-    }
-
-    Publication publication(String name) {
-        Mock(Publication) {
-            getName() >> name
-        }
-    }
-
-    IvyPublication ivyPublication(String name) {
-        IvyModuleDescriptor moduleDescriptor = Mock(IvyModuleDescriptorInternal)
-        Mock(IvyPublicationInternal) {
-            getName() >> name
-            getDescriptor() >> moduleDescriptor
-            1 * setDescriptorArtifact({it.file.path.contains name})
-        }
-    }
-
-    def getDescriptorGeneratorTasks() {
-        project.tasks.withType(GenerateIvyDescriptor)
-    }
-
-}
diff --git a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreatorTest.groovy b/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreatorTest.groovy
deleted file mode 100644
index ba3697a..0000000
--- a/subprojects/ivy/src/test/groovy/org/gradle/api/publish/ivy/tasks/internal/IvyPublishDynamicTaskCreatorTest.groovy
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.ivy.tasks.internal
-
-import org.gradle.api.Task
-import org.gradle.api.publish.Publication
-import org.gradle.api.publish.PublishingExtension
-import org.gradle.api.publish.ivy.IvyPublication
-import org.gradle.api.publish.ivy.internal.IvyPublicationInternal
-import org.gradle.api.publish.ivy.tasks.PublishToIvyRepository
-import org.gradle.api.publish.plugins.PublishingPlugin
-import org.gradle.util.HelperUtil
-import spock.lang.Specification
-
-class IvyPublishDynamicTaskCreatorTest extends Specification {
-
-    def project = HelperUtil.createRootProject()
-    def lifecycleTask = project.task("pl")
-    def creator = new IvyPublishDynamicTaskCreator(project.tasks, lifecycleTask)
-
-    PublishingExtension publishing
-
-    def setup() {
-        project.plugins.apply(PublishingPlugin)
-        publishing = project.extensions.getByType(PublishingExtension)
-        creator.monitor(publishing.publications, publishing.repositories)
-    }
-
-    def "creates tasks"() {
-        expect:
-        ivyPublishTasks.size() == 0
-
-        when:
-        publishing.repositories.ivy { }
-        publishing.publications.add(publication("foo"))
-
-        then:
-        ivyPublishTasks.size() == 0
-        lifecycleTaskDependencies.empty
-
-        when:
-        publishing.publications.add(ivyPublication("ivy"))
-
-        then:
-        ivyPublishTasks.size() == 1
-        project.tasks["publishIvyPublicationToIvyRepository"] != null
-        PublishToIvyRepository task = project.tasks.publishIvyPublicationToIvyRepository
-        task.group == "publishing"
-        task.description != null
-
-        lifecycleTaskDependencies == [task] as Set
-
-        when:
-        publishing.publications.add(ivyPublication("ivy2"))
-
-        then:
-        ivyPublishTasks.size() == 2
-        project.tasks["publishIvy2PublicationToIvyRepository"] != null
-        lifecycleTaskDependencies.size() == 2
-
-        when:
-        publishing.repositories.ivy {}
-
-        then:
-        lifecycleTaskDependencies.size() == 4
-        ivyPublishTasks.size() == 4
-        project.tasks["publishIvyPublicationToIvy2Repository"] != null
-        project.tasks["publishIvy2PublicationToIvy2Repository"] != null
-    }
-
-    protected Set<? extends Task> getLifecycleTaskDependencies() {
-        lifecycleTask.taskDependencies.getDependencies(lifecycleTask)
-    }
-
-    def getIvyPublishTasks() {
-        project.tasks.withType(PublishToIvyRepository)
-    }
-
-    Publication publication(String name) {
-        Mock(Publication) {
-            getName() >> name
-        }
-    }
-
-    IvyPublication ivyPublication(String name) {
-        Mock(IvyPublicationInternal) {
-            getName() >> name
-        }
-    }
-
-}
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/ConfigurationOnDemandIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/ConfigurationOnDemandIntegrationTest.groovy
new file mode 100644
index 0000000..07371b1
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/ConfigurationOnDemandIntegrationTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.launcher
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.ProjectLifecycleFixture
+import org.junit.Rule
+
+/**
+ * by Szczepan Faber, created at: 11/21/12
+ */
+class ConfigurationOnDemandIntegrationTest extends AbstractIntegrationSpec {
+
+    @Rule ProjectLifecycleFixture fixture = new ProjectLifecycleFixture(executer, temporaryFolder)
+
+    def "start parameter informs about the configuration on demand mode"() {
+        file("gradle.properties") << "org.gradle.configureondemand=true"
+        buildFile << "assert gradle.startParameter.configureOnDemand"
+        expect:
+        run()
+    }
+
+    def "can be enabled from command line and start parameter informs about it, too"() {
+        file("gradle.properties") << "org.gradle.configureondemand=false"
+
+        settingsFile << "include 'api', 'impl'"
+        buildFile << """
+            allprojects { task foo };
+            assert gradle.startParameter.configureOnDemand
+        """
+
+        when:
+        run("--configure-on-demand", ":api:foo")
+
+        then:
+        fixture.assertProjectsConfigured(":", ":api")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/EnablingParallelExecutionIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/EnablingParallelExecutionIntegrationTest.groovy
new file mode 100644
index 0000000..56af0fc
--- /dev/null
+++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/EnablingParallelExecutionIntegrationTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.launcher
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
+import spock.lang.IgnoreIf
+
+/**
+ * by Szczepan Faber, created at: 11/21/12
+ */
+ at IgnoreIf({ GradleContextualExecuter.parallel })
+class EnablingParallelExecutionIntegrationTest extends AbstractIntegrationSpec {
+
+    def "parallel mode enabled from command line"() {
+        buildFile << "assert gradle.startParameter.parallelThreadCount == 15"
+        expect:
+        run("--parallel-threads=15")
+    }
+
+    def "parallel mode enabled via gradle.properties"() {
+        file("gradle.properties") << "org.gradle.parallel=true"
+        buildFile << "assert gradle.startParameter.parallelThreadCount == -1"
+        expect:
+        run()
+    }
+
+    def "parallel mode setting at command line takes precedence over gradle.properties"() {
+        file("gradle.properties") << "org.gradle.parallel=false"
+        buildFile << "assert gradle.startParameter.parallelThreadCount == 15"
+        expect:
+        run("--parallel-threads=15")
+    }
+}
\ No newline at end of file
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 6215b05..41eec24 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
@@ -192,7 +192,7 @@ task sleep << {
         """
 
         when:
-        def daemon = executer.setAllowExtraLogging(false).withArguments("--foreground").start()
+        def daemon = executer.noExtraLogging().withArguments("--foreground").start()
         
         then:
         poll(60) { assert daemon.standardOutput.contains(DaemonMessages.PROCESS_STARTED) }
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 00e2445..adce874 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
@@ -148,7 +148,6 @@ class DaemonLifecycleSpec extends DaemonIntegrationSpec {
         }
         executer.withArguments("--foreground", "--info", "-Dorg.gradle.daemon.idletimeout=${daemonIdleTimeout * 1000}")
         foregroundDaemons << executer.start()
-        executer.withJavaHome(null)
     }
 
     //this is a windows-safe way of killing the process
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 4ff7d4f..8f9ad3a 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
@@ -31,8 +31,8 @@ class SingleUseDaemonIntegrationTest extends AbstractIntegrationSpec {
 
     def setup() {
         // Need forking executer
-        // '-ea' is always set on the forked process. So I've added it explicitly here. // TODO:DAZ Clean this up
-        executer.requireGradleHome(true).withEnvironmentVars(["JAVA_OPTS": "-ea"])
+        // '-ea' is always set on the forked process. So I've added it explicitly here.
+        executer.requireGradleHome().withEnvironmentVars(["JAVA_OPTS": "-ea"])
         executer.requireIsolatedDaemons()
     }
 
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 c370bcf..a1ce784 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
@@ -48,7 +48,7 @@ Thread.sleep(60000)
 
     def "reports exact number of daemons stopped and keeps console output clean"() {
         given:
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
         executer.run()
 
         when:
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 4656fab..5a1f320 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
@@ -25,6 +25,7 @@ import org.gradle.cli.ParsedCommandLine;
 import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.initialization.DefaultGradleLauncherFactory;
+import org.gradle.internal.SystemProperties;
 import org.gradle.internal.service.ServiceRegistry;
 import org.gradle.launcher.bootstrap.ExecutionListener;
 import org.gradle.launcher.daemon.bootstrap.ForegroundDaemonMain;
@@ -35,13 +36,12 @@ import org.gradle.launcher.daemon.client.StopDaemonClientServices;
 import org.gradle.launcher.daemon.configuration.CurrentProcess;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.daemon.configuration.ForegroundDaemonConfiguration;
+import org.gradle.launcher.daemon.configuration.GradlePropertiesConfigurer;
 import org.gradle.launcher.exec.BuildActionParameters;
 import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter;
 
-import java.io.File;
 import java.lang.management.ManagementFactory;
-import java.util.Map;
 
 class BuildActionsFactory implements CommandLineAction {
     private static final String FOREGROUND = "foreground";
@@ -50,15 +50,17 @@ class BuildActionsFactory implements CommandLineAction {
     private static final String STOP = "stop";
     private final ServiceRegistry loggingServices;
     private final CommandLineConverter<StartParameter> startParameterConverter;
+    private final GradlePropertiesConfigurer propertiesConfigurer;
 
     BuildActionsFactory(ServiceRegistry loggingServices) {
-        this.loggingServices = loggingServices;
-        this.startParameterConverter = new DefaultCommandLineConverter();
+        this(loggingServices, new DefaultCommandLineConverter(), new GradlePropertiesConfigurer());
     }
 
-    BuildActionsFactory(ServiceRegistry loggingServices, CommandLineConverter<StartParameter> commandLineConverter) {
+    BuildActionsFactory(ServiceRegistry loggingServices, CommandLineConverter<StartParameter> commandLineConverter,
+                        GradlePropertiesConfigurer propertiesConfigurer) {
         this.loggingServices = loggingServices;
         this.startParameterConverter = commandLineConverter;
+        this.propertiesConfigurer = propertiesConfigurer;
     }
 
     public void configureCommandLineParser(CommandLineParser parser) {
@@ -73,7 +75,7 @@ class BuildActionsFactory implements CommandLineAction {
     public Action<? super ExecutionListener> createAction(CommandLineParser parser, ParsedCommandLine commandLine) {
         StartParameter startParameter = new StartParameter();
         startParameterConverter.convert(commandLine, startParameter);
-        DaemonParameters daemonParameters = constructDaemonParameters(startParameter);
+        DaemonParameters daemonParameters = propertiesConfigurer.configureParameters(startParameter);
         if (commandLine.hasOption(STOP)) {
             return stopAllDaemons(daemonParameters, loggingServices);
         }
@@ -91,15 +93,6 @@ class BuildActionsFactory implements CommandLineAction {
         return runBuildInSingleUseDaemon(startParameter, daemonParameters, loggingServices);
     }
 
-    private DaemonParameters constructDaemonParameters(StartParameter startParameter) {
-        Map<String, String> mergedSystemProperties = startParameter.getMergedSystemProperties();
-        DaemonParameters daemonParameters = new DaemonParameters();
-        daemonParameters.configureFromBuildDir(startParameter.getCurrentDir(), startParameter.isSearchUpwards());
-        daemonParameters.configureFromGradleUserHome(startParameter.getGradleUserHomeDir());
-        daemonParameters.configureFromSystemProperties(mergedSystemProperties);
-        return daemonParameters;
-    }
-
     private Action<? super ExecutionListener> stopAllDaemons(DaemonParameters daemonParameters, ServiceRegistry loggingServices) {
         DaemonClientServices clientServices = new StopDaemonClientServices(loggingServices, daemonParameters, System.in);
         DaemonClient stopClient = clientServices.get(DaemonClient.class);
@@ -148,17 +141,13 @@ class BuildActionsFactory implements CommandLineAction {
 
     private Action<? super ExecutionListener> daemonBuildAction(StartParameter startParameter, DaemonParameters daemonParameters, GradleLauncherActionExecuter<BuildActionParameters> executer) {
         return Actions.toAction(
-                new RunBuildAction(executer, startParameter, getWorkingDir(), clientMetaData(), getBuildStartTime(), daemonParameters.getEffectiveSystemProperties(), System.getenv()));
+                new RunBuildAction(executer, startParameter, SystemProperties.getCurrentDir(), clientMetaData(), getBuildStartTime(), daemonParameters.getEffectiveSystemProperties(), System.getenv()));
     }
 
     private long getBuildStartTime() {
         return ManagementFactory.getRuntimeMXBean().getStartTime();
     }
 
-    private File getWorkingDir() {
-        return new File(System.getProperty("user.dir"));
-    }
-
     private GradleLauncherMetaData clientMetaData() {
         return new GradleLauncherMetaData();
     }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientConnection.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientConnection.java
index f70fc6f..5976309 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientConnection.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/client/DaemonClientConnection.java
@@ -58,7 +58,6 @@ public class DaemonClientConnection implements Connection<Object> {
     public Object receive() {
         try {
             Object result = connection.receive();
-            LOG.debug("thread {}: received {}", Thread.currentThread().getId(), result == null ? "null" : result.getClass());
             return result;
         } catch (Exception e) {
             LOG.debug("Problem receiving message to the daemon. Performing 'on failure' operation...");
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 2e6bf8f..f0758c2 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
@@ -16,35 +16,22 @@
 package org.gradle.launcher.daemon.configuration;
 
 import org.gradle.StartParameter;
-import org.gradle.api.GradleException;
-import org.gradle.api.Project;
-import org.gradle.api.UncheckedIOException;
 import org.gradle.api.internal.file.IdentityFileResolver;
-import org.gradle.initialization.layout.BuildLayout;
-import org.gradle.initialization.layout.BuildLayoutFactory;
-import org.gradle.internal.jvm.JavaHomeException;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.process.internal.JvmOptions;
-import org.gradle.util.GFileUtils;
 import org.gradle.util.GUtil;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
 import java.util.*;
 
 import static java.util.Arrays.asList;
 import static org.gradle.util.GFileUtils.canonicalise;
 
 public class DaemonParameters {
-    public static final String IDLE_TIMEOUT_SYS_PROPERTY = "org.gradle.daemon.idletimeout";
-    public static final String BASE_DIR_SYS_PROPERTY = "org.gradle.daemon.registry.base";
-    public static final String JVM_ARGS_SYS_PROPERTY = "org.gradle.jvmargs";
-    public static final String JAVA_HOME_SYS_PROPERTY = "org.gradle.java.home";
-    public static final String DAEMON_SYS_PROPERTY = "org.gradle.daemon";
-    public static final String DEBUG_SYS_PROPERTY = "org.gradle.debug";
     static final int DEFAULT_IDLE_TIMEOUT = 3 * 60 * 60 * 1000;
+
     private final String uid;
+
     private File baseDir = new File(StartParameter.DEFAULT_GRADLE_USER_HOME, "daemon");
     private int idleTimeout = DEFAULT_IDLE_TIMEOUT;
     private final JvmOptions jvmOptions = new JvmOptions(new IdentityFileResolver());
@@ -69,6 +56,11 @@ public class DaemonParameters {
         return enabled;
     }
 
+    public DaemonParameters setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        return this;
+    }
+
     public String getUid() {
         return uid;
     }
@@ -77,10 +69,6 @@ public class DaemonParameters {
         return baseDir;
     }
 
-    public void setBaseDir(File baseDir) {
-        this.baseDir = GFileUtils.canonicalise(baseDir);
-    }
-
     public int getIdleTimeout() {
         return idleTimeout;
     }
@@ -115,8 +103,9 @@ public class DaemonParameters {
         return Jvm.forHome(javaHome).getJavaExecutable().getAbsolutePath();
     }
 
-    public void setJavaHome(File javaHome) {
+    public DaemonParameters setJavaHome(File javaHome) {
         this.javaHome = javaHome;
+        return this;
     }
 
     public Map<String, String> getSystemProperties() {
@@ -137,79 +126,24 @@ public class DaemonParameters {
         jvmOptions.setAllJvmArgs(jvmArgs);
     }
 
-    public void configureFromGradleUserHome(File gradleUserHomeDir) {
-        setBaseDir(new File(gradleUserHomeDir, "daemon"));
-        maybeConfigureFrom(new File(gradleUserHomeDir, Project.GRADLE_PROPERTIES));
-    }
-
-    public void configureFromSystemProperties(Map<?, ?> properties) {
-        Object propertyValue = properties.get(BASE_DIR_SYS_PROPERTY);
-        if (propertyValue != null) {
-            setBaseDir(new File(propertyValue.toString()));
-        }
-        configureFrom(properties);
-    }
-
-    public void configureFromBuildDir(File currentDir, boolean searchUpwards) {
-        BuildLayoutFactory factory = new BuildLayoutFactory();
-        BuildLayout layout = factory.getLayoutFor(currentDir, searchUpwards);
-        maybeConfigureFrom(new File(layout.getRootDirectory(), Project.GRADLE_PROPERTIES));
-    }
-
-    private void maybeConfigureFrom(File propertiesFile) {
-        if (!propertiesFile.isFile()) {
-            return;
-        }
-
-        Properties properties = new Properties();
-        try {
-            FileInputStream inputStream = new FileInputStream(propertiesFile);
-            try {
-                properties.load(inputStream);
-            } finally {
-                inputStream.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
+    public void configureFrom(GradleProperties gradleProperties) {
+        if (gradleProperties.getIdleTimeout() != null) {
+            idleTimeout = gradleProperties.getIdleTimeout();
         }
-
-        configureFrom(properties);
-    }
-
-    void configureFrom(Map<?, ?> properties) {
-        Object propertyValue = properties.get(IDLE_TIMEOUT_SYS_PROPERTY);
-        if (propertyValue != null) {
-            try {
-                idleTimeout = Integer.parseInt(propertyValue.toString());
-            } catch (NumberFormatException e) {
-                throw new GradleException(String.format("Unable to parse %s property. The value should be an int but is: %s", IDLE_TIMEOUT_SYS_PROPERTY, propertyValue));
-            }
+        if (gradleProperties.getJvmArgs() != null) {
+            setJvmArgs(JvmOptions.fromString(gradleProperties.getJvmArgs()));
         }
-        propertyValue = properties.get(JVM_ARGS_SYS_PROPERTY);
-        if (propertyValue != null) {
-            setJvmArgs(JvmOptions.fromString(propertyValue.toString()));
+        if (gradleProperties.isDaemonEnabled()) {
+            enabled = true;
         }
-        propertyValue = properties.get(DAEMON_SYS_PROPERTY);
-        if (propertyValue != null) {
-            enabled = propertyValue.toString().equalsIgnoreCase("true");
+        if (gradleProperties.getJavaHome() != null) {
+            javaHome = gradleProperties.getJavaHome();
         }
-
-        propertyValue = properties.get(JAVA_HOME_SYS_PROPERTY);
-        if (propertyValue != null) {
-            javaHome = new File(propertyValue.toString());
-            if (!javaHome.isDirectory()) {
-                throw new GradleException(String.format("Java home supplied via '%s' is invalid. Dir does not exist: %s", JAVA_HOME_SYS_PROPERTY, propertyValue));
-            }
-            try {
-                Jvm.forHome(javaHome);
-            } catch (JavaHomeException e) {
-                throw new GradleException(String.format("Java home supplied via '%s' seems to be invalid: %s", JAVA_HOME_SYS_PROPERTY, propertyValue));
-            }
+        if (gradleProperties.isDebugMode()) {
+            jvmOptions.setDebug(true);
         }
-
-        propertyValue = properties.get(DEBUG_SYS_PROPERTY);
-        if (propertyValue != null) {
-            jvmOptions.setDebug(propertyValue.toString().equalsIgnoreCase("true"));
+        if (gradleProperties.getDaemonBaseDir() != null) {
+            baseDir = gradleProperties.getDaemonBaseDir();
         }
     }
 }
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradleProperties.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradleProperties.java
new file mode 100644
index 0000000..a8642f6
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradleProperties.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.launcher.daemon.configuration;
+
+import org.gradle.StartParameter;
+import org.gradle.api.GradleException;
+import org.gradle.api.Nullable;
+import org.gradle.api.Project;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.initialization.layout.BuildLayout;
+import org.gradle.initialization.layout.BuildLayoutFactory;
+import org.gradle.internal.jvm.JavaHomeException;
+import org.gradle.internal.jvm.Jvm;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * by Szczepan Faber, created at: 1/22/13
+ */
+public class GradleProperties {
+
+    public static final String IDLE_TIMEOUT_PROPERTY = "org.gradle.daemon.idletimeout";
+    public static final String BASE_DIR_PROPERTY = "org.gradle.daemon.registry.base";
+    public static final String JVM_ARGS_PROPERTY = "org.gradle.jvmargs";
+    public static final String JAVA_HOME_PROPERTY = "org.gradle.java.home";
+    public static final String DAEMON_ENABLED_PROPERTY = "org.gradle.daemon";
+    public static final String DEBUG_MODE_PROPERTY = "org.gradle.debug";
+    public static final String CONFIGURE_ON_DEMAND_PROPERTY = "org.gradle.configureondemand";
+    public static final String PARALLEL_PROPERTY = "org.gradle.parallel";
+
+    private File daemonBaseDir;
+    private String jvmArgs;
+
+    private Integer idleTimeout;
+    private boolean daemonEnabled;
+    private File javaHome;
+    private boolean debugMode;
+    private boolean configureOnDemand;
+    private boolean parallelMode;
+
+    public boolean isDaemonEnabled() {
+        return daemonEnabled;
+    }
+
+    @Nullable
+    public File getDaemonBaseDir() {
+        return daemonBaseDir;
+    }
+
+    public Integer getIdleTimeout() {
+        return idleTimeout;
+    }
+
+    @Nullable
+    public File getJavaHome() {
+        return javaHome;
+    }
+
+    public String getJvmArgs() {
+        return jvmArgs;
+    }
+
+    public boolean isDebugMode() {
+        return debugMode;
+    }
+
+    private void setBaseDir(File baseDir) {
+        this.daemonBaseDir = GFileUtils.canonicalise(baseDir);
+    }
+
+    public GradleProperties configureFromGradleUserHome(File gradleUserHomeDir) {
+        setBaseDir(new File(gradleUserHomeDir, "daemon"));
+        maybeConfigureFrom(new File(gradleUserHomeDir, Project.GRADLE_PROPERTIES));
+        return this;
+    }
+
+    public GradleProperties configureFromSystemProperties(Map<?, ?> properties) {
+        Object propertyValue = properties.get(BASE_DIR_PROPERTY);
+        if (propertyValue != null) {
+            setBaseDir(new File(propertyValue.toString()));
+        }
+        configureFrom(properties);
+        return this;
+    }
+
+    public GradleProperties configureFromBuildDir(File currentDir, boolean searchUpwards) {
+        BuildLayoutFactory factory = new BuildLayoutFactory();
+        BuildLayout layout = factory.getLayoutFor(currentDir, searchUpwards);
+        maybeConfigureFrom(new File(layout.getRootDirectory(), Project.GRADLE_PROPERTIES));
+        return this;
+    }
+
+    private void maybeConfigureFrom(File propertiesFile) {
+        if (!propertiesFile.isFile()) {
+            return;
+        }
+
+        Properties properties = new Properties();
+        try {
+            FileInputStream inputStream = new FileInputStream(propertiesFile);
+            try {
+                properties.load(inputStream);
+            } finally {
+                inputStream.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+
+        configureFrom(properties);
+    }
+
+    GradleProperties configureFrom(Map<?, ?> properties) {
+        Object propertyValue = properties.get(IDLE_TIMEOUT_PROPERTY);
+        if (propertyValue != null) {
+            try {
+                idleTimeout = new Integer(propertyValue.toString());
+            } catch (NumberFormatException e) {
+                throw new GradleException(String.format("Unable to parse %s property. The value should be an int but is: %s", IDLE_TIMEOUT_PROPERTY, propertyValue));
+            }
+        }
+        propertyValue = properties.get(JVM_ARGS_PROPERTY);
+        if (propertyValue != null) {
+            jvmArgs = propertyValue.toString();
+        }
+        propertyValue = properties.get(DAEMON_ENABLED_PROPERTY);
+        if (propertyValue != null) {
+            daemonEnabled = isTrue(propertyValue);
+        }
+
+        propertyValue = properties.get(JAVA_HOME_PROPERTY);
+        if (propertyValue != null) {
+            javaHome = new File(propertyValue.toString());
+            if (!javaHome.isDirectory()) {
+                throw new GradleException(String.format("Java home supplied via '%s' is invalid. Dir does not exist: %s", JAVA_HOME_PROPERTY, propertyValue));
+            }
+            try {
+                Jvm.forHome(javaHome);
+            } catch (JavaHomeException e) {
+                throw new GradleException(String.format("Java home supplied via '%s' seems to be invalid: %s", JAVA_HOME_PROPERTY, propertyValue));
+            }
+        }
+
+        propertyValue = properties.get(DEBUG_MODE_PROPERTY);
+        if (propertyValue != null) {
+            debugMode = isTrue(propertyValue);
+        }
+
+        propertyValue = properties.get(CONFIGURE_ON_DEMAND_PROPERTY);
+        if (propertyValue != null) {
+            configureOnDemand = isTrue(propertyValue);
+        }
+
+        propertyValue = properties.get(PARALLEL_PROPERTY);
+        if (propertyValue != null) {
+            parallelMode = isTrue(propertyValue);
+        }
+
+        return this;
+    }
+
+    public void updateStartParameter(StartParameter startParameter) {
+        if (configureOnDemand) {
+            startParameter.setConfigureOnDemand(configureOnDemand);
+        }
+        if (parallelMode && !startParameter.isParallelThreadCountConfigured()) {
+            startParameter.setParallelThreadCount(-1);
+        }
+    }
+
+    public boolean isConfigureOnDemand() {
+        return configureOnDemand;
+    }
+
+    public boolean isParallelMode() {
+        return parallelMode;
+    }
+
+    private static boolean isTrue(Object propertyValue) {
+        return propertyValue.toString().equalsIgnoreCase("true");
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurer.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurer.java
new file mode 100644
index 0000000..4948ca1
--- /dev/null
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurer.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.launcher.daemon.configuration;
+
+import org.gradle.StartParameter;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * by Szczepan Faber, created at: 1/22/13
+ */
+public class GradlePropertiesConfigurer {
+
+    public GradleProperties prepareProperties(File projectDir, boolean searchUpwards, File gradleUserHomeDir, Map<?, ?> systemProperties) {
+        return new GradleProperties()
+            .configureFromBuildDir(projectDir, searchUpwards)
+            .configureFromGradleUserHome(gradleUserHomeDir)
+            .configureFromSystemProperties(systemProperties);
+    }
+
+    public DaemonParameters configureParameters(StartParameter startParameter) {
+        DaemonParameters out = new DaemonParameters();
+        GradleProperties properties = configureStartParameter(startParameter);
+        out.configureFrom(properties);
+        return out;
+    }
+
+    public GradleProperties configureStartParameter(StartParameter startParameter) {
+        GradleProperties properties = this.prepareProperties(startParameter.getCurrentDir(), startParameter.isSearchUpwards(), startParameter.getGradleUserHomeDir(), startParameter.getMergedSystemProperties());
+        properties.updateStartParameter(startParameter);
+        return properties;
+    }
+}
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java
index c4e41bd..e93fe23 100644
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/DaemonCommandExecution.java
@@ -40,7 +40,6 @@ public class DaemonCommandExecution {
 
     private Throwable exception;
     private Object result;
-    private final List<Runnable> finalizers = new LinkedList<Runnable>();
 
     public DaemonCommandExecution(DaemonConnection connection, Command command, DaemonContext daemonContext, DaemonStateControl daemonStateControl, Runnable commandAbandoned, List<DaemonCommandAction> actions) {
         this.connection = connection;
@@ -132,15 +131,4 @@ public class DaemonCommandExecution {
     public String toString() {
         return String.format("DaemonCommandExecution[command = %s, connection = %s]", command, connection);
     }
-
-    public void addFinalizer(Runnable runnable) {
-        assert runnable != null;
-        finalizers.add(runnable);
-    }
-
-    public void executeFinalizers() {
-        for (Runnable finalizer : finalizers) {
-            finalizer.run();
-        }
-    }
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/EstablishBuildEnvironment.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/EstablishBuildEnvironment.java
old mode 100644
new mode 100755
index 406170c..8609e4f
--- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/EstablishBuildEnvironment.java
+++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/exec/EstablishBuildEnvironment.java
@@ -23,6 +23,7 @@ import org.gradle.launcher.daemon.protocol.Build;
 import org.gradle.util.GFileUtils;
 
 import java.io.File;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 
@@ -41,7 +42,7 @@ public class EstablishBuildEnvironment extends BuildCommandOnly {
     protected void doBuild(DaemonCommandExecution execution, Build build) {
         Properties originalSystemProperties = new Properties();
         originalSystemProperties.putAll(System.getProperties());
-        Map<String, String> originalEnv = System.getenv();
+        Map<String, String> originalEnv = new HashMap<String, String>(System.getenv());
         File originalProcessDir = GFileUtils.canonicalise(new File("."));
 
         for (Map.Entry<String, String> entry : build.getParameters().getSystemProperties().entrySet()) {
@@ -52,7 +53,6 @@ public class EstablishBuildEnvironment extends BuildCommandOnly {
 
         LOGGER.debug("Configuring env variables: {}", build.getParameters().getEnvVariables());
         processEnvironment.maybeSetEnvironment(build.getParameters().getEnvVariables());
-
         processEnvironment.maybeSetProcessDir(build.getParameters().getCurrentDir());
 
         try {
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
index 13ce0a6..d1537d6 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ConfiguringBuildAction.java
@@ -22,6 +22,7 @@ import org.gradle.api.logging.LogLevel;
 import org.gradle.cli.CommandLineArgumentException;
 import org.gradle.initialization.DefaultCommandLineConverter;
 import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.launcher.daemon.configuration.GradleProperties;
 import org.gradle.launcher.exec.InitializationAware;
 import org.gradle.logging.ShowStacktrace;
 import org.gradle.tooling.internal.protocol.exceptions.InternalUnsupportedBuildArgumentException;
@@ -43,10 +44,12 @@ class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, Initializati
 
     // Important that this is constructed on the client so that it has the right gradleHomeDir internally
     private final StartParameter startParameterTemplate = new StartParameter();
+    private boolean configureOnDemand;
 
     public ConfiguringBuildAction() {}
 
-    public ConfiguringBuildAction(ProviderOperationParameters parameters, GradleLauncherAction<T> action) {
+    public ConfiguringBuildAction(ProviderOperationParameters parameters, GradleLauncherAction<T> action, GradleProperties gradleProperties) {
+        this.configureOnDemand = gradleProperties.isConfigureOnDemand();
         this.gradleUserHomeDir = parameters.getGradleUserHomeDir();
         this.projectDirectory = parameters.getProjectDir();
         this.searchUpwards = parameters.isSearchUpwards();
@@ -70,6 +73,8 @@ class ConfiguringBuildAction<T> implements GradleLauncherAction<T>, Initializati
             startParameter.setTaskNames(tasks);
         }
 
+        startParameter.setConfigureOnDemand(configureOnDemand);
+
         if (arguments != null) {
             DefaultCommandLineConverter converter = new DefaultCommandLineConverter();
             try {
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
index 47f2de6..d9cdc86 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DaemonGradleLauncherActionExecuter.java
@@ -17,6 +17,7 @@ package org.gradle.tooling.internal.provider;
 
 import org.gradle.configuration.GradleLauncherMetaData;
 import org.gradle.initialization.GradleLauncherAction;
+import org.gradle.internal.SystemProperties;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
 import org.gradle.launcher.exec.BuildActionParameters;
 import org.gradle.launcher.exec.DefaultBuildActionParameters;
@@ -25,8 +26,6 @@ import org.gradle.launcher.exec.ReportedException;
 import org.gradle.tooling.internal.protocol.BuildExceptionVersion1;
 import org.gradle.tooling.internal.provider.connection.ProviderOperationParameters;
 
-import java.io.File;
-
 public class DaemonGradleLauncherActionExecuter implements GradleLauncherActionExecuter<ProviderOperationParameters> {
     private final GradleLauncherActionExecuter<BuildActionParameters> executer;
     private final DaemonParameters parameters;
@@ -38,7 +37,7 @@ public class DaemonGradleLauncherActionExecuter implements GradleLauncherActionE
 
     public <T> T execute(GradleLauncherAction<T> action, ProviderOperationParameters actionParameters) {
         BuildActionParameters parameters = new DefaultBuildActionParameters(new GradleLauncherMetaData(), actionParameters.getStartTime(),
-                this.parameters.getEffectiveSystemProperties(), System.getenv(), new File(System.getProperty("user.dir")), actionParameters.getBuildLogLevel());
+                this.parameters.getEffectiveSystemProperties(), System.getenv(), SystemProperties.getCurrentDir(), actionParameters.getBuildLogLevel());
         try {
             return executer.execute(action, parameters);
         } catch (ReportedException e) {
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/DefaultConnection.java
index 449bd8c..918f141 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
@@ -22,6 +22,8 @@ import org.gradle.internal.Factory;
 import org.gradle.launcher.daemon.client.DaemonClient;
 import org.gradle.launcher.daemon.client.DaemonClientServices;
 import org.gradle.launcher.daemon.configuration.DaemonParameters;
+import org.gradle.launcher.daemon.configuration.GradleProperties;
+import org.gradle.launcher.daemon.configuration.GradlePropertiesConfigurer;
 import org.gradle.launcher.exec.BuildActionParameters;
 import org.gradle.launcher.exec.GradleLauncherActionExecuter;
 import org.gradle.logging.LoggingManagerInternal;
@@ -111,12 +113,13 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
         if (type.equals(Void.class) && tasks == null) {
             throw new IllegalArgumentException("No model type or tasks specified.");
         }
+        GradleProperties gradleProperties = initGradleProperties(providerParameters);
         if (type == InternalBuildEnvironment.class) {
             //we don't really need to launch the daemon to acquire information needed for BuildEnvironment
             if (tasks != null) {
                 throw new IllegalArgumentException("Cannot run tasks and fetch the build environment model.");
             }
-            DaemonParameters daemonParameters = init(providerParameters);
+            DaemonParameters daemonParameters = init(providerParameters, gradleProperties);
             DefaultBuildEnvironment out = new DefaultBuildEnvironment(
                     GradleVersion.current().getVersion(),
                     daemonParameters.getEffectiveJavaHome(),
@@ -126,22 +129,22 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
         }
 
         DelegatingBuildModelAction<T> action = new DelegatingBuildModelAction<T>(type, tasks != null);
-        return run(action, providerParameters);
+        return run(action, providerParameters, gradleProperties);
     }
 
     private void logTargetVersion() {
         LOGGER.info("Tooling API uses target gradle version:" + " {}.", GradleVersion.current().getVersion());
     }
 
-    private <T> T run(GradleLauncherAction<T> action, ProviderOperationParameters operationParameters) {
+    private <T> T run(GradleLauncherAction<T> action, ProviderOperationParameters operationParameters, GradleProperties gradleProperties) {
         GradleLauncherActionExecuter<ProviderOperationParameters> executer = createExecuter(operationParameters);
-        ConfiguringBuildAction<T> configuringAction = new ConfiguringBuildAction<T>(operationParameters, action);
+        ConfiguringBuildAction<T> configuringAction = new ConfiguringBuildAction<T>(operationParameters, action, gradleProperties);
         return executer.execute(configuringAction, operationParameters);
     }
 
     private GradleLauncherActionExecuter<ProviderOperationParameters> createExecuter(ProviderOperationParameters operationParameters) {
         LoggingServiceRegistry loggingServices;
-        DaemonParameters daemonParams = init(operationParameters);
+        DaemonParameters daemonParams = init(operationParameters, initGradleProperties(operationParameters));
         GradleLauncherActionExecuter<BuildActionParameters> executer;
         if (Boolean.TRUE.equals(operationParameters.isEmbedded())) {
             loggingServices = embeddedExecuterSupport.getLoggingServices();
@@ -156,14 +159,10 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
         return new LoggingBridgingGradleLauncherActionExecuter(new DaemonGradleLauncherActionExecuter(executer, daemonParams), loggingManagerFactory);
     }
 
-    private DaemonParameters init(ProviderOperationParameters operationParameters) {
-        File gradleUserHomeDir = GUtil.elvis(operationParameters.getGradleUserHomeDir(), StartParameter.DEFAULT_GRADLE_USER_HOME);
+    private DaemonParameters init(ProviderOperationParameters operationParameters, GradleProperties gradleProperties) {
         DaemonParameters daemonParams = new DaemonParameters();
 
-        boolean searchUpwards = operationParameters.isSearchUpwards() != null ? operationParameters.isSearchUpwards() : true;
-        daemonParams.configureFromBuildDir(operationParameters.getProjectDir(), searchUpwards);
-        daemonParams.configureFromGradleUserHome(gradleUserHomeDir);
-        daemonParams.configureFromSystemProperties(System.getProperties());
+        daemonParams.configureFrom(gradleProperties);
 
         //override the params with the explicit settings provided by the tooling api
         List<String> defaultJvmArgs = daemonParams.getAllJvmArgs();
@@ -178,4 +177,11 @@ public class DefaultConnection implements InternalConnection, BuildActionRunner,
         return daemonParams;
     }
 
+    private GradleProperties initGradleProperties(ProviderOperationParameters operationParameters) {
+        File gradleUserHomeDir = GUtil.elvis(operationParameters.getGradleUserHomeDir(), StartParameter.DEFAULT_GRADLE_USER_HOME);
+        boolean searchUpwards = operationParameters.isSearchUpwards() != null ? operationParameters.isSearchUpwards() : true;
+        return new GradlePropertiesConfigurer()
+                .prepareProperties(operationParameters.getProjectDir(), searchUpwards, gradleUserHomeDir, System.getProperties());
+    }
+
 }
\ No newline at end of file
diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixIn.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixIn.java
index eddfd98..b799440 100644
--- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixIn.java
+++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixIn.java
@@ -33,7 +33,7 @@ public class BuildLogLevelMixIn {
 
     public LogLevel getBuildLogLevel() {
         LoggingCommandLineConverter converter = new LoggingCommandLineConverter();
-        CommandLineParser parser = new CommandLineParser().allowUnknownOptions();
+        CommandLineParser parser = new CommandLineParser().allowUnknownOptions().allowMixedSubcommandsAndOptions();
         converter.configure(parser);
         ParsedCommandLine parsedCommandLine = parser.parse(parameters.getArguments(Collections.<String>emptyList()));
         //configure verbosely only if arguments do not specify any log level.
@@ -44,5 +44,4 @@ public class BuildLogLevelMixIn {
         LoggingConfiguration loggingConfiguration = converter.convert(parsedCommandLine);
         return loggingConfiguration.getLogLevel();
     }
-
-}
+}
\ No newline at end of file
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 510fa04..5ccf12e 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
@@ -24,6 +24,7 @@ import org.gradle.launcher.daemon.bootstrap.DaemonMain
 import org.gradle.launcher.daemon.client.DaemonClient
 import org.gradle.launcher.daemon.client.SingleUseDaemonClient
 import org.gradle.launcher.daemon.configuration.DaemonParameters
+import org.gradle.launcher.daemon.configuration.GradlePropertiesConfigurer
 import org.gradle.launcher.exec.InProcessGradleLauncherActionExecuter
 import org.gradle.logging.ProgressLoggerFactory
 import org.gradle.logging.internal.OutputEventListener
@@ -39,14 +40,17 @@ class BuildActionsFactoryTest extends Specification {
     TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     final CommandLineConverter<StartParameter> startParameterConverter = Mock()
     final ServiceRegistry loggingServices = Mock()
-    final BuildActionsFactory factory = new BuildActionsFactory(loggingServices, startParameterConverter)
+    final GradlePropertiesConfigurer configurer = Stub()
+    final BuildActionsFactory factory = new BuildActionsFactory(loggingServices, startParameterConverter, configurer)
 
     def setup() {        
         _ * loggingServices.get(OutputEventListener) >> Mock(OutputEventListener)
         _ * loggingServices.get(ProgressLoggerFactory) >> Mock(ProgressLoggerFactory)
     }
 
-    def executesBuild() {
+    def "executes build"() {
+        configurer.configureParameters(_ as StartParameter) >> new DaemonParameters()
+
         when:
         def action = convert('args')
 
@@ -54,55 +58,49 @@ class BuildActionsFactoryTest extends Specification {
         isInProcess(action)
     }
 
-    def executesBuildUsingDaemon() {
-        when:
-        def action = convert('--daemon', 'args')
-
-        then:
-        isDaemon action
-    }
+    def "by default daemon is not used"() {
+        configurer.configureParameters(_) >> new DaemonParameters().setEnabled(false)
 
-    def executesBuildUsingDaemonWhenSystemPropertyIsSetToTrue() {
         when:
-        System.properties['org.gradle.daemon'] = 'false'
         def action = convert('args')
 
         then:
         isInProcess action
+    }
+
+    def "daemon is used when command line option is used"() {
+        configurer.configureParameters(_) >> new DaemonParameters().setEnabled(false)
 
         when:
-        System.properties['org.gradle.daemon'] = 'true'
-        action = convert('args')
+        def action = convert('--daemon', 'args')
 
         then:
         isDaemon action
     }
 
-    def doesNotUseDaemonWhenNoDaemonOptionPresent() {
-        when:
-        def action = convert('--no-daemon', 'args')
-
-        then:
-        isInProcess action
-    }
+    def "daemon is used when daemon parameters say so"() {
+        configurer.configureParameters(_) >> new DaemonParameters().setEnabled(true)
 
-    def daemonOptionTakesPrecedenceOverSystemProperty() {
         when:
-        System.properties['org.gradle.daemon'] = 'false'
-        def action = convert('--daemon', 'args')
+        def action = convert('args')
 
         then:
         isDaemon action
+    }
+
+    def "does not use daemon when no-daemon command line option issued"() {
+        configurer.configureParameters(_) >> new DaemonParameters().setEnabled(true)
 
         when:
-        System.properties['org.gradle.daemon'] = 'true'
-        action = convert('--no-daemon', 'args')
+        def action = convert('--no-daemon', 'args')
 
         then:
         isInProcess action
     }
 
-    def stopsDaemon() {
+    def "stops daemon"() {
+        configurer.configureParameters(_) >> new DaemonParameters()
+
         when:
         def action = convert('--stop')
 
@@ -111,7 +109,9 @@ class BuildActionsFactoryTest extends Specification {
         action.runnable instanceof StopDaemonAction
     }
 
-    def runsDaemonInForeground() {
+    def "runs daemon in foreground"() {
+        configurer.configureParameters(_) >> new DaemonParameters()
+
         when:
         def action = convert('--foreground')
 
@@ -120,65 +120,17 @@ class BuildActionsFactoryTest extends Specification {
         action.runnable instanceof DaemonMain
     }
 
-    def executesBuildWithSingleUseDaemonIfJavaHomeIsNotCurrent() {
-        when:
+    def "executes with single use daemon if java home is not current"() {
+        given:
         def javaHome = tmpDir.createDir("javahome")
         javaHome.createFile(OperatingSystem.current().getExecutableName("bin/java"))
-
-        System.properties['org.gradle.java.home'] = javaHome.canonicalPath
-        def action = convert()
-
-        then:
-        isSingleUseDaemon action
-    }
-
-    def "daemon setting in precedence is system prop, user home then project directory"() {
-        given:
-        def userHome = tmpDir.createDir("user_home")
-        userHome.file("gradle.properties").withOutputStream { outstr ->
-            new Properties((DaemonParameters.DAEMON_SYS_PROPERTY): 'false').store(outstr, "HEADER")
-        }
-        def projectDir = tmpDir.createDir("project_dir")
-        projectDir.createFile("settings.gradle")
-        projectDir.file("gradle.properties").withOutputStream { outstr ->
-            new Properties((DaemonParameters.DAEMON_SYS_PROPERTY): 'true').store(outstr, "HEADER")
-        }
+        configurer.configureParameters(_) >> new DaemonParameters().setJavaHome(javaHome.canonicalFile)
 
         when:
         def action = convert()
 
         then:
-        startParameterConverter.convert(!null, !null) >> { args, startParam ->
-            startParam.currentDir = projectDir
-        }
-
-        and:
-        isDaemon action
-
-        when:
-        action = convert()
-
-        then:
-        startParameterConverter.convert(!null, !null) >> { args, startParam ->
-            startParam.gradleUserHomeDir = userHome
-            startParam.currentDir = projectDir
-        }
-
-        and:
-        isInProcess action
-
-        when:
-        System.properties['org.gradle.daemon'] = 'true'
-        action = convert()
-
-        then:
-        startParameterConverter.convert(!null, !null) >> { args, startParam ->
-            startParam.gradleUserHomeDir = userHome
-            startParam.currentDir = projectDir
-        }
-
-        and:
-        isDaemon action
+        isSingleUseDaemon action
     }
 
     def convert(String... args) {
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
index a1ead12..e89cc05 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/DaemonParametersTest.groovy
@@ -16,130 +16,52 @@
 package org.gradle.launcher.daemon.configuration
 
 import org.gradle.StartParameter
-import org.gradle.api.GradleException
 import org.gradle.internal.jvm.Jvm
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.junit.Rule
 import spock.lang.Specification
 
+import static java.lang.Boolean.parseBoolean
+
 class DaemonParametersTest extends Specification {
-    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
     final DaemonParameters parameters = new DaemonParameters()
 
     def "has reasonable default values"() {
         expect:
-        !parameters.enabled
-        parameters.idleTimeout == DaemonParameters.DEFAULT_IDLE_TIMEOUT
-        def baseDir = new File(StartParameter.DEFAULT_GRADLE_USER_HOME, "daemon")
-        parameters.baseDir == baseDir
-        parameters.systemProperties.isEmpty()
-        // Not that reasonable
-        parameters.effectiveJvmArgs.containsAll(parameters.defaultJvmArgs)
-        parameters.effectiveJvmArgs.size() == parameters.defaultJvmArgs.size() + 1 // + 1 because effective JVM args contains -Dfile.encoding
-    }
-
-    def "determines base dir from user home dir"() {
-        def userHome = new File("some-dir")
-
-        when:
-        parameters.configureFromGradleUserHome(userHome)
-
-        then:
-        parameters.baseDir == new File(userHome, "daemon").canonicalFile
+        assertDefaultValues()
     }
 
-    def "can configure base directory using system property"() {
-        when:
-        parameters.configureFromSystemProperties((DaemonParameters.BASE_DIR_SYS_PROPERTY): 'some-dir')
-
-        then:
-        parameters.baseDir == new File('some-dir').canonicalFile
-    }
-
-    def "can configure idle timeout using system property"() {
-        when:
-        parameters.configureFromSystemProperties((DaemonParameters.IDLE_TIMEOUT_SYS_PROPERTY): '4000')
-
-        then:
-        parameters.idleTimeout == 4000
-    }
-
-    def "nice message for invalid idle timeout"() {
-        when:
-        parameters.configureFromSystemProperties((DaemonParameters.IDLE_TIMEOUT_SYS_PROPERTY): 'asdf')
-
-        then:
-        def ex = thrown(GradleException)
-        ex.message.contains 'org.gradle.daemon.idletimeout'
-        ex.message.contains 'asdf'
+    def "uses default values when no specific gradle properties provided"() {
+        expect:
+        parameters.configureFrom(new GradleProperties()) //empty gradle properties
+        assertDefaultValues()
     }
 
-    def "uses default idle timeout if prop not set"() {
-        when:
-        parameters.configureFromSystemProperties(abc: 'def')
-
-        then:
-        parameters.idleTimeout == DaemonParameters.DEFAULT_IDLE_TIMEOUT
+    void assertDefaultValues() {
+        assert !parameters.enabled
+        assert parameters.idleTimeout == DaemonParameters.DEFAULT_IDLE_TIMEOUT
+        def baseDir = new File(StartParameter.DEFAULT_GRADLE_USER_HOME, "daemon")
+        assert parameters.baseDir == baseDir
+        assert parameters.systemProperties.isEmpty()
+        assert parameters.effectiveJvmArgs.containsAll(parameters.defaultJvmArgs)
+        assert parameters.effectiveJvmArgs.size() == parameters.defaultJvmArgs.size() + 1 // + 1 because effective JVM args contains -Dfile.encoding
+        assert parameters.idleTimeout == DaemonParameters.DEFAULT_IDLE_TIMEOUT
+        assert parameters.usingDefaultJvmArgs
     }
 
     def "configuring jvmargs replaces the defaults"() {
         when:
-        parameters.configureFrom([(DaemonParameters.JVM_ARGS_SYS_PROPERTY) : "-Xmx17m"])
+        parameters.configureFrom(Stub(GradleProperties) {
+            getJvmArgs() >> "-Xmx17m"
+        })
 
         then:
         parameters.effectiveJvmArgs.each { assert !parameters.defaultJvmArgs.contains(it) }
     }
 
-    def "can configure jvm args using system property"() {
-        when:
-        parameters.configureFromSystemProperties((DaemonParameters.JVM_ARGS_SYS_PROPERTY):  '-Xmx1024m -Dprop=value')
-
-        then:
-        parameters.effectiveJvmArgs.contains('-Xmx1024m')
-        !parameters.effectiveJvmArgs.contains('-Dprop=value')
-
-        parameters.systemProperties == [prop: 'value']
-    }
-
-    def "can configure jvm args using gradle.properties in root directory"() {
-        given:
-        tmpDir.createFile("settings.gradle")
-        tmpDir.file("gradle.properties").withOutputStream { outstr ->
-            new Properties((DaemonParameters.JVM_ARGS_SYS_PROPERTY): '-Xmx1024m -Dprop=value').store(outstr, "HEADER")
-        }
-
-        when:
-        parameters.configureFromBuildDir(tmpDir.testDirectory, true)
-
-        then:
-        parameters.effectiveJvmArgs.contains('-Xmx1024m')
-        !parameters.effectiveJvmArgs.contains('-Dprop=value')
-
-        parameters.systemProperties == [prop: 'value']
-    }
-
-    def "can configure idle timeout using gradle.properties in root directory"() {
-        given:
-        tmpDir.createFile("settings.gradle")
-        tmpDir.file("gradle.properties").withOutputStream { outstr ->
-            new Properties((DaemonParameters.IDLE_TIMEOUT_SYS_PROPERTY): '1450').store(outstr, "HEADER")
-        }
-
-        when:
-        parameters.configureFromBuildDir(tmpDir.testDirectory, true)
-
-        then:
-        parameters.idleTimeout == 1450
-    }
-
-    def "can configure parameters using gradle.properties in user home directory"() {
-        given:
-        tmpDir.file("gradle.properties").withOutputStream { outstr ->
-            new Properties((DaemonParameters.JVM_ARGS_SYS_PROPERTY): '-Xmx1024m -Dprop=value').store(outstr, "HEADER")
-        }
-
+    def "can configure jvm args combined with a system property"() {
         when:
-        parameters.configureFromGradleUserHome(tmpDir.testDirectory)
+        parameters.configureFrom(Stub(GradleProperties) {
+            getJvmArgs() >> '-Xmx1024m -Dprop=value'
+        })
 
         then:
         parameters.effectiveJvmArgs.contains('-Xmx1024m')
@@ -148,70 +70,11 @@ class DaemonParametersTest extends Specification {
         parameters.systemProperties == [prop: 'value']
     }
 
-    def "can enable daemon using system property"() {
-        when:
-        parameters.configureFromSystemProperties((DaemonParameters.DAEMON_SYS_PROPERTY):  'true')
-
-        then:
-        parameters.enabled
-    }
-
-    def "can disable daemon using system property"() {
-        when:
-        parameters.configureFromSystemProperties((DaemonParameters.DAEMON_SYS_PROPERTY):  'no way')
-
-        then:
-        !parameters.enabled
-    }
-
-    def "can enable daemon using gradle.properties in root directory"() {
-        given:
-        tmpDir.createFile("settings.gradle")
-        tmpDir.file("gradle.properties").withOutputStream { outstr ->
-            new Properties((DaemonParameters.DAEMON_SYS_PROPERTY): 'true').store(outstr, "HEADER")
-        }
-
-        when:
-        parameters.configureFromBuildDir(tmpDir.testDirectory, true)
-
-        then:
-        parameters.enabled
-    }
-
-    def "can configure java home"() {
-        File jdk = Jvm.current().getJavaHome()
-
-        when:
-        parameters.configureFrom([(DaemonParameters.JAVA_HOME_SYS_PROPERTY) : jdk.toString()])
-
-        then:
-        parameters.effectiveJavaHome == jdk.canonicalFile
-    }
-
-    def "nice message for dummy java home"() {
-        when:
-        parameters.configureFrom([(DaemonParameters.JAVA_HOME_SYS_PROPERTY) : "/invalid/path"])
-
-        then:
-        def ex = thrown(GradleException)
-        ex.message.contains 'org.gradle.java.home'
-        ex.message.contains '/invalid/path'
-    }
-
-    def "nice message for invalid java home"() {
-        def dummyDir = tmpDir.createDir("foobar")
-        when:
-        parameters.configureFrom([(DaemonParameters.JAVA_HOME_SYS_PROPERTY) : dummyDir.absolutePath])
-
-        then:
-        def ex = thrown(GradleException)
-        ex.message.contains 'org.gradle.java.home'
-        ex.message.contains 'foobar'
-    }
-
     def "supports 'empty' system properties"() {
         when:
-        parameters.configureFrom([(DaemonParameters.JVM_ARGS_SYS_PROPERTY) : "-Dfoo= -Dbar"])
+        parameters.configureFrom(Stub(GradleProperties) {
+            getJvmArgs() >> "-Dfoo= -Dbar"
+        })
 
         then:
         parameters.getSystemProperties() == [foo: '', bar: '']
@@ -227,35 +90,62 @@ class DaemonParametersTest extends Specification {
         then:
         !parameters.usingDefaultJvmArgs
     }
-    
-    def "knows if using default jvm args"() {
-        when:
-        parameters.configureFrom([(DaemonParameters.JAVA_HOME_SYS_PROPERTY) : Jvm.current().getJavaHome()])
-
-        then:
-        parameters.usingDefaultJvmArgs
-    }
 
     def "knows if not using default jvm args when configured"() {
         given:
         assert parameters.usingDefaultJvmArgs
 
         when:
-        parameters.configureFrom([(DaemonParameters.JVM_ARGS_SYS_PROPERTY) : "-Dfoo= -Dbar"])
+        parameters.configureFrom(Stub(GradleProperties) {
+            getJvmArgs() >> "-Dfoo= -Dbar"
+        })
 
         then:
         !parameters.usingDefaultJvmArgs
     }
 
+    def "knows if using default jvm args"() {
+        when:
+        parameters.configureFrom(Stub(GradleProperties) {
+            getJavaHome() >> Jvm.current().getJavaHome()
+            getJvmArgs() >> null
+        })
+
+        then:
+        parameters.usingDefaultJvmArgs
+    }
+
     def "can configure debug mode"() {
         when:
-        parameters.configureFrom((DaemonParameters.DEBUG_SYS_PROPERTY): flag)
+        parameters.configureFrom(Stub (GradleProperties) {
+            isDebugMode() >> parseBoolean(flag)
+        })
 
         then:
-        parameters.effectiveJvmArgs.contains("-Xdebug") == Boolean.parseBoolean(flag)
-        parameters.effectiveJvmArgs.contains("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005") == Boolean.parseBoolean(flag)
+        parameters.effectiveJvmArgs.contains("-Xdebug") == parseBoolean(flag)
+        parameters.effectiveJvmArgs.contains("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005") == parseBoolean(flag)
 
         where:
         flag << ["true", "false"]
     }
+
+    def "configures from gradle properties"() {
+        def props = Stub (GradleProperties) {
+            getJvmArgs() >> '-Xmx256m'
+            getJavaHome() >> new File("javaHome")
+            isDaemonEnabled() >> true
+            getDaemonBaseDir() >> new File("baseDir")
+            getIdleTimeout() >> 115
+        }
+
+        when:
+        parameters.configureFrom(props)
+
+        then:
+        parameters.effectiveJvmArgs.contains("-Xmx256m")
+        parameters.javaHome == new File("javaHome")
+        parameters.enabled
+        parameters.baseDir == new File("baseDir")
+        parameters.idleTimeout == 115
+    }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurerTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurerTest.groovy
new file mode 100644
index 0000000..51d4892
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesConfigurerTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.launcher.daemon.configuration
+
+import org.gradle.StartParameter
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static java.util.Collections.singletonMap
+
+/**
+ * by Szczepan Faber, created at: 1/22/13
+ */
+class GradlePropertiesConfigurerTest extends Specification {
+
+    @Rule private TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    private configurer = Spy(GradlePropertiesConfigurer)
+
+    def "prepares properties and configures parameters"() {
+        def param = Mock(StartParameter)
+        def properties = Mock(GradleProperties)
+
+        configurer.prepareProperties(_, false, _, _) >> properties
+
+        when:
+        def daemonParams = configurer.configureParameters(param);
+
+        then:
+        //daemon params created
+        daemonParams
+        //start parameter updated
+        1 * properties.updateStartParameter(param)
+        //daemon params configured from properties
+        properties.getIdleTimeout() >> 123
+        daemonParams.idleTimeout == 123
+    }
+
+    def "gradle home properties take precedence over project dir properties"() {
+        def projectDir = tmpDir.createDir("project")
+        projectDir.file("gradle.properties") << "$GradleProperties.IDLE_TIMEOUT_PROPERTY=100"
+        def gradleHome = tmpDir.createDir("gradleHome")
+        gradleHome.file("gradle.properties") << "$GradleProperties.IDLE_TIMEOUT_PROPERTY=200"
+
+        when:
+        def props = configurer.prepareProperties(projectDir, false, gradleHome, [:])
+
+        then:
+        props.idleTimeout == 200
+    }
+
+    def "system property takes precedence over gradle home"() {
+        def projectDir = tmpDir.createDir("project")
+        def gradleHome = tmpDir.createDir("gradleHome")
+        gradleHome.file("gradle.properties") << "$GradleProperties.IDLE_TIMEOUT_PROPERTY=200"
+
+        when:
+        def props = configurer.prepareProperties(projectDir, false, gradleHome, singletonMap(GradleProperties.IDLE_TIMEOUT_PROPERTY, '300'))
+
+        then:
+        props.idleTimeout == 300
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesTest.groovy
new file mode 100644
index 0000000..0b8b1aa
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/configuration/GradlePropertiesTest.groovy
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.launcher.daemon.configuration
+
+import org.gradle.api.GradleException
+import org.gradle.internal.jvm.Jvm
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.StartParameter
+
+/**
+ * by Szczepan Faber, created at: 1/22/13
+ */
+class GradlePropertiesTest extends Specification {
+
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    private properties = new GradleProperties()
+
+    def "configures from gradle home dir (using jvm args as example)"() {
+        given:
+        tmpDir.file("gradle.properties").withOutputStream { outstr ->
+            new Properties((GradleProperties.JVM_ARGS_PROPERTY): '-Xmx1024m -Dprop=value').store(outstr, "HEADER")
+        }
+
+        when:
+        properties.configureFromGradleUserHome(tmpDir.testDirectory)
+
+        then:
+        properties.jvmArgs == '-Xmx1024m -Dprop=value'
+    }
+
+    def "configures from project dir (using jvm args as example)"() {
+        given:
+        tmpDir.createFile("settings.gradle")
+        tmpDir.file("gradle.properties").withOutputStream { outstr ->
+            new Properties((GradleProperties.JVM_ARGS_PROPERTY): '-Xmx1024m -Dprop=value').store(outstr, "HEADER")
+        }
+
+        when:
+        properties.configureFromBuildDir(tmpDir.testDirectory, true)
+
+        then:
+        properties.jvmArgs == '-Xmx1024m -Dprop=value'
+    }
+
+    def "configures from system properties (using jvm args as example)"() {
+        when:
+        properties.configureFromSystemProperties((GradleProperties.JVM_ARGS_PROPERTY):  '-Xmx1024m -Dprop=value')
+
+        then:
+        properties.jvmArgs == '-Xmx1024m -Dprop=value'
+    }
+
+    def "sets default daemon base dir when configuring from gradle user home"() {
+        def userHome = new File("some-dir")
+
+        when:
+        properties.configureFromGradleUserHome(userHome)
+
+        then:
+        properties.daemonBaseDir == new File(userHome, "daemon").canonicalFile
+    }
+
+    def "configures daemon base dir when configuring from system properties"() {
+        when:
+        properties.configureFromSystemProperties((GradleProperties.BASE_DIR_PROPERTY): 'some-dir')
+
+        then:
+        properties.daemonBaseDir == new File('some-dir').canonicalFile
+    }
+
+    def "configures idle timeout"() {
+        when:
+        properties.configureFrom((GradleProperties.IDLE_TIMEOUT_PROPERTY): '4000')
+
+        then:
+        properties.idleTimeout == 4000
+    }
+
+    def "shows nice message for invalid idle timeout"() {
+        when:
+        properties.configureFrom((GradleProperties.IDLE_TIMEOUT_PROPERTY): 'asdf')
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.daemon.idletimeout'
+        ex.message.contains 'asdf'
+    }
+
+    def "configures daemon mode"() {
+        when:
+        properties.configureFrom((GradleProperties.DAEMON_ENABLED_PROPERTY):  flag)
+
+        then:
+        properties.daemonEnabled.toString() == flag
+
+        where:
+        flag << ["true", "false"]
+    }
+
+    def "configures java home"() {
+        File jdk = Jvm.current().getJavaHome()
+
+        when:
+        properties.configureFrom([(GradleProperties.JAVA_HOME_PROPERTY) : jdk.toString()])
+
+        then:
+        properties.javaHome == jdk.canonicalFile
+    }
+
+    def "shows nice message for dummy java home"() {
+        when:
+        properties.configureFrom([(GradleProperties.JAVA_HOME_PROPERTY) : "/invalid/path"])
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.java.home'
+        ex.message.contains '/invalid/path'
+    }
+
+    def "shows nice message for invalid java home"() {
+        def dummyDir = tmpDir.createDir("foobar")
+        when:
+        properties.configureFrom([(GradleProperties.JAVA_HOME_PROPERTY) : dummyDir.absolutePath])
+
+        then:
+        def ex = thrown(GradleException)
+        ex.message.contains 'org.gradle.java.home'
+        ex.message.contains 'foobar'
+    }
+
+    def "configures debug mode"() {
+        when:
+        properties.configureFrom((GradleProperties.DEBUG_MODE_PROPERTY): flag)
+
+        then:
+        properties.debugMode.toString() == flag
+
+        where:
+        flag << ["true", "false"]
+    }
+
+    def "configures parallel mode"() {
+        when:
+        properties.configureFrom((GradleProperties.PARALLEL_PROPERTY): flag)
+
+        then:
+        properties.parallelMode.toString() == flag
+
+        where:
+        flag << ["true", "false"]
+    }
+
+    def "informs start parameter about configure on demand"() {
+        def param = Mock(StartParameter)
+
+        when:
+        properties.updateStartParameter(param)
+
+        then:
+        0 * param._
+
+        when:
+        properties.configureOnDemand = true
+        properties.updateStartParameter(param)
+
+        then:
+        1 * param.setConfigureOnDemand(true)
+        0 * _
+    }
+
+    def "informs start parameter about parallel mode"() {
+        def param = Mock(StartParameter)
+
+        when:
+        properties.updateStartParameter(param)
+
+        then:
+        0 * param._
+
+        when:
+        properties.parallelMode = true
+        properties.updateStartParameter(param)
+
+        then:
+        1 * param.setParallelThreadCount(-1)
+    }
+
+    def "does not set parallel mode when it was already configured even parallel mode is requested"() {
+        def param = Mock(StartParameter) {
+            isParallelThreadCountConfigured() >> true
+        }
+
+        when:
+        properties.parallelMode = true //requested
+        properties.updateStartParameter(param)
+
+        then:
+        0 * param.setParallelThreadCount(_)
+    }
+}
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
index e5c706f..5e8b6fc 100644
--- a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/ConfiguringBuildActionTest.groovy
@@ -72,4 +72,16 @@ class ConfiguringBuildActionTest extends Specification {
         then:
         !start.isSearchUpwards()
     }
+
+    def "can overwrite configure on demand via build arguments"() {
+        expect:
+        !new ConfiguringBuildAction().configureStartParameter().configureOnDemand
+
+        when:
+        def action = new ConfiguringBuildAction(arguments: ['--configure-on-demand'])
+        def start = action.configureStartParameter()
+
+        then:
+        start.configureOnDemand
+    }
 }
diff --git a/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixInTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixInTest.groovy
new file mode 100644
index 0000000..105d527
--- /dev/null
+++ b/subprojects/launcher/src/test/groovy/org/gradle/tooling/internal/provider/connection/BuildLogLevelMixInTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.tooling.internal.provider.connection
+
+import org.gradle.api.logging.LogLevel
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/18/13
+ */
+class BuildLogLevelMixInTest extends Specification {
+
+    final parameters = Mock(ProviderOperationParameters)
+    final mixin = new BuildLogLevelMixIn(parameters)
+
+    def "knows build log level for mixed set of arguments"() {
+        when:
+        parameters.getArguments([]) >> args
+        parameters.getVerboseLogging(false) >> false
+
+        then:
+        mixin.getBuildLogLevel() == logLevel
+
+        where:
+        args                     | logLevel
+        ['-i']                   | LogLevel.INFO
+        ['-q']                   | LogLevel.QUIET
+        ['foo', '--info', 'bar'] | LogLevel.INFO
+        ['-i', 'foo', 'bar']     | LogLevel.INFO
+        ['foo', 'bar', '-i']     | LogLevel.INFO
+    }
+
+    def "verbose flag is only used when no log level arguments"() {
+        when:
+        parameters.getArguments([]) >> args
+        parameters.getVerboseLogging(false) >> verbose
+
+        then:
+        mixin.getBuildLogLevel() == logLevel
+
+        where:
+        args                     | verbose | logLevel
+        ['-q']                   | false   | LogLevel.QUIET
+        ['-q']                   | true    | LogLevel.QUIET
+        ['noLogLevelArguments']  | true    | LogLevel.DEBUG
+    }
+
+    def "default log level is lifecycle"() {
+        when:
+        parameters.getArguments([]) >> ['no log level arguments']
+        parameters.getVerboseLogging(false) >> false
+
+        then:
+        mixin.getBuildLogLevel() == LogLevel.LIFECYCLE
+    }
+}
diff --git a/subprojects/maven/maven.gradle b/subprojects/maven/maven.gradle
index 0e62608..5aed01a 100644
--- a/subprojects/maven/maven.gradle
+++ b/subprojects/maven/maven.gradle
@@ -27,6 +27,8 @@ dependencies {
     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"
+
+    integTestCompile project(":ear")
 }
 
 useTestFixtures()
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
index 7345d60..8233c72 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/plugins/maven/MavenConversionIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.api.plugins.maven
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 
@@ -28,7 +28,7 @@ import static org.gradle.util.TextUtil.toPlatformLineSeparators
  */
 class MavenConversionIntegrationTest extends AbstractIntegrationSpec {
 
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(temporaryFolder)
 
     def "multiModule"() {
         given:
@@ -49,7 +49,7 @@ class MavenConversionIntegrationTest extends AbstractIntegrationSpec {
         file("webinar-war/build/libs/webinar-war-1.0-SNAPSHOT.war").exists()
         file('webinar-impl/build/reports/tests/index.html').exists()
 
-        new DefaultTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
+        new JUnitXmlTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
 
         when:
         run 'projects'
@@ -84,7 +84,7 @@ Root project 'webinar-parent'
         file("webinar-war/build/libs/webinar-war-1.0-SNAPSHOT.war").exists()
         file('webinar-impl/build/reports/tests/index.html').exists()
 
-        new DefaultTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
+        new JUnitXmlTestExecutionResult(file("webinar-impl")).assertTestClassesExecuted('webinar.WebinarTest')
 
         when:
         executer.inDirectory(file("webinar-parent"))
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/AbstractMavenPublishIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/AbstractMavenPublishIntegTest.groovy
new file mode 100644
index 0000000..ab9f75b
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/AbstractMavenPublishIntegTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api.publish.maven
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.maven.MavenFileModule
+
+class AbstractMavenPublishIntegTest extends AbstractIntegrationSpec {
+
+    protected def resolveArtifact(MavenFileModule module, def extension) {
+        doResolveArtifacts("""
+    dependencies {
+        resolve group: '${sq(module.groupId)}', name: '${sq(module.artifactId)}', version: '${sq(module.version)}', ext: '${sq(extension)}'
+    }
+""")
+    }
+    protected def resolveArtifact(MavenFileModule module, def extension, def classifier) {
+        doResolveArtifacts("""
+    dependencies {
+        resolve group: '${sq(module.groupId)}', name: '${sq(module.artifactId)}', version: '${sq(module.version)}', classifier: '${sq(classifier)}', ext: '${sq(extension)}'
+    }
+""")
+    }
+
+    protected def resolveArtifacts(MavenFileModule module) {
+        doResolveArtifacts("""
+    dependencies {
+        resolve group: '${sq(module.groupId)}', name: '${sq(module.artifactId)}', version: '${sq(module.version)}'
+    }
+""")
+    }
+
+    protected def resolveArtifacts(MavenFileModule module, Map... additionalArtifacts) {
+        def dependencies = """
+    dependencies {
+        resolve group: '${sq(module.groupId)}', name: '${sq(module.artifactId)}', version: '${sq(module.version)}'
+        resolve(group: '${sq(module.groupId)}', name: '${sq(module.artifactId)}', version: '${sq(module.version)}') {
+"""
+        additionalArtifacts.each {
+            // TODO:DAZ Docs say type defaults to 'jar', but seems it must be set explicitly
+            def type = it.type == null ? 'jar' : it.type
+            dependencies += """
+            artifact {
+                name = '${sq(module.artifactId)}' // TODO:DAZ Get NPE if name isn't set
+                classifier = '${it.classifier}'
+                type = '${type}'
+            }
+"""
+        }
+        dependencies += """
+        }
+    }
+"""
+        doResolveArtifacts(dependencies)
+    }
+
+    protected def doResolveArtifacts(def dependencies) {
+        // Replace the existing buildfile with one for resolving the published module
+        // TODO:DAZ Use a separate directory for resolving
+        settingsFile.text = "rootProject.name = 'resolve'"
+        buildFile.text = """
+            configurations {
+                resolve
+            }
+            repositories {
+                maven { url "${mavenRepo.uri}" }
+                mavenCentral()
+            }
+            $dependencies
+            task resolveArtifacts(type: Sync) {
+                from configurations.resolve
+                into "artifacts"
+            }
+
+"""
+
+        // TODO:DAZ Remove this requirement (by always publishing a jar/war/ear in tests?: Maven doesn't really support other file types as main artifact)
+        executer.withDeprecationChecksDisabled()
+        run "resolveArtifacts"
+        def artifactsList = file("artifacts").exists() ? file("artifacts").list() : []
+        return artifactsList.sort()
+    }
+
+
+    String sq(String input) {
+        return escapeForSingleQuoting(input)
+    }
+
+    String escapeForSingleQuoting(String input) {
+        return input.replace('\\', '\\\\').replace('\'', '\\\'')
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomisationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomisationIntegTest.groovy
new file mode 100644
index 0000000..317f542
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishArtifactCustomisationIntegTest.groovy
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven
+
+class MavenPublishArtifactCustomisationIntegTest extends AbstractMavenPublishIntegTest {
+
+    def "can attach custom artifacts"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact "customFile.txt"
+                    artifact customJar
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.parsedPom.packaging == "txt"
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-customjar.jar")
+
+        and:
+        resolveArtifacts(module, [classifier: 'customjar']) == ["projectText-1.0-customjar.jar", "projectText-1.0.txt"]
+    }
+
+    def "can set custom artifacts to override component artifacts"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    from components.java
+                    artifacts = ["customFile.txt", customJar]
+                }
+            }
+
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.parsedPom.packaging == "txt"
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-customjar.jar")
+
+        and:
+        resolveArtifacts(module, [classifier: 'customjar']) == ["projectText-1.0-customjar.jar", "projectText-1.0.txt"]
+    }
+
+    def "can configure custom artifacts when creating"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact("customFile.txt") {
+                        classifier "output"
+                    }
+                    artifact(customFileTask.outputFile) {
+                        extension "htm"
+                        classifier "documentation"
+                        builtBy customFileTask
+                    }
+                    artifact customJar {
+                        classifier null
+                        extension "war"
+                    }
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.parsedPom.packaging == "war"
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.war", "projectText-1.0-documentation.htm", "projectText-1.0-output.txt")
+
+        and:
+        resolveArtifacts(module, [classifier: 'documentation', type: 'htm'], [classifier: 'output', type: 'txt']) == ["projectText-1.0-documentation.htm", "projectText-1.0-output.txt", "projectText-1.0.war"]
+    }
+
+    def "can attach custom file artifacts with map notation"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact source: "customFile.txt", classifier: "output"
+                    artifact source: customFileTask.outputFile, extension: "htm", classifier: "documentation", builtBy: customFileTask
+                    artifact source: customJar, extension: "war", classifier: null
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.parsedPom.packaging == "war"
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.war", "projectText-1.0-documentation.htm", "projectText-1.0-output.txt")
+
+        and:
+        resolveArtifacts(module, [classifier: 'documentation', type: 'htm'], [classifier: 'output', type: 'txt']) == ["projectText-1.0-documentation.htm", "projectText-1.0-output.txt", "projectText-1.0.war"]
+    }
+
+    def "can configure custom artifacts post creation"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact "customFile.txt"
+                    artifact customFileTask.outputFile
+                    artifact customJar
+                }
+            }
+""", """
+            publishing.publications.mavenCustom.artifacts.each {
+                if (it.extension == "html") {
+                    it.classifier = "docs"
+                    it.builtBy customFileTask
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.txt", "projectText-1.0-docs.html", "projectText-1.0-customjar.jar")
+    }
+
+    def "can attach artifact with no extension"() {
+        given:
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    from components.java
+                    artifact source: "customFile.txt", extension: "", classifier: "classified"
+                }
+            }
+""")
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module("group", "projectText", "1.0")
+        module.assertPublished()
+        module.assertArtifactsPublished("projectText-1.0.pom", "projectText-1.0.jar", "projectText-1.0-classified")
+
+        // TODO:DAZ Find a way to resolve Maven artifact with no extension
+//        and:
+//        resolveArtifact(module, '', 'classified') == ["projectText-1.0-classifier"]
+    }
+
+    def "reports failure publishing when validation fails"() {
+        given:
+        file("a-directory.dir").createDir()
+
+        createBuildScripts("""
+            publications {
+                mavenCustom(MavenPublication) {
+                    artifact "a-directory.dir"
+                }
+            }
+""")
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishMavenCustomPublicationToMavenRepository'.")
+        failure.assertHasCause("Failed to publish publication 'mavenCustom' to repository 'maven'")
+        failure.assertHasCause("Invalid publication 'mavenCustom': artifact file is a directory")
+    }
+
+    private createBuildScripts(def publications, def append = "") {
+        settingsFile << "rootProject.name = 'projectText'"
+        buildFile << """
+            apply plugin: 'java'
+            apply plugin: 'maven-publish'
+
+            group = 'group'
+            version = '1.0'
+
+            file("customFile.txt") << 'some content'
+
+            task customFileTask {
+                ext.outputFile = file('customFile-1.0-docs.html')
+                doLast {
+                    outputFile << '<html/>'
+                }
+            }
+
+            task customJar(type: Jar) {
+                from file("customFile.txt")
+                classifier "customjar"
+            }
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                $publications
+            }
+
+            $append
+        """
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishBasicIntegTest.groovy
index 91d8af7..2c12e43 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
@@ -16,28 +16,26 @@
 
 package org.gradle.api.publish.maven
 
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.test.fixtures.maven.M2Installation
 import org.gradle.test.fixtures.maven.MavenFileRepository
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
-
+import spock.lang.Ignore
 /**
  * Tests “simple” maven publishing scenarios
  */
-class MavenPublishBasicIntegTest extends AbstractIntegrationSpec {
+class MavenPublishBasicIntegTest extends AbstractMavenPublishIntegTest {
     @Rule SetSystemProperties sysProp = new SetSystemProperties()
 
-    M2Installation m2Installation
     MavenFileRepository m2Repo
 
     def "setup"() {
-        m2Installation = new M2Installation(testDirectory)
-        using m2Installation
+        def m2Installation = new M2Installation(testDirectory)
         m2Repo = m2Installation.mavenRepo()
+        executer.beforeExecute m2Installation
     }
 
-    def "publishes nothing without component"() {
+    def "publishes nothing without defined publication"() {
         given:
         settingsFile << "rootProject.name = 'root'"
         buildFile << """
@@ -59,8 +57,42 @@ class MavenPublishBasicIntegTest extends AbstractIntegrationSpec {
         mavenRepo.module('group', 'root', '1.0').assertNotPublished()
     }
 
+    def "publishes empty pom when publication has no added component"() {
+        given:
+        settingsFile << "rootProject.name = 'empty-project'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+
+            group = 'org.gradle.test'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication)
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module('org.gradle.test', 'empty-project', '1.0')
+        module.assertPublished()
+        module.parsedPom.scopes.isEmpty()
+
+        and:
+        resolveArtifacts(module) == []
+    }
+
     def "can publish simple jar"() {
         given:
+        def repoModule = mavenRepo.module('group', 'root', '1.0')
+        def localModule = m2Repo.module('group', 'root', '1.0')
+
+        and:
         settingsFile << "rootProject.name = 'root'"
         buildFile << """
             apply plugin: 'maven-publish'
@@ -73,6 +105,11 @@ class MavenPublishBasicIntegTest extends AbstractIntegrationSpec {
                 repositories {
                     maven { url "${mavenRepo.uri}" }
                 }
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                    }
+                }
             }
         """
 
@@ -80,21 +117,119 @@ class MavenPublishBasicIntegTest extends AbstractIntegrationSpec {
         succeeds 'assemble'
 
         then: "jar is built but not published"
-        mavenRepo.module('group', 'root', '1.0').assertNotPublished()
-        m2Repo.module('group', 'root', '1.0').assertNotPublished()
+        repoModule.assertNotPublished()
+        localModule.assertNotPublished()
         file('build/libs/root-1.0.jar').assertExists()
 
         when:
         succeeds 'publish'
 
         then: "jar is published to defined maven repository"
-        mavenRepo.module('group', 'root', '1.0').assertPublishedAsJavaModule()
-        m2Repo.module('group', 'root', '1.0').assertNotPublished()
+        repoModule.assertPublishedAsJavaModule()
+        localModule.assertNotPublished()
 
         when:
         succeeds 'publishToMavenLocal'
 
         then: "jar is published to maven local repository"
-        m2Repo.module('group', 'root', '1.0').assertPublishedAsJavaModule()
+        localModule.assertPublishedAsJavaModule()
+
+        and:
+        resolveArtifacts(repoModule) == ['root-1.0.jar']
+    }
+
+    def "can publish a snapshot version"() {
+        settingsFile << 'rootProject.name = "snapshotPublish"'
+        buildFile << """
+    apply plugin: 'java'
+    apply plugin: 'maven-publish'
+
+    group = 'org.gradle'
+    version = '1.0-SNAPSHOT'
+
+    publishing {
+        repositories {
+            maven { url "${mavenRepo.uri}" }
+        }
+        publications {
+            pub(MavenPublication) {
+                from components.java
+            }
+        }
+    }
+"""
+
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module('org.gradle', 'snapshotPublish', '1.0-SNAPSHOT')
+        module.assertArtifactsPublished("snapshotPublish-${module.publishArtifactVersion}.jar", "snapshotPublish-${module.publishArtifactVersion}.pom", "maven-metadata.xml")
+
+        and:
+        resolveArtifacts(module) == ["snapshotPublish-${module.publishArtifactVersion}.jar"]
+    }
+
+    def "reports failure publishing when model validation fails"() {
+        given:
+        settingsFile << "rootProject.name = 'bad-project'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'war'
+
+            group = 'org.gradle.test'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                        from components.web
+                    }
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("A problem occurred configuring the 'publishing' extension")
+        failure.assertHasCause("Maven publication 'maven' cannot include multiple components")
+    }
+
+    @Ignore("Not yet implemented - currently the second publication will overwrite") // TODO:DAZ fix in validation story
+    def "cannot publish multiple maven publications with the same identity"() {
+        given:
+        settingsFile << "rootProject.name = 'bad-project'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'war'
+
+            group = 'org.gradle.test'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    mavenJava(MavenPublication) {
+                        from components.java
+                    }
+                    mavenWeb(MavenPublication) {
+                        from components.web
+                    }
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("A problem occurred evaluating root project 'bad-project'")
+        failure.assertHasCause("Publication with name 'mavenJava' already exists")
     }
 }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy
index 1c704ac..cafeb28 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishCrossVersionIntegrationTest.groovy
@@ -16,25 +16,39 @@
 package org.gradle.api.publish.maven
 
 import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec
+import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.test.fixtures.maven.MavenFileRepository
 
+ at TargetVersions('0.9+')
 class MavenPublishCrossVersionIntegrationTest extends CrossVersionIntegrationSpec {
 
     final MavenFileRepository repo = new MavenFileRepository(file("maven-repo"))
 
-    def "maven publication generated by maven-publish plugin can be consumed by previous versions of Gradle"() {
+    def "maven java publication generated by maven-publish plugin can be consumed by previous versions of Gradle"() {
         given:
-        projectPublishedUsingMavenPublishPlugin()
+        projectPublishedUsingMavenPublishPlugin('java')
 
         expect:
-        canConsumePublicationWithPreviousVersion()
+        consumePublicationWithPreviousVersion('')
+
+        file('build/resolved').assertHasDescendants('published-1.9.jar', 'commons-collections-3.0.jar')
     }
 
-    def projectPublishedUsingMavenPublishPlugin() {
+    def "maven war publication generated by maven-publish plugin can be consumed by previous versions of Gradle"() {
+        given:
+        projectPublishedUsingMavenPublishPlugin('web')
+
+        expect:
+        consumePublicationWithPreviousVersion('@war')
+
+        file('build/resolved').assertHasDescendants('published-1.9.war')
+    }
+
+    def projectPublishedUsingMavenPublishPlugin(def componentToPublish) {
         settingsFile.text = "rootProject.name = 'published'"
 
         buildFile.text = """
-apply plugin: 'java'
+apply plugin: 'war'
 apply plugin: 'maven-publish'
 
 group = 'org.gradle.crossversion'
@@ -50,13 +64,18 @@ publishing {
     repositories {
         maven { url "${repo.uri}" }
     }
+    publications {
+        maven(MavenPublication) {
+            from components['${componentToPublish}']
+        }
+    }
 }
 """
 
         version current withTasks 'publish' run()
     }
 
-    def canConsumePublicationWithPreviousVersion() {
+    def consumePublicationWithPreviousVersion(def artifact) {
         settingsFile.text = "rootProject.name = 'consumer'"
 
         buildFile.text = """
@@ -68,16 +87,14 @@ repositories {
     mavenRepo(urls: ['${repo.uri}'])
 }
 dependencies {
-    lib 'org.gradle.crossversion:published:1.9'
+    lib 'org.gradle.crossversion:published:1.9$artifact'
 }
-task retrieve(type: Copy) {
+task retrieve(type: Sync) {
     into 'build/resolved'
     from configurations.lib
 }
 """
 
         version previous withDeprecationChecksDisabled() withTasks 'retrieve' run()
-
-        file('build/resolved').assertHasDescendants('published-1.9.jar', 'commons-collections-3.0.jar')
     }
 }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishEarIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishEarIntegTest.groovy
new file mode 100644
index 0000000..48ad12a
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishEarIntegTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven
+
+class MavenPublishEarIntegTest extends AbstractMavenPublishIntegTest {
+    public void "can publish ear module"() {
+        def earModule = mavenRepo.module("org.gradle.test", "publishEar", "1.9")
+
+        given:
+        settingsFile << "rootProject.name = 'publishEar' "
+
+        and:
+        buildFile << """
+apply plugin: 'ear'
+apply plugin: 'war'
+apply plugin: 'maven-publish'
+
+group = 'org.gradle.test'
+version = '1.9'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "commons-collections:commons-collections:3.2.1"
+    runtime "commons-io:commons-io:1.4"
+}
+
+publishing {
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+    publications {
+        mavenEar(MavenPublication) {
+            artifact ear
+        }
+    }
+}
+"""
+
+        when:
+        succeeds 'assemble'
+
+        then: "ear is built but not published"
+        earModule.assertNotPublished()
+        file('build/libs/publishEar-1.9.ear').assertExists()
+
+        when:
+        run "publish"
+
+        then:
+        earModule.assertPublishedAsEarModule()
+        earModule.parsedPom.scopes.isEmpty()
+
+        and:
+        resolveArtifacts(earModule) == ["publishEar-1.9.ear"]
+    }
+}
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 43721f9..3ece02b 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
@@ -18,7 +18,6 @@ package org.gradle.api.publish.maven
 
 import org.gradle.api.internal.artifacts.repositories.DefaultPasswordCredentials
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.executer.ProgressLoggingFixture
 import org.gradle.test.fixtures.maven.MavenHttpModule
 import org.gradle.test.fixtures.maven.MavenHttpRepository
 import org.gradle.test.fixtures.server.http.HttpServer
@@ -29,7 +28,6 @@ import spock.lang.Unroll
 class MavenPublishHttpIntegTest extends AbstractIntegrationSpec {
 
     @Rule HttpServer server
-    @Rule ProgressLoggingFixture progressLogging
 
     MavenHttpRepository mavenRemoteRepo
     MavenHttpModule module
@@ -61,6 +59,11 @@ class MavenPublishHttpIntegTest extends AbstractIntegrationSpec {
                         url "$mavenRemoteRepo.uri"
                     }
                 }
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                    }
+                }
             }
         """
     }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIdentifierValidationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIdentifierValidationIntegTest.groovy
new file mode 100644
index 0000000..808a064
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIdentifierValidationIntegTest.groovy
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven
+import org.gradle.test.fixtures.publish.Identifier
+import spock.lang.Unroll
+
+class MavenPublishIdentifierValidationIntegTest extends AbstractMavenPublishIntegTest {
+
+    // Group and Artifact are restricted to [A-Za-z0-9_\-.]+ by org.apache.maven.project.validation.DefaultModelValidator
+    def groupId = 'a-valid.group'
+    def artifactId = 'valid_artifact.name'
+
+    @Unroll
+    def "can publish with version and description containing #title characters"() {
+        given:
+        def version = identifier.safeForFileName().decorate("version")
+        def description = identifier.decorate("description")
+        settingsFile << "rootProject.name = '${artifactId}'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = '${groupId}'
+            version = '${sq(version)}'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                        pom.withXml {
+                            asNode().appendNode('description', '${sq(description)}')
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module(groupId, artifactId, version)
+        module.assertPublishedAsJavaModule()
+        module.parsedPom.description == description
+
+        and:
+        resolveArtifacts(module) == ["${artifactId}-${version}.jar"]
+
+        where:
+        title        | identifier
+        "punctuation"| Identifier.punctuation
+        "non-ascii"  | Identifier.nonAscii
+        "whitespace" | Identifier.whiteSpace
+        "filesystem" | Identifier.fileSystemReserved
+        "xml markup" | Identifier.xmlMarkup
+    }
+
+    @Unroll
+    def "can publish artifacts with version, extension and classifier containing #title characters"() {
+        given:
+        file("content-file") << "some content"
+        def version = identifier.safeForFileName().decorate("version")
+        def extension = identifier.safeForFileName().decorate("extension")
+        def classifier = identifier.safeForFileName().decorate("classifier")
+
+        and:
+        settingsFile << "rootProject.name = '${artifactId}'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = '${groupId}'
+            version = '${sq(version)}'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                        artifact source: 'content-file', extension: '${sq(extension)}', classifier: '${sq(classifier)}'
+                    }
+                }
+            }
+        """
+        when:
+        succeeds 'publish'
+
+        then:
+        def module = mavenRepo.module(groupId, artifactId, version)
+        module.assertPublished()
+
+        module.assertArtifactsPublished("${artifactId}-${version}.pom", "${artifactId}-${version}.jar", "${artifactId}-${version}-${classifier}.${extension}")
+
+        and:
+        resolveArtifact(module, extension, classifier) == ["${artifactId}-${version}-${classifier}.${extension}"]
+
+        where:
+        title        | identifier
+        "punctuation"| Identifier.punctuation
+        "non-ascii"  | Identifier.nonAscii
+        "whitespace" | Identifier.whiteSpace
+        "filesystem" | Identifier.fileSystemReserved
+        "xml markup" | Identifier.xmlMarkup
+    }
+
+    def "fails with reasonable error message for invalid identifier value"() {
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = ''
+            version = ''
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication)
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription "Execution failed for task ':publishMavenPublicationToMavenRepository'"
+        failure.assertHasCause "Failed to publish publication 'maven' to repository 'maven'"
+        failure.assertHasCause "Invalid publication 'maven': groupId cannot be empty"
+    }
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIssuesIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIssuesIntegTest.groovy
new file mode 100644
index 0000000..192f69f
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishIssuesIntegTest.groovy
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.maven.M2Installation
+import org.spockframework.util.TextUtil
+import spock.lang.Issue
+/**
+ * Tests for bugfixes to maven publishing scenarios
+ */
+class MavenPublishIssuesIntegTest extends AbstractIntegrationSpec {
+
+    @Issue("GRADLE-2456")
+    def "generates SHA1 file with leading zeros"() {
+        given:
+        def module = mavenRepo.module("org.gradle", "publish", "2")
+        byte[] jarBytes = [0, 0, 0, 5]
+        def artifactFile = file("testfile.bin")
+        artifactFile << jarBytes
+        def artifactPath = TextUtil.escape(artifactFile.path)
+
+        and:
+        settingsFile << 'rootProject.name = "publish"'
+        buildFile << """
+    apply plugin: 'maven-publish'
+
+    group = "org.gradle"
+    version = '2'
+
+    publishing {
+        repositories {
+            maven { url "${mavenRepo.uri}" }
+        }
+        publications {
+            pub(MavenPublication) {
+                artifact file("${artifactPath}")
+            }
+        }
+    }
+    """
+
+        when:
+        succeeds 'publish'
+
+        then:
+        def shaOneFile = module.moduleDir.file("publish-2.bin.sha1")
+        shaOneFile.exists()
+        shaOneFile.text == "00e14c6ef59816760e2c9b5a57157e8ac9de4012"
+    }
+
+    @Issue("GRADLE-2681")
+    def "gradle ignores maven mirror configuration for uploading archives"() {
+        given:
+        TestFile m2Home = temporaryFolder.createDir("m2_home");
+        M2Installation m2Installation = new M2Installation(m2Home)
+
+        m2Installation.globalSettingsFile << """
+<settings>
+  <mirrors>
+    <mirror>
+      <id>ACME</id>
+      <name>ACME Central</name>
+      <url>http://acme.maven.org/maven2</url>
+      <mirrorOf>*</mirrorOf>
+    </mirror>
+  </mirrors>
+</settings>
+"""
+
+        and:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+apply plugin: 'java'
+apply plugin: 'maven-publish'
+group = 'group'
+version = '1.0'
+
+publishing {
+    repositories {
+        maven { url "${mavenRepo.uri}" }
+    }
+    publications {
+        maven(MavenPublication) {
+            from components.java
+        }
+    }
+}
+   """
+        when:
+        using m2Installation
+
+        then:
+        succeeds "publish"
+    }
+
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy
index ef69409..498b7a9 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy
@@ -14,23 +14,109 @@
  * limitations under the License.
  */
 
+package org.gradle.api.publish.maven
 
+class MavenPublishJavaIntegTest extends AbstractMavenPublishIntegTest {
+    def mavenModule = mavenRepo.module("org.gradle.test", "publishTest", "1.9")
 
+    public void "can publish jar and meta-data to maven repository"() {
+        given:
+        createBuildScripts("""
+            publishing {
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                    }
+                }
+            }
+""")
 
-package org.gradle.api.publish.maven
+        when:
+        run "publish"
 
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+        then:
+        mavenModule.assertPublishedAsJavaModule()
 
-class MavenPublishJavaIntegTest extends AbstractIntegrationSpec {
-    public void "can publish jar and meta-data to maven repository"() {
+        mavenModule.parsedPom.scopes.keySet() == ["runtime"] as Set
+        mavenModule.parsedPom.scopes.runtime.assertDependsOn("commons-collections:commons-collections:3.2.1", "commons-io:commons-io:1.4")
+
+        and:
+        resolveArtifacts(mavenModule) == ["commons-collections-3.2.1.jar", "commons-io-1.4.jar", "publishTest-1.9.jar"]
+    }
+
+    public void "can publish attached artifacts to maven repository"() {
         given:
-        settingsFile << "rootProject.name = 'publishTest' "
+        createBuildScripts("""
+            task sourceJar(type: Jar) {
+                from sourceSets.main.allJava
+                classifier "source"
+            }
+
+            publishing {
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                        artifact sourceJar
+                    }
+                }
+            }
+""")
+
+        when:
+        run "publish"
+
+        then:
+        mavenModule.assertPublished()
+        mavenModule.assertArtifactsPublished("publishTest-1.9.jar", "publishTest-1.9.pom", "publishTest-1.9-source.jar")
+
+        and:
+        resolveArtifacts(mavenModule, [classifier: 'source']) == ["commons-collections-3.2.1.jar", "commons-io-1.4.jar", "publishTest-1.9-source.jar", "publishTest-1.9.jar"]
+    }
+
+    public void "can configure artifacts added from component"() {
+        given:
+        createBuildScripts("""
+            publishing {
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                    }
+                }
+                publications.maven.artifacts.each {
+                    if (it.extension == 'jar') {
+                        it.classifier = 'classified'
+                    }
+                }
+            }
+""")
+
+        when:
+        run "publish"
+
+        then:
+        def mavenModule = mavenRepo.module("org.gradle.test", "publishTest", "1.9")
+        mavenModule.assertPublished()
+        mavenModule.assertArtifactsPublished("publishTest-1.9-classified.jar", "publishTest-1.9.pom")
 
         and:
+        resolveArtifact(mavenModule, 'jar', 'classified') == ["publishTest-1.9-classified.jar"]
+    }
+
+    def createBuildScripts(def append) {
+        settingsFile << "rootProject.name = 'publishTest' "
+
         buildFile << """
             apply plugin: 'maven-publish'
             apply plugin: 'java'
 
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+            }
+
+$append
+
             group = 'org.gradle.test'
             version = '1.9'
 
@@ -43,26 +129,8 @@ class MavenPublishJavaIntegTest extends AbstractIntegrationSpec {
                 runtime "commons-io:commons-io:1.4"
                 testCompile "junit:junit:4.11"
             }
-
-            publishing {
-                repositories {
-                    maven { url "${mavenRepo.uri}" }
-                }
-            }
 """
 
-        when:
-        run "publish"
-
-        then:
-        def mavenModule = mavenRepo.module("org.gradle.test", "publishTest", "1.9")
-        mavenModule.assertPublishedAsJavaModule()
-
-        mavenModule.parsedPom.scopes.runtime.dependencies.size() == 2
-        mavenModule.parsedPom.scopes.runtime.assertDependsOn("commons-collections", "commons-collections", "3.2.1")
-        mavenModule.parsedPom.scopes.runtime.assertDependsOn("commons-io", "commons-io", "1.4")
-        mavenModule.parsedPom.scopes.compile == null
-        mavenModule.parsedPom.scopes.testCompile == null
     }
 
 }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy
index 3cdee56..1488ff4 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy
@@ -15,20 +15,16 @@
  */
 
 package org.gradle.api.publish.maven
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+
 import spock.lang.Ignore
 
-class MavenPublishMultiProjectIntegTest extends AbstractIntegrationSpec {
-    def project1module = mavenRepo.module("org.gradle.test", "project1", "1.9")
+class MavenPublishMultiProjectIntegTest extends AbstractMavenPublishIntegTest {
+    def project1 = mavenRepo.module("org.gradle.test", "project1", "1.0")
+    def project2 = mavenRepo.module("org.gradle.test", "project2", "2.0")
+    def project3 = mavenRepo.module("org.gradle.test", "project3", "3.0")
 
     def "project dependency correctly reflected in POM"() {
-        createBuildScripts("""
-project(":project1") {
-    dependencies {
-        compile project(":project2")
-    }
-}
-        """)
+        createBuildScripts()
 
         when:
         run "publish"
@@ -39,12 +35,6 @@ project(":project1") {
 
     def "maven-publish plugin does not take archivesBaseName into account when publishing"() {
         createBuildScripts("""
-project(":project1") {
-    dependencies {
-        compile project(":project2")
-    }
-}
-
 project(":project2") {
     archivesBaseName = "changed"
 }
@@ -59,18 +49,12 @@ project(":project2") {
 
     def "maven-publish plugin does not take mavenDeployer.pom.artifactId into account when publishing"() {
         createBuildScripts("""
-project(":project1") {
-    dependencies {
-        compile project(":project2")
-    }
-}
-
 project(":project2") {
     apply plugin: 'maven'
     uploadArchives {
         repositories {
             mavenDeployer {
-                repository(url: "file:///\$rootProject.projectDir/maven-repo")
+                repository(url: "${mavenRepo.uri}")
                 pom.artifactId = "changed"
             }
         }
@@ -86,17 +70,21 @@ project(":project2") {
     }
 
     private def projectsCorrectlyPublished() {
-        def project2 = mavenRepo.module("org.gradle.test", "project2", "1.9")
+        project1.assertPublishedAsJavaModule()
+        project1.parsedPom.scopes.runtime.assertDependsOn("org.gradle.test:project2:2.0", "org.gradle.test:project3:3.0")
+
         project2.assertPublishedAsJavaModule()
+        project2.parsedPom.scopes.runtime.assertDependsOn("org.gradle.test:project3:3.0")
+
+        project3.assertPublishedAsJavaModule()
+        project3.parsedPom.scopes == null
 
-        def project1pom = project1module.parsedPom
-        project1pom.scopes.runtime.assertDependsOn("org.gradle.test", "project2", "1.9")
+        resolveArtifacts(project1) == ["project1-1.0.jar", "project2-2.0.jar", "project3-3.0.jar"]
 
         return true
     }
 
-
-    def "maven-publish plugin will use target project archivesBaseName for project dependency when target project does not have maven-publish plugin applied"() {
+    def "maven-publish plugin uses target project name for project dependency when target project does not have maven-publish plugin applied"() {
         given:
         settingsFile << """
 include "project1", "project2"
@@ -105,25 +93,32 @@ include "project1", "project2"
         buildFile << """
 allprojects {
     group = "org.gradle.test"
-    version = 1.9
 }
 
 project(":project1") {
     apply plugin: "java"
     apply plugin: "maven-publish"
 
+    version = "1.0"
+
     dependencies {
         compile project(":project2")
     }
 
     publishing {
         repositories {
-            maven { url "file:///\$rootProject.projectDir/maven-repo" }
+            maven { url "${mavenRepo.uri}" }
+        }
+        publications {
+            maven(MavenPublication) {
+                from components.java
+            }
         }
     }
 }
 project(":project2") {
     apply plugin: 'maven'
+    version = "2.0"
     archivesBaseName = "changed"
 }
         """
@@ -132,11 +127,12 @@ project(":project2") {
         run "publish"
 
         then:
-        def project1pom = project1module.parsedPom
-        project1pom.scopes.runtime.assertDependsOn("org.gradle.test", "changed", "1.9")
+
+        project1.assertPublishedAsJavaModule()
+        project1.parsedPom.scopes.runtime.assertDependsOn("org.gradle.test:project2:2.0")
     }
 
-    @Ignore("This does not work: fix this as part of making the project coordinates customisable via DSL") // TODO:DAZ
+    @Ignore("This does not work: fix this as part of making the project coordinates customisable via DSL (using new mechanism to set artifactId")
     def "project dependency correctly reflected in POM if dependency publication pom is changed"() {
         createBuildScripts("""
 project(":project1") {
@@ -162,40 +158,8 @@ project(":project2") {
         run ":project1:publish"
 
         then:
-        def pom = project1module.parsedPom
-        pom.scopes.runtime.assertDependsOn("org.gradle.test", "changed", "1.9")
-    }
-
-    def "multiple project dependencies correctly reflected in POMs"() {
-        createBuildScripts("""
-project(":project1") {
-    dependencies {
-        compile project(":project2")
-        compile project(":project3")
-    }
-}
-
-project(":project2") {
-    dependencies {
-        compile project(":project3")
-    }
-}
-        """)
-
-        when:
-        run "publish"
-
-        then:
-        def pom = project1module.parsedPom
-        pom.scopes.runtime.assertDependsOnArtifacts("project2", "project3")
-
-        and:
-        def pom2 = mavenRepo.module("org.gradle.test", "project2", "1.9").parsedPom
-        pom2.scopes.runtime.assertDependsOnArtifacts("project3")
-
-        and:
-        def pom3 = mavenRepo.module("org.gradle.test", "project3", "1.9").parsedPom
-        pom3.scopes.runtime == null
+        def pom = project1.parsedPom
+        pom.scopes.runtime.assertDependsOn("org.gradle.test:changed:1.9")
     }
 
     private void createBuildScripts(String append = "") {
@@ -204,19 +168,38 @@ include "project1", "project2", "project3"
         """
 
         buildFile << """
-allprojects {
-    group = "org.gradle.test"
-    version = 1.9
-}
-
 subprojects {
     apply plugin: "java"
     apply plugin: "maven-publish"
 
     publishing {
         repositories {
-            maven { url "file:///\$rootProject.projectDir/maven-repo" }
+            maven { url "${mavenRepo.uri}" }
         }
+        publications {
+            maven(MavenPublication) {
+                from components.java
+            }
+        }
+    }
+}
+
+allprojects {
+    group = "org.gradle.test"
+    version = "3.0"
+}
+
+project(":project1") {
+    version = "1.0"
+    dependencies {
+        compile project(":project2")
+        compile project(":project3")
+    }
+}
+project(":project2") {
+    version = "2.0"
+    dependencies {
+        compile project(":project3")
     }
 }
 
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy
index de92676..a167328 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishPomCustomisationIntegTest.groovy
@@ -17,9 +17,11 @@
 
 
 package org.gradle.api.publish.maven
+
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
+
 /**
  * Tests maven POM customisation
  */
@@ -28,12 +30,11 @@ class MavenPublishPomCustomisationIntegTest extends AbstractIntegrationSpec {
 
     def "can customise pom xml"() {
         given:
-        settingsFile << "rootProject.name = 'root'"
+        settingsFile << "rootProject.name = 'customisePom'"
         buildFile << """
             apply plugin: 'maven-publish'
-            apply plugin: 'java'
 
-            group = 'group'
+            group = 'org.gradle.test'
             version = '1.0'
 
             publishing {
@@ -41,11 +42,8 @@ class MavenPublishPomCustomisationIntegTest extends AbstractIntegrationSpec {
                     maven { url "${mavenRepo.uri}" }
                 }
                 publications {
-                    maven {
+                    mavenCustom(MavenPublication) {
                         pom.withXml {
-                            asNode().groupId[0].value = "changed-group"
-                            asNode().artifactId[0].value = "changed-artifact"
-                            asNode().version[0].value = "changed-version"
                             asNode().appendNode('description', "custom-description")
 
                             def dependency = asNode().appendNode('dependencies').appendNode('dependency')
@@ -62,50 +60,52 @@ class MavenPublishPomCustomisationIntegTest extends AbstractIntegrationSpec {
         succeeds 'publish'
 
         then:
-        def module = mavenRepo.module('changed-group', 'changed-artifact', 'changed-version')
-        module.assertPublishedAsJavaModule()
+        def module = mavenRepo.module('org.gradle.test', 'customisePom', '1.0')
+        module.assertPublished()
         module.parsedPom.description == 'custom-description'
-        module.parsedPom.scopes.runtime.assertDependsOn("junit", "junit", "4.11")
+        module.parsedPom.scopes.runtime.assertDependsOn("junit:junit:4.11")
     }
 
-    def "pom can contain non-ascii characters"() {
-        // Group and Artifact are restricted to [A-Za-z0-9_\\-.]+ by org.apache.maven.project.validation.DefaultModelValidator
-        def groupId = 'group'
-        def artifactId = 'artifact'
-
-        // Try version & description with non-ascii characters
-        def version = 'version-₦ガき∆'
-        def description = 'description-ç√∫'
-
+    def "can generate pom file without publishing"() {
         given:
-        settingsFile << "rootProject.name = '${artifactId}'"
+        settingsFile << "rootProject.name = 'generatePom'"
         buildFile << """
             apply plugin: 'maven-publish'
-            apply plugin: 'java'
 
-            group = '${groupId}'
-            version = '${version}'
+            group = 'org.gradle.test'
+            version = '1.0'
 
             publishing {
                 repositories {
                     maven { url "${mavenRepo.uri}" }
                 }
                 publications {
-                    maven {
+                    emptyMaven(MavenPublication) {
                         pom.withXml {
-                            asNode().appendNode('description', "${description}")
+                            asNode().appendNode('description', "Test for pom generation")
                         }
                     }
                 }
+                generatePomFileForEmptyMavenPublication {
+                    destination = 'build/generated-pom.xml'
+                }
             }
         """
+
         when:
-        succeeds 'publish'
+        run "generatePomFileForEmptyMavenPublication"
 
         then:
-        def module = mavenRepo.module(groupId, artifactId, version)
-        module.assertPublishedAsJavaModule()
-        module.parsedPom.description == description
+        def mavenModule = mavenRepo.module("org.gradle.test", "generatePom", "1.0")
+        mavenModule.assertNotPublished()
+
+        and:
+        file('build/generated-pom.xml').assertIsFile()
+        def pom = new org.gradle.test.fixtures.maven.MavenPom(file('build/generated-pom.xml'))
+        pom.groupId == "org.gradle.test"
+        pom.artifactId == "generatePom"
+        pom.version == "1.0"
+        pom.description == "Test for pom generation"
     }
 
     def "has reasonable error message when withXml fails"() {
@@ -123,7 +123,7 @@ class MavenPublishPomCustomisationIntegTest extends AbstractIntegrationSpec {
                     maven { url "${mavenRepo.uri}" }
                 }
                 publications {
-                    maven {
+                    maven(MavenPublication) {
                         pom.withXml {
                             asNode().foo = 'this is not a real element'
                         }
@@ -135,8 +135,40 @@ class MavenPublishPomCustomisationIntegTest extends AbstractIntegrationSpec {
         fails 'publish'
 
         then:
-        failure.assertHasDescription("Execution failed for task ':publishMavenPublicationToMavenRepository'")
+        failure.assertHasDescription("Execution failed for task ':generatePomFileForMavenPublication'")
         failure.assertHasCause("Could not apply withXml() to generated POM")
         failure.assertHasCause("No such property: foo for class: groovy.util.Node")
     }
+
+    def "has reasonable error message when withXml produces invalid POM file"() {
+        given:
+        settingsFile << "rootProject.name = 'root'"
+        buildFile << """
+            apply plugin: 'maven-publish'
+            apply plugin: 'java'
+
+            group = 'group'
+            version = '1.0'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        pom.withXml {
+                            asNode().appendNode('invalid-node', "This is not a valid node for a Maven POM")
+                        }
+                    }
+                }
+            }
+        """
+        when:
+        fails 'publish'
+
+        then:
+        failure.assertHasDescription("Execution failed for task ':publishMavenPublicationToMavenRepository'")
+        failure.assertHasCause("Failed to publish publication 'maven' to repository 'maven'")
+        failure.assertHasCause("Invalid publication 'maven': POM file is invalid. Check any modifications you have made to the POM file.")
+    }
 }
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishWarProjectIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishWarProjectIntegTest.groovy
new file mode 100644
index 0000000..ab13ab2
--- /dev/null
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishWarProjectIntegTest.groovy
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven
+
+class MavenPublishWarProjectIntegTest extends AbstractMavenPublishIntegTest {
+    public void "publishes war and meta-data for web component with external dependencies"() {
+        def webModule = mavenRepo.module("org.gradle.test", "project1", "1.9")
+
+        given:
+        settingsFile << "rootProject.name = 'project1'"
+
+        and:
+        buildFile << """
+            apply plugin: 'war'
+            apply plugin: 'maven-publish'
+
+            group = 'org.gradle.test'
+            version = '1.9'
+
+            repositories {
+                mavenCentral()
+            }
+
+            dependencies {
+                compile "commons-collections:commons-collections:3.2.1"
+                runtime "commons-io:commons-io:1.4"
+                testRuntime "junit:junit:4.11"
+            }
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+                publications {
+                    maven(MavenPublication) {
+                        from components.web
+                    }
+                }
+            }
+"""
+        when:
+        succeeds 'assemble'
+
+        then: "war is built but not published"
+        webModule.assertNotPublished()
+        file('build/libs/project1-1.9.war').assertExists()
+
+        when:
+        run "publish"
+
+        then:
+        webModule.assertPublishedAsWebModule()
+        webModule.parsedPom.scopes.isEmpty()
+
+        and:
+        resolveArtifacts(webModule) == ["project1-1.9.war"]
+    }
+
+    public void "publishes war and meta-data for web component with project dependencies"() {
+        given:
+        settingsFile << "include 'projectWeb', 'depProject1', 'depProject2'"
+
+        and:
+        buildFile << """
+        subprojects {
+            apply plugin: 'war'
+            apply plugin: 'maven-publish'
+
+            group = 'org.gradle.test'
+            version = '1.9'
+
+            publishing {
+                repositories {
+                    maven { url "${mavenRepo.uri}" }
+                }
+            }
+        }
+
+        project(":projectWeb") {
+            dependencies {
+                compile project(":depProject1")
+                compile project(":depProject2")
+            }
+
+            publishing {
+                publications {
+                    maven(MavenPublication) {
+                        from components.web
+                    }
+                }
+            }
+         }
+
+        project(":depProject1") {
+            publishing {
+                publications {
+                    maven(MavenPublication) {
+                        from components.java
+                    }
+                }
+            }
+        }
+
+        project(":depProject2") {
+            publishing {
+                publications {
+                    maven(MavenPublication) {
+                        from components.web
+                    }
+                }
+            }
+        }
+"""
+
+        when:
+        run "publish"
+
+        then:
+        mavenRepo.module("org.gradle.test", "depProject1", "1.9").assertPublishedAsJavaModule()
+        mavenRepo.module("org.gradle.test", "depProject2", "1.9").assertPublishedAsWebModule()
+
+        def webModule = mavenRepo.module("org.gradle.test", "projectWeb", "1.9")
+        webModule.assertPublishedAsWebModule()
+
+        webModule.parsedPom.scopes.isEmpty()
+
+        and:
+        resolveArtifacts(webModule) == ["projectWeb-1.9.war"]
+    }
+
+}
diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy
index a0c678b..cc8ec70 100644
--- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy
+++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/SamplesMavenPublishIntegrationTest.groovy
@@ -18,19 +18,21 @@
 package org.gradle.api.publish.maven
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
+import org.gradle.test.fixtures.maven.M2Installation
 import org.junit.Rule
 
 public class SamplesMavenPublishIntegrationTest extends AbstractIntegrationSpec {
+    @Rule public final Sample quickstart = new Sample(temporaryFolder, "maven-publish/quickstart")
+    @Rule public final Sample javaProject = new Sample(temporaryFolder, "maven-publish/javaProject")
+    @Rule public final Sample pomCustomization = new Sample(temporaryFolder, "maven-publish/pomCustomization")
 
-    @Rule Sample sample = new Sample("maven/publish-new")
-
-    def sample() {
+    def quickstartPublish() {
         given:
-        executer.inDirectory(sample.dir)
+        sample quickstart
 
         and:
-        def fileRepo = maven(sample.dir.file("build/repo"))
-        def module = fileRepo.module("org.gradle.sample", "publish-new", "1.0")
+        def fileRepo = maven(quickstart.dir.file("build/repo"))
+        def module = fileRepo.module("org.gradle.sample", "quickstart", "1.0")
 
         when:
         succeeds "publish"
@@ -38,7 +40,61 @@ public class SamplesMavenPublishIntegrationTest extends AbstractIntegrationSpec
         then:
         def pom = module.parsedPom
         module.assertPublishedAsJavaModule()
-        pom.description == "A demonstration of maven pom customisation"
-        pom.scopes.runtime.assertDependsOn("commons-collections", "commons-collections", "3.0")
+        pom.scopes.isEmpty()
+    }
+
+    def quickstartPublishLocal() {
+        given:
+        def m2Installation = new M2Installation(testDirectory)
+        executer.beforeExecute m2Installation
+        def localModule = m2Installation.mavenRepo().module("org.gradle.sample", "quickstart", "1.0")
+
+        and:
+        sample quickstart
+
+        and:
+        def fileRepo = maven(quickstart.dir.file("build/repo"))
+        def module = fileRepo.module("org.gradle.sample", "quickstart", "1.0")
+
+        when:
+        succeeds 'publishToMavenLocal'
+
+        then: "jar is published to maven local repository"
+        module.assertNotPublished()
+        localModule.assertPublishedAsJavaModule()
+    }
+
+    def javaProject() {
+        given:
+        sample javaProject
+
+        and:
+        def fileRepo = maven(javaProject.dir.file("build/repo"))
+        def module = fileRepo.module("org.gradle.sample", "javaProject", "1.0")
+
+        when:
+        succeeds "publish"
+
+        then:
+        module.assertPublished()
+        module.assertArtifactsPublished("javaProject-1.0.jar", "javaProject-1.0-sources.jar", "javaProject-1.0.pom")
+        module.parsedPom.packaging == null
+        module.parsedPom.scopes.runtime.assertDependsOn("commons-collections:commons-collections:3.0")
+    }
+
+    def pomCustomization() {
+        given:
+        sample pomCustomization
+
+        and:
+        def fileRepo = maven(pomCustomization.dir.file("build/repo"))
+        def module = fileRepo.module("org.gradle.sample", "pomCustomization", "1.0")
+
+        when:
+        succeeds "publish"
+
+        then:
+        module.assertPublishedAsPomModule()
+        module.parsedPom.description == "A demonstration of maven pom customisation"
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
index c24fc11..60fe1a3 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
@@ -73,7 +73,7 @@ public class MavenPluginConvention implements MavenPomMetaInfoProvider {
     /**
      * Sets the directory to generate Maven POMs into.
      *
-     * @param pomDir The new POM directory. Evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @param pomDir The new POM directory. Evaluated as per {@link org.gradle.api.Project#file(Object)}.
      */
     public void setMavenPomDir(Object pomDir) {
         this.pomDir = pomDir;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java
index 7604963..40076e6 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/MavenRepositoryHandlerConvention.java
@@ -29,13 +29,25 @@ public interface MavenRepositoryHandlerConvention {
     String DEFAULT_MAVEN_DEPLOYER_NAME = "mavenDeployer";
     String DEFAULT_MAVEN_INSTALLER_NAME = "mavenInstaller";
 
+    /**
+     * Adds a repository for publishing to a Maven repository. This repository can not be used for resolving dependencies.
+     *
+     * @return The added repository
+     * @see #mavenDeployer(java.util.Map, groovy.lang.Closure)
+     */
     GroovyMavenDeployer mavenDeployer();
 
+    /**
+     * Adds a repository for publishing to a Maven repository. This repository can not be used for resolving dependencies.
+     *
+     * @param configureClosure A closure to use to configure the repository.
+     * @return The added repository
+     * @see #mavenDeployer(java.util.Map, groovy.lang.Closure)
+     */
     GroovyMavenDeployer mavenDeployer(Closure configureClosure);
 
     /**
-     * Adds a repository for publishing to a Maven repository. This repository can not be used for reading from a Maven
-     * repository.
+     * Adds a repository for publishing to a Maven repository. This repository can not be used for resolving dependencies.
      *
      * The following parameter are accepted as keys for the map:
      *
@@ -56,21 +68,33 @@ public interface MavenRepositoryHandlerConvention {
     GroovyMavenDeployer mavenDeployer(Map<String, ?> args);
 
     /**
-     * Behaves the same way as {@link #mavenDeployer(java.util.Map)}. Additionally a closure can be passed to
-     * further configure the added repository.
+     * Adds a repository for publishing to a Maven repository. This repository can not be used for resolving dependencies.
      *
      * @param args The argument to create the repository
-     * @param configureClosure
+     * @param configureClosure A closure to use to configure the repository.
      * @return The added repository
      */
     GroovyMavenDeployer mavenDeployer(Map<String, ?> args, Closure configureClosure);
 
+    /**
+     * Adds a repository for installing to a local Maven cache. This repository can not be used for resolving dependencies.
+     *
+     * @return The added repository
+     * @see #mavenInstaller(java.util.Map, groovy.lang.Closure) (java.util.Map, groovy.lang.Closure)
+     */
     MavenResolver mavenInstaller();
 
+    /**
+     * Adds a repository for installing to a local Maven cache. This repository can not be used for resolving dependencies.
+     *
+     * @param configureClosure A closure to use to configure the repository.
+     * @return The added repository
+     * @see #mavenInstaller(java.util.Map, groovy.lang.Closure) (java.util.Map, groovy.lang.Closure)
+     */
     MavenResolver mavenInstaller(Closure configureClosure);
 
     /**
-     * Adds a repository for installing to a local Maven cache. This repository can not be used for reading.
+     * Adds a repository for installing to a local Maven cache. This repository can not be used for resolving dependencies.
      *
      * The following parameter are accepted as keys for the map:
      *
@@ -91,9 +115,10 @@ public interface MavenRepositoryHandlerConvention {
     MavenResolver mavenInstaller(Map<String, ?> args);
 
     /**
-     * Behaves the same way as {@link #mavenInstaller(java.util.Map)}. Additionally a closure can be passed to further configure the added repository.
+     * Adds a repository for installing to a local Maven cache. This repository can not be used for resolving dependencies.
      *
      * @param args The argument to create the repository
+     * @param configureClosure A closure to use to configure the repository.
      * @return The added repository
      */
     MavenResolver mavenInstaller(Map<String, ?> args, Closure configureClosure);
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/ConvertMaven2Gradle.groovy b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/ConvertMaven2Gradle.groovy
index c99233a..9697af0 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/ConvertMaven2Gradle.groovy
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/ConvertMaven2Gradle.groovy
@@ -19,44 +19,31 @@
 package org.gradle.api.plugins.maven
 
 import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
 import org.gradle.api.internal.artifacts.DependencyManagementServices
-import org.gradle.api.logging.Logger
-import org.gradle.api.logging.Logging
+import org.gradle.api.internal.artifacts.mvnsettings.MavenSettingsProvider
 import org.gradle.api.plugins.maven.internal.Maven2Gradle
 import org.gradle.api.plugins.maven.internal.MavenProjectsCreator
 import org.gradle.api.tasks.TaskAction
+import org.gradle.util.SingleMessageLogger
 
-import org.gradle.api.internal.DocumentationRegistry
 import javax.inject.Inject
-import org.gradle.api.internal.artifacts.mvnsettings.MavenSettingsProvider
-import org.gradle.api.Incubating
 
 /**
  * by Szczepan Faber, created at: 8/1/12
  */
 @Incubating
 class ConvertMaven2Gradle extends DefaultTask {
-    private final static Logger LOG = Logging.getLogger(ConvertMaven2Gradle.class)
-
-    private final DocumentationRegistry documentationRegistry
     private final MavenSettingsProvider settingsProvider
 
     @Inject
-    ConvertMaven2Gradle(DocumentationRegistry documentationRegistry, DependencyManagementServices managementServices) {
-        this.documentationRegistry = documentationRegistry
+    ConvertMaven2Gradle(DependencyManagementServices managementServices) {
         this.settingsProvider = managementServices.get(MavenSettingsProvider)
     }
 
     @TaskAction
     void convertNow() {
-        LOG.lifecycle("""
----------------
-Maven to Gradle conversion is an "incubating" feature, which means it is still in development.
-See ${documentationRegistry.featureLifecycle} for more on "incubating" features.
-Please use it, report any problems and share your feedback with us.
----------------
-""")
-
+        SingleMessageLogger.informAboutIncubating("Maven to Gradle conversion")
 
         def settings = settingsProvider.buildSettings()
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriter.java b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriter.java
index 8789c1e..5cce743 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriter.java
@@ -28,9 +28,8 @@ import java.util.Set;
  */
 public class MavenProjectXmlWriter {
 
-    //TODO SF this class attempts to mimic the behavior of the output of mvn help:effective-pom
-    //we can remove it when the conversion feature no longer depends on the effective xml
-    //if we want to keep this class, we need to add more tests.
+    //TODO this class attempts to mimic the behavior of the output of mvn help:effective-pom
+    //instead of this class we should walk the maven project object model (instead of parsing the xml!)
 
     String toXml(Set<MavenProject> projects) {
         assert !projects.isEmpty() : "Cannot prepare the maven projects effective xml because provided projects set is empty.";
@@ -53,6 +52,10 @@ public class MavenProjectXmlWriter {
         } catch (IOException e) {
             throw new RuntimeException("Unable to serialize maven model to xml. Maven project: " + project, e);
         }
-        return out.toString().replace("<?xml version=\"1.0\"?>", "");
+        return prepareXml(out.toString());
+    }
+
+    String prepareXml(String xml) {
+        return xml.replaceFirst("^<\\?xml.+?\\?>", "");
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java
index fb2d050..31e5f1c 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/DefaultMavenPom.java
@@ -197,7 +197,7 @@ public class DefaultMavenPom implements MavenPom {
     }
 
     public DefaultMavenPom writeTo(Object path) {
-        IoActions.writeFile(fileResolver.resolve(path), POM_FILE_ENCODING, new Action<BufferedWriter>() {
+        IoActions.writeTextFile(fileResolver.resolve(path), POM_FILE_ENCODING, new Action<BufferedWriter>() {
             public void execute(BufferedWriter writer) {
                 writeTo(writer);
             }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
index a90d7fd..9bd93e4 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/AbstractMavenResolver.java
@@ -42,9 +42,9 @@ import org.gradle.api.Action;
 import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.artifacts.maven.*;
 import org.gradle.api.internal.ClosureBackedAction;
+import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.IvyAwareModuleVersionRepository;
 import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.NoOpRepositoryCacheManager;
 import org.gradle.api.internal.artifacts.repositories.AbstractArtifactRepository;
-import org.gradle.api.internal.artifacts.repositories.ArtifactRepositoryInternal;
 import org.gradle.api.logging.LogLevel;
 import org.gradle.api.publication.maven.internal.ArtifactPomContainer;
 import org.gradle.api.publication.maven.internal.PomFilter;
@@ -61,7 +61,7 @@ import java.util.Set;
 /**
  * @author Hans Dockter
  */
-public abstract class AbstractMavenResolver extends AbstractArtifactRepository implements MavenResolver, DependencyResolver, ArtifactRepositoryInternal {
+public abstract class AbstractMavenResolver extends AbstractArtifactRepository implements MavenResolver, DependencyResolver {
     
     private ArtifactPomContainer artifactPomContainer;
 
@@ -81,73 +81,80 @@ public abstract class AbstractMavenResolver extends AbstractArtifactRepository i
         this.loggingManager = loggingManager;
     }
 
-    protected abstract InstallDeployTaskSupport createPreConfiguredTask(Project project);
+    public IvyAwareModuleVersionRepository createResolver() {
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
+    }
+
+    public DependencyResolver createPublisher() {
+        return this;
+    }
 
-    public DependencyResolver createResolver() {
+    public DependencyResolver createLegacyDslObject() {
         return this;
     }
 
+    protected abstract InstallDeployTaskSupport createPreConfiguredTask(Project project);
+
     public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public boolean exists(Artifact artifact) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public ArtifactOrigin locate(Artifact artifact) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public void reportFailure() {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public void reportFailure(Artifact art) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public String[] listTokenValues(String token, Map otherTokenValues) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public Map[] listTokenValues(String[] tokens, Map criteria) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public OrganisationEntry[] listOrganisations() {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public ModuleEntry[] listModules(OrganisationEntry org) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public RevisionEntry[] listRevisions(ModuleEntry module) {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public Namespace getNamespace() {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
     public void dumpSettings() {
-        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+        throw new UnsupportedOperationException("A Maven deployer cannot be used to resolve dependencies. It can only be used to publish artifacts.");
     }
 
-
     public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
         if (isIgnorable(artifact)) {
             return;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
index 38435b4..bd9499d 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/CustomDeployTask.java
@@ -16,9 +16,6 @@
 package org.gradle.api.publication.maven.internal.ant;
 
 import org.apache.maven.artifact.ant.DeployTask;
-import org.apache.maven.artifact.ant.Pom;
-import org.apache.maven.artifact.repository.ArtifactRepository;
-import org.apache.maven.project.MavenProjectBuilder;
 import org.apache.maven.settings.Settings;
 import org.codehaus.plexus.PlexusContainer;
 
@@ -44,15 +41,6 @@ public class CustomDeployTask extends DeployTask implements CustomInstallDeployT
         super.doExecute();
     }
 
-    @Override
-    public Pom initializePom(ArtifactRepository localArtifactRepository) {
-        // Pom initialization is not thread-safe, as it holds static state in the maven classes.
-        // This class-level lock means this piece of code will never execute concurrently for a given instance of the maven classes
-        synchronized (MavenProjectBuilder.class) {
-            return super.initializePom(localArtifactRepository);
-        }
-    }
-
     public void clearAttachedArtifactsList() {
         attachedArtifacts.clear();
     }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
index 735fa8a..aa8f6e9 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/DefaultPomDependenciesConverter.java
@@ -41,10 +41,12 @@ public class DefaultPomDependenciesConverter implements PomDependenciesConverter
         Map<ModuleDependency, String> dependenciesMap = createDependencyToScopeMap(conf2ScopeMappingContainer, dependencyToConfigurations);
         List<org.apache.maven.model.Dependency> mavenDependencies = new ArrayList<org.apache.maven.model.Dependency>();
         for (ModuleDependency dependency : dependenciesMap.keySet()) {
+            String scope = dependenciesMap.get(dependency);
+            Set<Configuration> dependencyConfigurations = dependencyToConfigurations.get(dependency);
             if (dependency.getArtifacts().size() == 0) {
-                addFromDependencyDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency), dependencyToConfigurations.get(dependency));
+                addFromDependencyDescriptor(mavenDependencies, dependency, scope, dependencyConfigurations);
             } else {
-                addFromArtifactDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency), dependencyToConfigurations.get(dependency));
+                addFromArtifactDescriptor(mavenDependencies, dependency, scope, dependencyConfigurations);
             }
         }
         return mavenDependencies;
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java
index ddd9deb..a5f5997 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publication/maven/internal/ant/NoInstallDeployTaskFactory.java
@@ -16,6 +16,7 @@
 
 package org.gradle.api.publication.maven.internal.ant;
 
+import org.apache.maven.artifact.ant.RemoteRepository;
 import org.apache.maven.artifact.repository.ArtifactRepository;
 import org.apache.maven.artifact.repository.DefaultArtifactRepository;
 import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
@@ -46,5 +47,10 @@ public class NoInstallDeployTaskFactory implements Factory<CustomDeployTask> {
             ArtifactRepositoryLayout repositoryLayout = (ArtifactRepositoryLayout) lookup(ArtifactRepositoryLayout.ROLE, getLocalRepository().getLayout());
             return new DefaultArtifactRepository("local", tmpDirFactory.create().toURI().toString(), repositoryLayout);
         }
+
+        @Override
+        protected void updateRepositoryWithSettings(RemoteRepository repository) {
+            // Do nothing
+        }
     }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/InvalidMavenPublicationException.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/InvalidMavenPublicationException.java
new file mode 100644
index 0000000..42bdd04
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/InvalidMavenPublicationException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.InvalidUserDataException;
+
+/**
+ * Thrown when attempting to publish with an invalid {@link MavenPublication}.
+ *
+ * @since 1.4
+ */
+ at Incubating
+public class InvalidMavenPublicationException extends InvalidUserDataException {
+    public InvalidMavenPublicationException(String publicationName, String error) {
+        super(formatMessage(publicationName, error));
+    }
+
+    public InvalidMavenPublicationException(String publicationName, String error, Throwable cause) {
+        super(formatMessage(publicationName, error), cause);
+    }
+
+    private static String formatMessage(String publicationName, String error) {
+        return String.format("Invalid publication '%s': %s", publicationName, error);
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifact.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifact.java
new file mode 100644
index 0000000..91809db
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifact.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Incubating;
+import org.gradle.api.Nullable;
+
+import java.io.File;
+
+/**
+ * An artifact published as part of a {@link MavenPublication}.
+ */
+ at Incubating
+public interface MavenArtifact extends Buildable {
+    /**
+     * The extension used to publish the artifact file, never <code>null</code>.
+     * For an artifact without an extension, this value will be an empty String.
+     */
+    String getExtension();
+
+    /**
+     * Sets the extension used to publish the artifact file.
+     * @param extension The extension.
+     */
+    void setExtension(String extension);
+
+    /**
+     * The classifier used to publish the artifact file.
+     * A <code>null</code> value (the default) indicates that this artifact will be published without a classifier.
+     */
+    @Nullable
+    String getClassifier();
+
+    /**
+     * Sets the classifier used to publish the artifact file.
+     * @param classifier The classifier.
+     */
+    void setClassifier(@Nullable String classifier);
+
+    /**
+     * The actual file contents to publish.
+     */
+    File getFile();
+
+    /**
+     * Registers some tasks which build this artifact.
+     *
+     * @param tasks The tasks. These are evaluated as per {@link org.gradle.api.Task#dependsOn(Object...)}.
+     */
+    void builtBy(Object... tasks);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifactSet.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifactSet.java
new file mode 100644
index 0000000..db2b46f
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenArtifactSet.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven;
+
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectSet;
+import org.gradle.api.Incubating;
+
+/**
+ * A Collection of {@link MavenArtifact}s to be included in a {@link MavenPublication}.
+ *
+ * Being a {@link DomainObjectSet}, a {@code MavenArtifactSet} provides convenient methods for querying, filtering, and applying actions to the set of {@link MavenArtifact}s.
+ *
+ * <pre autoTested="true">
+ * apply plugin: 'maven-publish'
+ *
+ * def publication = publishing.publications.add("name", MavenPublication)
+ * def artifacts = publication.artifacts
+ *
+ * artifacts.matching({
+ *     it.classifier == "classy"
+ * }).all({
+ *     it.extension = "ext"
+ * })
+ * </pre>
+ *
+ * @see DomainObjectSet
+ */
+ at Incubating
+public interface MavenArtifactSet extends DomainObjectSet<MavenArtifact> {
+    /**
+     * Creates and adds a {@link MavenArtifact} to the set.
+     *
+     * The semantics of this method are the same as {@link MavenPublication#artifact(Object)}.
+     *
+     * @param source The source of the artifact content.
+     */
+    MavenArtifact artifact(Object source);
+
+    /**
+     * Creates and adds a {@link MavenArtifact} to the set, which is configured by the associated action.
+     *
+     * The semantics of this method are the same as {@link MavenPublication#artifact(Object, Action)}.
+     *
+     * @param source The source of the artifact.
+     * @param config An action or closure to configure the values of the constructed {@link MavenArtifact}.
+     */
+     MavenArtifact artifact(Object source, Action<? super MavenArtifact> config);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java
index 20a80f4..9a711d3 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPom.java
@@ -36,13 +36,14 @@ public interface MavenPom {
      * Allows configuration of the POM, after it has been generated according to the input data.
      *
      * <pre autoTested="true">
-     * apply plugin: "java"
      * apply plugin: "maven-publish"
      *
      * publishing {
      *   publications {
-     *     maven.pom.withXml {
-     *       asNode().appendNode('description', 'A demonstration of maven pom customisation')
+     *     maven(MavenPublication) {
+     *       pom.withXml {
+     *         asNode().appendNode('description', 'A demonstration of maven pom customisation')
+     *       }
      *     }
      *   }
      * }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java
index ab7e27c..900f2dc 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/MavenPublication.java
@@ -18,30 +18,63 @@ package org.gradle.api.publish.maven;
 
 import org.gradle.api.Action;
 import org.gradle.api.Incubating;
+import org.gradle.api.component.SoftwareComponent;
 import org.gradle.api.internal.HasInternalProtocol;
 import org.gradle.api.publish.Publication;
 
 /**
  * A {@code MavenPublication} is the representation/configuration of how Gradle should publish something in Maven format.
  *
- * The "{@code maven-publish}" plugin creates one {@code MavenPublication} named "{@code maven}" in the project's
- * {@code publishing.publications} container. This publication is configured to publish all of the project's
- * <i>visible</i> configurations (i.e. {@link org.gradle.api.Project#getConfigurations()}).
- * <p>
- * The Maven POM identifying attributes are mapped as follows:
+ * You directly add a named Maven Publication the project's {@code publishing.publications} container by providing {@link MavenPublication} as the type.
+ * <pre>
+ * publishing {
+ *   publications {
+ *     myPublicationName(MavenPublication)
+ *   }
+ * }
+ * </pre>
+ *
+ * The default Maven POM identifying attributes are mapped as follows:
  * <ul>
  * <li>{@code groupId} - {@code project.group}</li>
  * <li>{@code artifactId} - {@code project.name}</li>
  * <li>{@code version} - {@code project.version}</li>
  * </ul>
- * <p>
- * The ability to add multiple publications and finely configure publications will be added in future Gradle versions.
  *
- * <h4>Customising the publication prior to publishing</h4>
- *
- * It is possible to modify the generated POM prior to publication. This is done using the {@link MavenPom#withXml(org.gradle.api.Action)} method
+ * <p>
+ * For certain common use cases, it's often sufficient to specify the component to publish, and nothing more ({@link #from(org.gradle.api.component.SoftwareComponent)}.
+ * The published component is used to determine which artifacts to publish, and which dependencies should be listed in the generated POM file.
+ * </p><p>
+ * To add additional artifacts to the set published, use the {@link #artifact(Object)} and {@link #artifact(Object, org.gradle.api.Action)} methods.
+ * You can also completely replace the set of published artifacts using {@link #setArtifacts(Iterable)}.
+ * Together, these methods give you full control over what artifacts will be published.
+ * </p><p>
+ * For any other tweaks to the publication, it is possible to modify the generated POM prior to publication. This is done using the {@link MavenPom#withXml(org.gradle.api.Action)} method
  * of the POM returned via the {@link #getPom()} method, or directly by an action (or closure) passed into {@link #pom(org.gradle.api.Action)}.
+ * </p>
+ * <h4>Example of publishing a java module with a source artifact and custom POM description</h4>
+ * <pre autoTested="true">
+ * apply plugin: "java"
+ * apply plugin: "maven-publish"
  *
+ * task sourceJar(type: Jar) {
+ *   from sourceSets.main.allJava
+ * }
+ *
+ * publishing {
+ *   publications {
+ *     myPublication(MavenPublication) {
+ *       from components.java
+ *       artifact sourceJar {
+ *         classifier "source"
+ *       }
+ *       pom.withXml {
+ *         asNode().appendNode('description', 'A demonstration of maven pom customisation')
+ *       }
+ *     }
+ *   }
+ * }
+ * </pre>
  *
  * @since 1.4
  */
@@ -65,4 +98,138 @@ public interface MavenPublication extends Publication {
      */
     void pom(Action<? super MavenPom> configure);
 
+    /**
+     * Provides the software component that should be published.
+     *
+     * <ul>
+     *     <li>Any artifacts declared by the component will be included in the publication.</li>
+     *     <li>The dependencies declared by the component will be included in the published meta-data.</li>
+     * </ul>
+     *
+     * Currently 2 types of component are supported: 'components.java' (added by the JavaPlugin) and 'components.web' (added by the WarPlugin).
+     * For any individual MavenPublication, only a single component can be provided in this way.
+     *
+     * The following example demonstrates how to publish the 'java' component to a maven repository.
+     * <pre autoTested="true">
+     * apply plugin: "java"
+     * apply plugin: "maven-publish"
+     *
+     * publishing {
+     *   publications {
+     *     maven(MavenPublication) {
+     *       from components.java
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param component The software component to publish.
+     */
+    void from(SoftwareComponent component);
+
+    /**
+     * Creates a custom {@link MavenArtifact} to be included in the publication.
+     *
+     * The <code>artifact</code> method can take a variety of input:
+     * <ul>
+     *     <li>A {@link org.gradle.api.artifacts.PublishArtifact} instance. Extension and classifier values are taken from the wrapped instance.</li>
+     *     <li>An {@link org.gradle.api.tasks.bundling.AbstractArchiveTask} instance. Extension and classifier values are taken from the wrapped instance.</li>
+     *     <li>Anything that can be resolved to a {@link java.io.File} via the {@link org.gradle.api.Project#file(Object)} method.
+     *          Extension and classifier values are interpolated from the file name.</li>
+     *     <li>A {@link java.util.Map} that contains a 'source' entry that can be resolved as any of the other input types, including file.
+     *         This map can contain a 'classifier' and an 'extension' entry to further configure the constructed artifact.</li>
+     * </ul>
+     *
+     * The following example demonstrates the addition of various custom artifacts.
+     * <pre autoTested="true">
+     * apply plugin: "maven-publish"
+     *
+     * task sourceJar(type: Jar) {
+     *   classifier "source"
+     * }
+     *
+     * publishing {
+     *   publications {
+     *     maven(MavenPublication) {
+     *       artifact sourceJar // Publish the output of the sourceJar task
+     *       artifact 'my-file-name.jar' // Publish a file created outside of the build
+     *       artifact source: sourceJar, classifier: 'src', extension: 'zip'
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param source The source of the artifact content.
+     */
+    MavenArtifact artifact(Object source);
+
+    /**
+     * Creates an {@link MavenArtifact} to be included in the publication, which is configured by the associated action.
+     *
+     * The first parameter is used to create a custom artifact and add it to the publication, as per {@link #artifact(Object)}.
+     * The created {@link MavenArtifact} is then configured using the supplied action, which can override the extension or classifier of the artifact.
+     * This method also accepts the configure action as a closure argument, by type coercion.
+     *
+     * <pre autoTested="true">
+     * apply plugin: "maven-publish"
+     *
+     * task sourceJar(type: Jar) {
+     *   classifier "source"
+     * }
+     *
+     * publishing {
+     *   publications {
+     *     maven(MavenPublication) {
+     *       artifact sourceJar {
+     *         // These values will be used instead of the values from the task. The task values will not be updated.
+     *         classifier "src"
+     *         extension "zip"
+     *       }
+     *       artifact("my-docs-file.htm") {
+     *         classifier "documentation"
+     *         extension "html"
+     *       }
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param source The source of the artifact.
+     * @param config An action to configure the values of the constructed {@link MavenArtifact}.
+     */
+    MavenArtifact artifact(Object source, Action<? super MavenArtifact> config);
+
+    /**
+     * Clears any previously added artifacts from {@link #getArtifacts} and creates artifacts from the specified sources.
+     * Each supplied source is interpreted as per {@link #artifact(Object)}.
+     *
+     * For example, to exclude the dependencies declared by a component and instead use a custom set of artifacts:
+     * <pre autoTested="true">
+     * apply plugin: "java"
+     * apply plugin: "maven-publish"
+     *
+     * task sourceJar(type: Jar) {
+     *   classifier "source"
+     * }
+
+     * publishing {
+     *   publications {
+     *     maven(MavenPublication) {
+     *       from components.java
+     *       artifacts = ["my-custom-jar.jar", sourceJar]
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param sources The set of artifacts for this publication.
+     */
+    void setArtifacts(Iterable<?> sources);
+
+    /**
+     * Returns the complete set of artifacts for this publication.
+     * @return the artifacts.
+     */
+    MavenArtifactSet getArtifacts();
+
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPom.java
deleted file mode 100644
index d06c9c9..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPom.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.internal.UserCodeAction;
-import org.gradle.api.XmlProvider;
-import org.gradle.listener.ActionBroadcast;
-
-public class DefaultMavenPom implements MavenPomInternal {
-
-    private final ActionBroadcast<XmlProvider> xmlAction = new ActionBroadcast<XmlProvider>();
-
-    public void withXml(Action<? super XmlProvider> action) {
-        xmlAction.add(new UserCodeAction<XmlProvider>("Could not apply withXml() to generated POM", action));
-    }
-
-    public Action<XmlProvider> getXmlAction() {
-        return xmlAction;
-    }
-
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPublication.java
deleted file mode 100644
index faf0fb7..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/DefaultMavenPublication.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.component.SoftwareComponentInternal;
-import org.gradle.api.publish.maven.MavenPom;
-import org.gradle.internal.reflect.Instantiator;
-
-import java.io.File;
-
-public class DefaultMavenPublication implements MavenPublicationInternal {
-
-    private final String name;
-    private final MavenPomInternal pom;
-    private final MavenProjectIdentity projectIdentity;
-    private final SoftwareComponentInternal component;
-    private final File pomDir;
-
-    public DefaultMavenPublication(
-            String name, Instantiator instantiator, MavenProjectIdentity projectIdentity, SoftwareComponentInternal component, File pomDir
-    ) {
-        this.name = name;
-        this.pom = instantiator.newInstance(DefaultMavenPom.class);
-        this.projectIdentity = projectIdentity;
-        this.component = component;
-        this.pomDir = pomDir;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public MavenPomInternal getPom() {
-        return pom;
-    }
-
-    public void pom(Action<? super MavenPom> configure) {
-        configure.execute(pom);
-    }
-
-    public FileCollection getPublishableFiles() {
-        return component.getArtifacts().getFiles();
-    }
-
-    public MavenNormalizedPublication asNormalisedPublication() {
-        return new MavenNormalizedPublication(projectIdentity, component.getArtifacts(), component.getRuntimeDependencies(), pom.getXmlAction());
-    }
-
-    public File getPomDir() {
-        return pomDir;
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenDeployerConfigurer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenDeployerConfigurer.java
deleted file mode 100644
index 1b2ed44..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenDeployerConfigurer.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.apache.maven.artifact.ant.Authentication;
-import org.apache.maven.artifact.ant.RemoteRepository;
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.maven.MavenDeployer;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.artifacts.repositories.PasswordCredentials;
-
-public class MavenDeployerConfigurer implements Action<MavenDeployer> {
-
-    private final MavenArtifactRepository artifactRepository;
-
-    public MavenDeployerConfigurer(MavenArtifactRepository artifactRepository) {
-        this.artifactRepository = artifactRepository;
-    }
-
-    public void execute(MavenDeployer deployer) {
-        deployer.setRepository(createRepository());
-    }
-
-    private RemoteRepository createRepository() {
-        RemoteRepository remoteRepository = new RemoteRepository();
-        remoteRepository.setUrl(artifactRepository.getUrl().toString());
-
-        PasswordCredentials credentials = artifactRepository.getCredentials();
-        String username = credentials.getUsername();
-        String password = credentials.getPassword();
-
-        if (username != null || password != null) {
-            Authentication authentication = new Authentication();
-            authentication.setUserName(username);
-            authentication.setPassword(password);
-            remoteRepository.addAuthentication(authentication);
-        }
-
-        return remoteRepository;
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenNormalizedPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenNormalizedPublication.java
deleted file mode 100644
index bc816be..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenNormalizedPublication.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.XmlProvider;
-import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.artifacts.PublishArtifact;
-
-public class MavenNormalizedPublication implements MavenProjectIdentity {
-
-    private final MavenProjectIdentity projectIdentity;
-    private final Iterable<PublishArtifact> artifacts;
-    private final Iterable<Dependency> runtimeDependencies;
-    private final Action<XmlProvider> pomWithXmlAction;
-
-    public MavenNormalizedPublication(MavenProjectIdentity projectIdentity, Iterable<PublishArtifact> artifacts, Iterable<Dependency> runtimeDependencies, Action<XmlProvider> pomWithXmlAction) {
-        this.projectIdentity = projectIdentity;
-        this.artifacts = artifacts;
-        this.runtimeDependencies = runtimeDependencies;
-        this.pomWithXmlAction = pomWithXmlAction;
-    }
-
-    public String getArtifactId() {
-        return projectIdentity.getArtifactId();
-    }
-
-    public String getGroupId() {
-        return projectIdentity.getGroupId();
-    }
-
-    public String getVersion() {
-        return projectIdentity.getVersion();
-    }
-
-    public String getPackaging() {
-        return projectIdentity.getPackaging();
-    }
-
-    public Iterable<PublishArtifact> getArtifacts() {
-        return artifacts;
-    }
-
-    public Iterable<Dependency> getRuntimeDependencies() {
-        return runtimeDependencies;
-    }
-
-    public Action<XmlProvider> getPomWithXmlAction() {
-        return pomWithXmlAction;
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPomInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPomInternal.java
deleted file mode 100644
index 896f95c..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPomInternal.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.XmlProvider;
-import org.gradle.api.publish.maven.MavenPom;
-
-public interface MavenPomInternal extends MavenPom {
-
-    Action<XmlProvider> getXmlAction();
-
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentity.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentity.java
deleted file mode 100644
index 580bdfb..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentity.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-public interface MavenProjectIdentity {
-
-    String getArtifactId();
-
-    String getGroupId();
-
-    String getVersion();
-
-    String getPackaging();
-
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentityModuleAdapter.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentityModuleAdapter.java
deleted file mode 100644
index cb71b84..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenProjectIdentityModuleAdapter.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.artifacts.Module;
-
-public class MavenProjectIdentityModuleAdapter implements Module {
-
-    private final MavenProjectIdentity mavenProjectIdentity;
-
-    public MavenProjectIdentityModuleAdapter(MavenProjectIdentity mavenProjectIdentity) {
-        this.mavenProjectIdentity = mavenProjectIdentity;
-    }
-
-    public String getGroup() {
-        return mavenProjectIdentity.getGroupId();
-    }
-
-    public String getName() {
-        return mavenProjectIdentity.getArtifactId();
-    }
-
-    public String getVersion() {
-        return mavenProjectIdentity.getVersion();
-    }
-
-    public String getStatus() {
-        return "integration"; // not used
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublicationInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublicationInternal.java
deleted file mode 100644
index 7c3912b..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublicationInternal.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.file.FileCollection;
-import org.gradle.api.publish.maven.MavenPublication;
-
-import java.io.File;
-
-public interface MavenPublicationInternal extends MavenPublication {
-
-    MavenPomInternal getPom();
-
-    FileCollection getPublishableFiles();
-
-    MavenNormalizedPublication asNormalisedPublication();
-
-    File getPomDir();
-}
-
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishDynamicTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishDynamicTaskCreator.java
deleted file mode 100644
index 52a91b3..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishDynamicTaskCreator.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectList;
-import org.gradle.api.NamedDomainObjectSet;
-import org.gradle.api.Task;
-import org.gradle.api.artifacts.ArtifactRepositoryContainer;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.maven.tasks.PublishToMavenRepository;
-import org.gradle.api.tasks.TaskContainer;
-
-import static org.apache.commons.lang.StringUtils.capitalize;
-
-public class MavenPublishDynamicTaskCreator {
-
-    final private TaskContainer tasks;
-    private final Task publishLifecycleTask;
-
-    public MavenPublishDynamicTaskCreator(TaskContainer tasks, Task publishLifecycleTask) {
-        this.tasks = tasks;
-        this.publishLifecycleTask = publishLifecycleTask;
-    }
-
-    public void monitor(final PublicationContainer publications, final ArtifactRepositoryContainer repositories) {
-        final NamedDomainObjectSet<MavenPublicationInternal> mavenPublications = publications.withType(MavenPublicationInternal.class);
-        final NamedDomainObjectList<MavenArtifactRepository> mavenRepositories = repositories.withType(MavenArtifactRepository.class);
-
-        mavenPublications.all(new Action<MavenPublicationInternal>() {
-            public void execute(MavenPublicationInternal publication) {
-                for (MavenArtifactRepository repository : mavenRepositories) {
-                    maybeCreatePublishTask(publication, repository);
-                }
-            }
-        });
-
-        mavenRepositories.all(new Action<MavenArtifactRepository>() {
-            public void execute(MavenArtifactRepository repository) {
-                for (MavenPublicationInternal publication : mavenPublications) {
-                    maybeCreatePublishTask(publication, repository);
-                }
-            }
-        });
-
-        // Note: we aren't supporting removal of repositories or publications
-        // Note: we also aren't considering that repos have a setName, so their name can change
-        //       (though this is a violation of the Named contract)
-    }
-
-    private void maybeCreatePublishTask(MavenPublicationInternal publication, MavenArtifactRepository repository) {
-        String publicationName = publication.getName();
-        String repositoryName = repository.getName();
-
-        String publishTaskName = calculatePublishTaskName(publicationName, repositoryName);
-        if (tasks.findByName(publishTaskName) == null) {
-            PublishToMavenRepository publishTask = tasks.add(publishTaskName, PublishToMavenRepository.class);
-            publishTask.setPublication(publication);
-            publishTask.setRepository(repository);
-            publishTask.setGroup("publishing");
-            publishTask.setDescription(String.format("Publishes Maven publication '%s' to Maven repository '%s'", publicationName, repositoryName));
-
-            publishLifecycleTask.dependsOn(publishTask);
-        }
-    }
-
-    private String calculatePublishTaskName(String publicationName, String repositoryName) {
-        return String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishLocalDynamicTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishLocalDynamicTaskCreator.java
deleted file mode 100644
index 26aecde..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublishLocalDynamicTaskCreator.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.NamedDomainObjectSet;
-import org.gradle.api.Task;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.maven.tasks.PublishToMavenLocal;
-import org.gradle.api.tasks.TaskContainer;
-
-import static org.apache.commons.lang.StringUtils.capitalize;
-
-public class MavenPublishLocalDynamicTaskCreator {
-
-    final private TaskContainer tasks;
-    private final Task publishToMavenLocalLifecycleTask;
-
-    public MavenPublishLocalDynamicTaskCreator(TaskContainer tasks, Task publishToMavenLocalLifecycleTask) {
-        this.tasks = tasks;
-        this.publishToMavenLocalLifecycleTask = publishToMavenLocalLifecycleTask;
-    }
-
-    public void monitor(final PublicationContainer publications) {
-        final NamedDomainObjectSet<MavenPublicationInternal> mavenPublications = publications.withType(MavenPublicationInternal.class);
-
-        mavenPublications.all(new Action<MavenPublicationInternal>() {
-            public void execute(MavenPublicationInternal publication) {
-                createInstallTask(publication);
-            }
-        });
-        // Note: we aren't supporting removal of publications
-    }
-
-    private void createInstallTask(MavenPublicationInternal publication) {
-        String publicationName = publication.getName();
-        String installTaskName = calculatePublishLocalTaskName(publicationName);
-
-        PublishToMavenLocal publishTask = tasks.add(installTaskName, PublishToMavenLocal.class);
-        publishTask.setPublication(publication);
-        publishTask.setGroup("publishing");
-        publishTask.setDescription(String.format("Publishes Maven publication '%s' to Maven Local repository", publicationName));
-
-        publishToMavenLocalLifecycleTask.dependsOn(publishTask);
-    }
-
-    private String calculatePublishLocalTaskName(String publicationName) {
-        return String.format("publish%sPublicationToMavenLocal", capitalize(publicationName));
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublisher.java
deleted file mode 100644
index 7bc7d78..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/MavenPublisher.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.apache.ivy.plugins.resolver.DependencyResolver;
-import org.gradle.api.artifacts.*;
-import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
-import org.gradle.api.artifacts.maven.MavenDeployer;
-import org.gradle.api.artifacts.maven.MavenPom;
-import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.internal.Cast;
-import org.gradle.api.internal.artifacts.ArtifactPublisher;
-import org.gradle.api.publication.maven.internal.DefaultConf2ScopeMappingContainer;
-import org.gradle.api.publication.maven.internal.DeployerFactory;
-import org.gradle.api.publication.maven.internal.PomDependenciesConverter;
-import org.gradle.api.publication.maven.internal.ant.DefaultExcludeRuleConverter;
-import org.gradle.api.publication.maven.internal.ant.DefaultPomDependenciesConverter;
-import org.gradle.api.publication.maven.internal.ant.ProjectDependencyArtifactIdExtractorHack;
-import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
-import org.gradle.internal.Factory;
-
-import java.util.Collections;
-import java.util.List;
-
-public class MavenPublisher {
-
-    private final DeployerFactory deployerFactory;
-    private final Factory<Configuration> configurationFactory;
-    private final ArtifactPublisher artifactPublisher;
-    private final PomDependenciesConverter pomDependenciesConverter = new MavenPublishPomDependenciesConverter();
-
-    public MavenPublisher(DeployerFactory deployerFactory, Factory<Configuration> configurationFactory, ArtifactPublisher artifactPublisher) {
-        this.deployerFactory = deployerFactory;
-        this.configurationFactory = configurationFactory;
-        this.artifactPublisher = artifactPublisher;
-    }
-
-    public void publish(MavenNormalizedPublication publication, MavenArtifactRepository artifactRepository) {
-        MavenDeployer deployer = deployerFactory.createMavenDeployer();
-        new MavenDeployerConfigurer(artifactRepository).execute(deployer);
-        doPublish(publication, deployer);
-    }
-
-    private void doPublish(MavenNormalizedPublication publication, MavenDeployer deployer) {
-        Configuration publishConfiguration = createPopulatedConfiguration(publication);
-
-        MavenPom deployerPom = deployer.getPom();
-        copyIdentity(publication, deployerPom);
-        copyDependencies(publishConfiguration, deployerPom);
-        deployerPom.withXml(publication.getPomWithXmlAction());
-
-        Module module = new MavenProjectIdentityModuleAdapter(publication);
-
-        DependencyResolver dependencyResolver = Cast.cast(DependencyResolver.class, deployer);
-        artifactPublisher.publish(Collections.singleton(dependencyResolver), module, Collections.singleton(publishConfiguration), null);
-    }
-
-    private void copyIdentity(MavenProjectIdentity projectIdentity, MavenPom pom) {
-        pom.setArtifactId(projectIdentity.getArtifactId());
-        pom.setGroupId(projectIdentity.getGroupId());
-        pom.setVersion(projectIdentity.getVersion());
-        pom.setPackaging(projectIdentity.getPackaging());
-    }
-
-    private void copyDependencies(Configuration configuration, MavenPom pom) {
-        Conf2ScopeMappingContainer mappingContainer = new DefaultConf2ScopeMappingContainer();
-        mappingContainer.addMapping(0, configuration, "runtime");
-        List<?> mavenDependencies = pomDependenciesConverter.convert(mappingContainer, Collections.singleton(configuration));
-        pom.setDependencies(mavenDependencies);
-    }
-
-    Configuration createPopulatedConfiguration(MavenNormalizedPublication publication) {
-        Configuration configuration = configurationFactory.create();
-        for (PublishArtifact artifact : publication.getArtifacts()) {
-            configuration.getArtifacts().add(artifact);
-        }
-        for (Dependency runtimeDependency : publication.getRuntimeDependencies()) {
-            configuration.getDependencies().add(runtimeDependency);
-        }
-        return configuration;
-    }
-
-    private static class MavenPublishPomDependenciesConverter extends DefaultPomDependenciesConverter {
-        public MavenPublishPomDependenciesConverter() {
-            super(new DefaultExcludeRuleConverter());
-        }
-
-        @Override
-        protected String determineProjectDependencyArtifactId(ProjectDependency dependency) {
-            // Don't use artifact id hacks when the target project has the maven-publish plugin applied
-            if (dependency.getDependencyProject().getPlugins().hasPlugin(MavenPublishPlugin.class)) {
-                return dependency.getDependencyProject().getName();
-            }
-            return new ProjectDependencyArtifactIdExtractorHack(dependency).extract();
-        }
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentity.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentity.java
deleted file mode 100644
index 3b6473c..0000000
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal;
-
-import org.gradle.api.artifacts.Module;
-
-public class ModuleBackedMavenProjectIdentity implements MavenProjectIdentity {
-
-    private final Module module;
-
-    public ModuleBackedMavenProjectIdentity(Module module) {
-        this.module = module;
-    }
-
-    public String getArtifactId() {
-        return module.getName();
-    }
-
-    public String getGroupId() {
-        return module.getGroup();
-    }
-
-    public String getVersion() {
-        return module.getVersion();
-    }
-
-    public String getPackaging() {
-        return null;
-    }
-}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifact.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifact.java
new file mode 100644
index 0000000..db4a67f
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifact.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.artifact;
+
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.publish.maven.MavenArtifact;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+
+public class DefaultMavenArtifact implements MavenArtifact {
+    private final DefaultTaskDependency buildDependencies = new DefaultTaskDependency();
+    private final File file;
+    private String extension;
+    private String classifier;
+
+    public DefaultMavenArtifact(File file, String extension, String classifier) {
+        this.file = file;
+        this.extension = extension;
+        // Handle empty classifiers that come from PublishArtifact and AbstractArchiveTask
+        this.classifier = GUtil.elvis(classifier, null);
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public void setExtension(String extension) {
+        this.extension = extension;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public void setClassifier(String classifier) {
+        this.classifier = classifier;
+    }
+
+    public void builtBy(Object... tasks) {
+        buildDependencies.add(tasks);
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return buildDependencies;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifactSet.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifactSet.java
new file mode 100644
index 0000000..6f5ea60
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/DefaultMavenArtifactSet.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven.internal.artifact;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyInternal;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.publish.maven.MavenArtifact;
+import org.gradle.api.publish.maven.MavenArtifactSet;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultMavenArtifactSet extends DefaultDomainObjectSet<MavenArtifact> implements MavenArtifactSet {
+    private final String publicationName;
+    private final TaskDependencyInternal builtBy = new ArtifactsTaskDependency();
+    private final ArtifactsFileCollection files = new ArtifactsFileCollection();
+    private final NotationParser<MavenArtifact> mavenArtifactParser;
+
+    public DefaultMavenArtifactSet(String publicationName, NotationParser<MavenArtifact> mavenArtifactParser) {
+        super(MavenArtifact.class);
+        this.publicationName = publicationName;
+        this.mavenArtifactParser = mavenArtifactParser;
+    }
+
+    public MavenArtifact artifact(Object source) {
+        MavenArtifact artifact = mavenArtifactParser.parseNotation(source);
+        add(artifact);
+        return artifact;
+    }
+
+    public MavenArtifact artifact(Object source, Action<? super MavenArtifact> config) {
+        MavenArtifact artifact = artifact(source);
+        config.execute(artifact);
+        return artifact;
+    }
+
+    public FileCollection getFiles() {
+        return files;
+    }
+
+    private class ArtifactsFileCollection extends AbstractFileCollection {
+
+        public String getDisplayName() {
+            return String.format("artifacts for " + publicationName + " publication");
+        }
+
+        @Override
+        public TaskDependency getBuildDependencies() {
+            return builtBy;
+        }
+
+        public Set<File> getFiles() {
+            Set<File> files = new LinkedHashSet<File>();
+            for (MavenArtifact artifact : DefaultMavenArtifactSet.this) {
+                files.add(artifact.getFile());
+            }
+            return files;
+        }
+    }
+
+    private class ArtifactsTaskDependency extends AbstractTaskDependency {
+        public void resolve(TaskDependencyResolveContext context) {
+            for (MavenArtifact mavenArtifact : DefaultMavenArtifactSet.this) {
+                context.add(mavenArtifact);
+            }
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactory.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactory.java
new file mode 100644
index 0000000..14ca0f5
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactory.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.artifact;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.notations.NotationParserBuilder;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.internal.notations.api.UnsupportedNotationException;
+import org.gradle.api.internal.notations.parsers.MapKey;
+import org.gradle.api.internal.notations.parsers.MapNotationParser;
+import org.gradle.api.internal.notations.parsers.TypedNotationParser;
+import org.gradle.api.publish.maven.MavenArtifact;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.internal.Factory;
+import org.gradle.internal.reflect.Instantiator;
+
+import java.io.File;
+import java.util.Collection;
+
+public class MavenArtifactNotationParserFactory implements Factory<NotationParser<MavenArtifact>> {
+    private final Instantiator instantiator;
+    private final FileResolver fileResolver;
+
+    public MavenArtifactNotationParserFactory(Instantiator instantiator, FileResolver fileResolver) {
+        this.instantiator = instantiator;
+        this.fileResolver = fileResolver;
+    }
+
+    public NotationParser<MavenArtifact> create() {
+        FileNotationParser fileNotationParser = new FileNotationParser(fileResolver);
+        ArchiveTaskNotationParser archiveTaskNotationParser = new ArchiveTaskNotationParser();
+        PublishArtifactNotationParser publishArtifactNotationParser = new PublishArtifactNotationParser();
+
+        NotationParser<MavenArtifact> sourceNotationParser = new NotationParserBuilder<MavenArtifact>()
+                .resultingType(MavenArtifact.class)
+                .parser(archiveTaskNotationParser)
+                .parser(publishArtifactNotationParser)
+                .parser(fileNotationParser)
+                .toComposite();
+
+        MavenArtifactMapNotationParser mavenArtifactMapNotationParser = new MavenArtifactMapNotationParser(sourceNotationParser);
+
+        NotationParserBuilder<MavenArtifact> parserBuilder = new NotationParserBuilder<MavenArtifact>()
+                .resultingType(MavenArtifact.class)
+                .parser(archiveTaskNotationParser)
+                .parser(publishArtifactNotationParser)
+                .parser(mavenArtifactMapNotationParser)
+                .parser(fileNotationParser);
+
+        return parserBuilder.toComposite();
+    }
+
+    private class ArchiveTaskNotationParser extends TypedNotationParser<AbstractArchiveTask, MavenArtifact> {
+        private ArchiveTaskNotationParser() {
+            super(AbstractArchiveTask.class);
+        }
+
+        @Override
+        protected MavenArtifact parseType(AbstractArchiveTask archiveTask) {
+            DefaultMavenArtifact artifact = instantiator.newInstance(
+                    DefaultMavenArtifact.class,
+                    archiveTask.getArchivePath(), archiveTask.getExtension(), archiveTask.getClassifier());
+            artifact.builtBy(archiveTask);
+            return artifact;
+        }
+    }
+
+    private class PublishArtifactNotationParser extends TypedNotationParser<PublishArtifact, MavenArtifact> {
+        private PublishArtifactNotationParser() {
+            super(PublishArtifact.class);
+        }
+
+        @Override
+        protected MavenArtifact parseType(PublishArtifact publishArtifact) {
+            DefaultMavenArtifact artifact = instantiator.newInstance(
+                    DefaultMavenArtifact.class,
+                    publishArtifact.getFile(), publishArtifact.getExtension(), publishArtifact.getClassifier());
+            artifact.builtBy(publishArtifact.getBuildDependencies());
+            return artifact;
+        }
+    }
+
+    private class FileNotationParser implements NotationParser<MavenArtifact> {
+        private final NotationParser<File> fileResolverNotationParser;
+
+        private FileNotationParser(FileResolver fileResolver) {
+            this.fileResolverNotationParser = fileResolver.asNotationParser();
+        }
+
+        public MavenArtifact parseNotation(Object notation) throws UnsupportedNotationException {
+            File file = fileResolverNotationParser.parseNotation(notation);
+            return parseFile(file);
+        }
+
+        protected MavenArtifact parseFile(File file) {
+            String extension = StringUtils.substringAfterLast(file.getName(), ".");
+            return instantiator.newInstance(DefaultMavenArtifact.class, file, extension, null);
+        }
+
+        public void describe(Collection<String> candidateFormats) {
+            fileResolverNotationParser.describe(candidateFormats);
+        }
+    }
+
+    private class MavenArtifactMapNotationParser extends MapNotationParser<MavenArtifact> {
+        private final NotationParser<MavenArtifact> sourceNotationParser;
+
+        private MavenArtifactMapNotationParser(NotationParser<MavenArtifact> sourceNotationParser) {
+            this.sourceNotationParser = sourceNotationParser;
+        }
+
+        protected MavenArtifact parseMap(@MapKey("source") Object source) {
+            return sourceNotationParser.parseNotation(source);
+        }
+
+        @Override
+        public void describe(Collection<String> candidateFormats) {
+            candidateFormats.add("Maps containing a 'source' entry, e.g. [source: '/path/to/file', extension: 'zip'].");
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/GeneratePomTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/GeneratePomTaskCreator.java
new file mode 100644
index 0000000..3fa99c5
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/GeneratePomTaskCreator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.plugins;
+
+import  org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
+import org.gradle.api.publish.maven.tasks.GenerateMavenPom;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class GeneratePomTaskCreator {
+
+    private final Project project;
+
+    public GeneratePomTaskCreator(Project project) {
+        this.project = project;
+    }
+
+    public void monitor(PublicationContainer publications) {
+        publications.withType(MavenPublicationInternal.class).all(new Action<MavenPublicationInternal>() {
+            public void execute(MavenPublicationInternal publication) {
+                create(publication);
+            }
+        });
+    }
+
+    private void create(final MavenPublicationInternal publication) {
+        String publicationName = publication.getName();
+
+        String descriptorTaskName = calculateDescriptorTaskName(publicationName);
+        GenerateMavenPom generatePomTask = project.getTasks().add(descriptorTaskName, GenerateMavenPom.class);
+        generatePomTask.setDescription(String.format("Generates the Maven POM file for publication '%s'.", publication.getName()));
+        generatePomTask.setPom(publication.getPom());
+
+        ConventionMapping descriptorTaskConventionMapping = new DslObject(generatePomTask).getConventionMapping();
+        descriptorTaskConventionMapping.map("destination", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new File(project.getBuildDir(), "publications/" + publication.getName() + "/pom-default.xml");
+            }
+        });
+
+        // Wire the generated pom into the publication.
+        publication.setPomFile(generatePomTask.getOutputs().getFiles());
+    }
+
+    private String calculateDescriptorTaskName(String publicationName) {
+        return String.format("generatePomFileFor%sPublication", capitalize(publicationName));
+    }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishDynamicTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishDynamicTaskCreator.java
new file mode 100644
index 0000000..d5b88a7
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishDynamicTaskCreator.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.plugins;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectList;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.ArtifactRepositoryContainer;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
+import org.gradle.api.publish.maven.tasks.PublishToMavenRepository;
+import org.gradle.api.tasks.TaskContainer;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class MavenPublishDynamicTaskCreator {
+
+    final private TaskContainer tasks;
+    private final Task publishLifecycleTask;
+
+    public MavenPublishDynamicTaskCreator(TaskContainer tasks, Task publishLifecycleTask) {
+        this.tasks = tasks;
+        this.publishLifecycleTask = publishLifecycleTask;
+    }
+
+    public void monitor(final PublicationContainer publications, final ArtifactRepositoryContainer repositories) {
+        final NamedDomainObjectSet<MavenPublicationInternal> mavenPublications = publications.withType(MavenPublicationInternal.class);
+        final NamedDomainObjectList<MavenArtifactRepository> mavenRepositories = repositories.withType(MavenArtifactRepository.class);
+
+        mavenPublications.all(new Action<MavenPublicationInternal>() {
+            public void execute(MavenPublicationInternal publication) {
+                for (MavenArtifactRepository repository : mavenRepositories) {
+                    maybeCreatePublishTask(publication, repository);
+                }
+            }
+        });
+
+        mavenRepositories.all(new Action<MavenArtifactRepository>() {
+            public void execute(MavenArtifactRepository repository) {
+                for (MavenPublicationInternal publication : mavenPublications) {
+                    maybeCreatePublishTask(publication, repository);
+                }
+            }
+        });
+
+        // Note: we aren't supporting removal of repositories or publications
+        // Note: we also aren't considering that repos have a setName, so their name can change
+        //       (though this is a violation of the Named contract)
+    }
+
+    private void maybeCreatePublishTask(MavenPublicationInternal publication, MavenArtifactRepository repository) {
+        String publicationName = publication.getName();
+        String repositoryName = repository.getName();
+
+        String publishTaskName = calculatePublishTaskName(publicationName, repositoryName);
+        if (tasks.findByName(publishTaskName) == null) {
+            PublishToMavenRepository publishTask = tasks.add(publishTaskName, PublishToMavenRepository.class);
+            publishTask.setPublication(publication);
+            publishTask.setRepository(repository);
+            publishTask.setGroup("publishing");
+            publishTask.setDescription(String.format("Publishes Maven publication '%s' to Maven repository '%s'.", publicationName, repositoryName));
+
+            publishLifecycleTask.dependsOn(publishTask);
+        }
+    }
+
+    private String calculatePublishTaskName(String publicationName, String repositoryName) {
+        return String.format("publish%sPublicationTo%sRepository", capitalize(publicationName), capitalize(repositoryName));
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishLocalDynamicTaskCreator.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishLocalDynamicTaskCreator.java
new file mode 100644
index 0000000..3709bc3
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/plugins/MavenPublishLocalDynamicTaskCreator.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.plugins;
+
+import org.gradle.api.Action;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.Task;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
+import org.gradle.api.publish.maven.tasks.PublishToMavenLocal;
+import org.gradle.api.tasks.TaskContainer;
+
+import static org.apache.commons.lang.StringUtils.capitalize;
+
+public class MavenPublishLocalDynamicTaskCreator {
+
+    final private TaskContainer tasks;
+    private final Task publishToMavenLocalLifecycleTask;
+
+    public MavenPublishLocalDynamicTaskCreator(TaskContainer tasks, Task publishToMavenLocalLifecycleTask) {
+        this.tasks = tasks;
+        this.publishToMavenLocalLifecycleTask = publishToMavenLocalLifecycleTask;
+    }
+
+    public void monitor(final PublicationContainer publications) {
+        final NamedDomainObjectSet<MavenPublicationInternal> mavenPublications = publications.withType(MavenPublicationInternal.class);
+
+        mavenPublications.all(new Action<MavenPublicationInternal>() {
+            public void execute(MavenPublicationInternal publication) {
+                createInstallTask(publication);
+            }
+        });
+        // Note: we aren't supporting removal of publications
+    }
+
+    private void createInstallTask(MavenPublicationInternal publication) {
+        String publicationName = publication.getName();
+        String installTaskName = calculatePublishLocalTaskName(publicationName);
+
+        PublishToMavenLocal publishTask = tasks.add(installTaskName, PublishToMavenLocal.class);
+        publishTask.setPublication(publication);
+        publishTask.setGroup("publishing");
+        publishTask.setDescription(String.format("Publishes Maven publication '%s' to the local Maven cache.", publicationName));
+
+        publishToMavenLocalLifecycleTask.dependsOn(publishTask);
+    }
+
+    private String calculatePublishLocalTaskName(String publicationName) {
+        return String.format("publish%sPublicationToMavenLocal", capitalize(publicationName));
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPom.java
new file mode 100644
index 0000000..9c692f0
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPom.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publication;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.UserCodeAction;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
+import org.gradle.listener.ActionBroadcast;
+
+import java.util.Set;
+
+public class DefaultMavenPom implements MavenPomInternal {
+
+    private final ActionBroadcast<XmlProvider> xmlAction = new ActionBroadcast<XmlProvider>();
+    private final MavenPublicationInternal mavenPublication;
+
+    public DefaultMavenPom(MavenPublicationInternal mavenPublication) {
+        this.mavenPublication = mavenPublication;
+    }
+
+    public void withXml(Action<? super XmlProvider> action) {
+        xmlAction.add(new UserCodeAction<XmlProvider>("Could not apply withXml() to generated POM", action));
+    }
+
+    public Action<XmlProvider> getXmlAction() {
+        return xmlAction;
+    }
+
+    public String getPackaging() {
+        return mavenPublication.determinePackagingFromArtifacts();
+    }
+
+    public MavenProjectIdentity getProjectIdentity() {
+        return mavenPublication.getMavenProjectIdentity();
+    }
+
+    public Set<Dependency> getRuntimeDependencies() {
+        return mavenPublication.getRuntimeDependencies();
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenProjectIdentity.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenProjectIdentity.java
new file mode 100644
index 0000000..19089f0
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenProjectIdentity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publication;
+
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
+
+public class DefaultMavenProjectIdentity implements MavenProjectIdentity {
+    private String groupId;
+    private String artifactId;
+    private String version;
+
+    public DefaultMavenProjectIdentity(String groupId, String artifactId, String version) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.version = version;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(String groupId) {
+        this.groupId = groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public void setArtifactId(String artifactId) {
+        this.artifactId = artifactId;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java
new file mode 100644
index 0000000..42d1638
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publication;
+
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.component.SoftwareComponent;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.component.SoftwareComponentInternal;
+import org.gradle.api.internal.component.Usage;
+import org.gradle.api.internal.file.UnionFileCollection;
+import org.gradle.api.internal.notations.api.NotationParser;
+import org.gradle.api.publish.maven.MavenArtifact;
+import org.gradle.api.publish.maven.MavenArtifactSet;
+import org.gradle.api.publish.maven.MavenPom;
+import org.gradle.api.publish.maven.internal.artifact.DefaultMavenArtifactSet;
+import org.gradle.api.publish.maven.internal.publisher.MavenNormalizedPublication;
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultMavenPublication implements MavenPublicationInternal {
+
+    private final String name;
+    private final MavenPomInternal pom;
+    private final MavenProjectIdentity projectIdentity;
+    private final DefaultMavenArtifactSet mavenArtifacts;
+    private final Set<Dependency> runtimeDependencies = new LinkedHashSet<Dependency>();
+    private FileCollection pomFile;
+    private SoftwareComponentInternal component;
+
+    public DefaultMavenPublication(
+            String name, MavenProjectIdentity projectIdentity, NotationParser<MavenArtifact> mavenArtifactParser, Instantiator instantiator
+    ) {
+        this.name = name;
+        this.projectIdentity = projectIdentity;
+        mavenArtifacts = instantiator.newInstance(DefaultMavenArtifactSet.class, name, mavenArtifactParser);
+        pom = instantiator.newInstance(DefaultMavenPom.class, this);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public MavenPomInternal getPom() {
+        return pom;
+    }
+
+    public void setPomFile(FileCollection pomFile) {
+        this.pomFile = pomFile;
+    }
+
+
+    public void pom(Action<? super MavenPom> configure) {
+        configure.execute(pom);
+    }
+
+    public void from(SoftwareComponent component) {
+        if (this.component != null) {
+            throw new InvalidUserDataException(String.format("Maven publication '%s' cannot include multiple components", name));
+        }
+        this.component = (SoftwareComponentInternal) component;
+
+        for (Usage usage : this.component.getUsages()) {
+            // TODO:DAZ Need a smarter way to map usage to artifact classifier
+            for (PublishArtifact publishArtifact : usage.getArtifacts()) {
+                artifact(publishArtifact);
+            }
+
+            // TODO:DAZ Need a smarter way to map usage to scope
+            runtimeDependencies.addAll(usage.getDependencies());
+        }
+    }
+
+    public MavenArtifact artifact(Object source) {
+        return mavenArtifacts.artifact(source);
+    }
+
+    public MavenArtifact artifact(Object source, Action<? super MavenArtifact> config) {
+        return mavenArtifacts.artifact(source, config);
+    }
+
+    public MavenArtifactSet getArtifacts() {
+        return mavenArtifacts;
+    }
+
+    public void setArtifacts(Iterable<?> sources) {
+        mavenArtifacts.clear();
+        for (Object source : sources) {
+            artifact(source);
+        }
+    }
+
+    public FileCollection getPublishableFiles() {
+        return new UnionFileCollection(mavenArtifacts.getFiles(), pomFile);
+    }
+
+    public MavenProjectIdentity getMavenProjectIdentity() {
+        return projectIdentity;
+    }
+
+    public Set<Dependency> getRuntimeDependencies() {
+        return runtimeDependencies;
+    }
+
+    public MavenNormalizedPublication asNormalisedPublication() {
+        return new MavenNormalizedPublication(name, getPomFile(), projectIdentity, getArtifacts());
+    }
+
+    private File getPomFile() {
+        if (pomFile == null) {
+            throw new IllegalStateException("pomFile not set for publication");
+        }
+        return pomFile.getSingleFile();
+    }
+
+    public String determinePackagingFromArtifacts() {
+        for (MavenArtifact mavenArtifact : mavenArtifacts) {
+            if (!GUtil.isTrue(mavenArtifact.getClassifier()) && GUtil.isTrue(mavenArtifact.getExtension())) {
+                return mavenArtifact.getExtension();
+            }
+        }
+        return "pom";
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPomInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPomInternal.java
new file mode 100644
index 0000000..09f4013
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPomInternal.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publication;
+
+import org.gradle.api.Action;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.publish.maven.MavenPom;
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
+
+import java.util.Set;
+
+public interface MavenPomInternal extends MavenPom {
+
+    MavenProjectIdentity getProjectIdentity();
+
+    String getPackaging();
+
+    Set<Dependency> getRuntimeDependencies();
+
+    Action<XmlProvider> getXmlAction();
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPublicationInternal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPublicationInternal.java
new file mode 100644
index 0000000..fb3db51
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publication/MavenPublicationInternal.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publication;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.gradle.api.publish.maven.internal.publisher.MavenNormalizedPublication;
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
+
+import java.util.Set;
+
+public interface MavenPublicationInternal extends MavenPublication {
+
+    MavenPomInternal getPom();
+
+    void setPomFile(FileCollection pomFile);
+
+    FileCollection getPublishableFiles();
+
+    MavenProjectIdentity getMavenProjectIdentity();
+
+    Set<Dependency> getRuntimeDependencies();
+
+    MavenNormalizedPublication asNormalisedPublication();
+
+    // TODO:DAZ Remove this attempt to guess packaging from artifacts. Packaging should come from component, or be explicitly set.
+    String determinePackagingFromArtifacts();
+
+}
+
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenPublisher.java
new file mode 100644
index 0000000..6ef2a0b
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/AntTaskBackedMavenPublisher.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publisher;
+
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.Pom;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.publication.maven.internal.ant.CustomDeployTask;
+import org.gradle.api.publication.maven.internal.ant.EmptyMavenSettingsSupplier;
+import org.gradle.api.publication.maven.internal.ant.MavenSettingsSupplier;
+import org.gradle.api.publication.maven.internal.ant.NoInstallDeployTaskFactory;
+import org.gradle.api.publish.maven.InvalidMavenPublicationException;
+import org.gradle.api.publish.maven.MavenArtifact;
+import org.gradle.api.specs.Spec;
+import org.gradle.internal.Factory;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.AntUtil;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Set;
+
+public class AntTaskBackedMavenPublisher implements MavenPublisher {
+    private final Factory<LoggingManagerInternal> loggingManagerFactory;
+
+    private static Logger logger = LoggerFactory.getLogger(AntTaskBackedMavenPublisher.class);
+    private final Factory<File> temporaryDirFactory;
+
+    public AntTaskBackedMavenPublisher(Factory<LoggingManagerInternal> loggingManagerFactory, Factory<File> temporaryDirFactory) {
+        this.loggingManagerFactory = loggingManagerFactory;
+        this.temporaryDirFactory = temporaryDirFactory;
+    }
+
+    public void publish(MavenNormalizedPublication publication, MavenArtifactRepository artifactRepository) {
+        logger.info("Publishing to repository {}", artifactRepository);
+        CustomDeployTask deployTask = createDeployTask();
+
+        MavenSettingsSupplier mavenSettingsSupplier = new EmptyMavenSettingsSupplier();
+        mavenSettingsSupplier.supply(deployTask);
+
+        addRepository(deployTask, artifactRepository);
+        addPomAndArtifacts(deployTask, publication);
+        execute(deployTask);
+
+        mavenSettingsSupplier.done();
+    }
+
+    private CustomDeployTask createDeployTask() {
+        Factory<CustomDeployTask> deployTaskFactory = new NoInstallDeployTaskFactory(temporaryDirFactory);
+        CustomDeployTask deployTask = deployTaskFactory.create();
+        deployTask.setProject(AntUtil.createProject());
+        deployTask.setUniqueVersion(true);
+        return deployTask;
+    }
+
+    private void addRepository(CustomDeployTask deployTask, MavenArtifactRepository artifactRepository) {
+        RemoteRepository mavenRepository = new MavenDeployerConfigurer(artifactRepository).createRepository();
+        deployTask.addRemoteRepository(mavenRepository);
+    }
+
+    private void addPomAndArtifacts(InstallDeployTaskSupport installOrDeployTask, MavenNormalizedPublication publication) {
+        Pom pom = new Pom();
+        pom.setProject(installOrDeployTask.getProject());
+        pom.setFile(publication.getPomFile());
+        installOrDeployTask.addPom(pom);
+
+        MavenArtifact mainArtifact = determineMainArtifact(publication.getName(), publication.getArtifacts());
+        installOrDeployTask.setFile(mainArtifact == null ? publication.getPomFile() : mainArtifact.getFile());
+
+        for (MavenArtifact mavenArtifact : publication.getArtifacts()) {
+            if (mavenArtifact == mainArtifact) {
+                continue;
+            }
+            AttachedArtifact attachedArtifact = installOrDeployTask.createAttach();
+            attachedArtifact.setClassifier(GUtil.elvis(mavenArtifact.getClassifier(), ""));
+            attachedArtifact.setType(GUtil.elvis(mavenArtifact.getExtension(), ""));
+            attachedArtifact.setFile(mavenArtifact.getFile());
+        }
+    }
+
+    private MavenArtifact determineMainArtifact(String publicationName, Set<MavenArtifact> mavenArtifacts) {
+        Set<MavenArtifact> candidateMainArtifacts = CollectionUtils.filter(mavenArtifacts, new Spec<MavenArtifact>() {
+            public boolean isSatisfiedBy(MavenArtifact element) {
+                return element.getClassifier() == null || element.getClassifier().length() == 0;
+            }
+        });
+        if (candidateMainArtifacts.isEmpty()) {
+            return null;
+        }
+        if (candidateMainArtifacts.size() > 1) {
+            throw new InvalidMavenPublicationException(publicationName, "Cannot determine main artifact - multiple artifacts found with empty classifier.");
+        }
+        return candidateMainArtifacts.iterator().next();
+    }
+
+
+    private void execute(InstallDeployTaskSupport deployTask) {
+            LoggingManagerInternal loggingManager = loggingManagerFactory.create();
+            loggingManager.captureStandardOutput(LogLevel.INFO).start();
+            try {
+                deployTask.execute();
+            } finally {
+                loggingManager.stop();
+            }
+        }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenDeployerConfigurer.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenDeployerConfigurer.java
new file mode 100644
index 0000000..a57c03f
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenDeployerConfigurer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publisher;
+
+import org.apache.maven.artifact.ant.Authentication;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.maven.MavenDeployer;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.artifacts.repositories.PasswordCredentials;
+
+class MavenDeployerConfigurer implements Action<MavenDeployer> {
+
+    private final MavenArtifactRepository artifactRepository;
+
+    public MavenDeployerConfigurer(MavenArtifactRepository artifactRepository) {
+        this.artifactRepository = artifactRepository;
+    }
+
+    public void execute(MavenDeployer deployer) {
+        deployer.setRepository(createRepository());
+    }
+
+    public RemoteRepository createRepository() {
+        RemoteRepository remoteRepository = new RemoteRepository();
+        remoteRepository.setUrl(artifactRepository.getUrl().toString());
+
+        PasswordCredentials credentials = artifactRepository.getCredentials();
+        String username = credentials.getUsername();
+        String password = credentials.getPassword();
+
+        if (username != null || password != null) {
+            Authentication authentication = new Authentication();
+            authentication.setUserName(username);
+            authentication.setPassword(password);
+            remoteRepository.addAuthentication(authentication);
+        }
+
+        return remoteRepository;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenNormalizedPublication.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenNormalizedPublication.java
new file mode 100644
index 0000000..ca44bbf
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenNormalizedPublication.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publisher;
+
+import org.gradle.api.publish.maven.MavenArtifact;
+
+import java.io.File;
+import java.util.Set;
+
+public class MavenNormalizedPublication {
+
+    private final String name;
+    private final File pomFile;
+    private final MavenProjectIdentity projectIdentity;
+    private final Set<MavenArtifact> artifacts;
+
+    public MavenNormalizedPublication(String name, File pomFile, MavenProjectIdentity projectIdentity, Set<MavenArtifact> artifacts) {
+        this.name = name;
+        this.pomFile = pomFile;
+        this.projectIdentity = projectIdentity;
+        this.artifacts = artifacts;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public File getPomFile() {
+        return pomFile;
+    }
+
+    public Set<MavenArtifact> getArtifacts() {
+        return artifacts;
+    }
+
+    public MavenProjectIdentity getProjectIdentity() {
+        return projectIdentity;
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenProjectIdentity.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenProjectIdentity.java
new file mode 100644
index 0000000..6eeead4
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenProjectIdentity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publisher;
+
+public interface MavenProjectIdentity {
+
+    String getGroupId();
+
+    void setGroupId(String groupId);
+
+    String getArtifactId();
+
+    void setArtifactId(String artifactId);
+
+    String getVersion();
+
+    void setVersion(String version);
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenPublisher.java
new file mode 100644
index 0000000..ba86eb5
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/MavenPublisher.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven.internal.publisher;
+
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+
+public interface MavenPublisher {
+    void publish(MavenNormalizedPublication publication, MavenArtifactRepository artifactRepository);
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/StaticLockingMavenPublisher.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/StaticLockingMavenPublisher.java
new file mode 100644
index 0000000..d85ecea
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/StaticLockingMavenPublisher.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven.internal.publisher;
+
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A MavenPublisher that restricts publishing to a single thread per Classloader that loads this class.
+ * This is required to prevent concurrent access to the Maven Ant Tasks, which hold static state.
+ */
+public class StaticLockingMavenPublisher implements MavenPublisher {
+    private static final Lock STATIC_LOCK = new ReentrantLock();
+    private final MavenPublisher delegate;
+
+    public StaticLockingMavenPublisher(MavenPublisher delegate) {
+        this.delegate = delegate;
+    }
+
+    public void publish(MavenNormalizedPublication publication, MavenArtifactRepository artifactRepository) {
+        STATIC_LOCK.lock();
+        try {
+            delegate.publish(publication, artifactRepository);
+        } finally {
+            STATIC_LOCK.unlock();
+        }
+    }
+}
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
new file mode 100644
index 0000000..5a263d1
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisher.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publisher;
+
+import org.apache.commons.lang.ObjectUtils;
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
+import org.gradle.api.publish.internal.PublicationFieldValidator;
+import org.gradle.api.publish.maven.InvalidMavenPublicationException;
+import org.gradle.api.publish.maven.MavenArtifact;
+import org.gradle.internal.UncheckedException;
+import org.gradle.mvn3.org.apache.maven.model.Model;
+import org.gradle.mvn3.org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.gradle.mvn3.org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ValidatingMavenPublisher implements MavenPublisher {
+    private static final java.lang.String ID_REGEX = "[A-Za-z0-9_\\-.]+";
+    private final MavenPublisher delegate;
+
+    public ValidatingMavenPublisher(MavenPublisher delegate) {
+        this.delegate = delegate;
+    }
+
+    public void publish(MavenNormalizedPublication publication, MavenArtifactRepository artifactRepository) {
+        validateIdentity(publication);
+        validateArtifacts(publication);
+        checkNoDuplicateArtifacts(publication);
+
+        delegate.publish(publication, artifactRepository);
+    }
+
+    private void validateIdentity(MavenNormalizedPublication publication) {
+        MavenProjectIdentity projectIdentity = publication.getProjectIdentity();
+        Model model = parsePomFileIntoMavenModel(publication);
+        field(publication, "groupId", projectIdentity.getGroupId())
+                .validMavenIdentifier()
+                .matches(model.getGroupId());
+        field(publication, "artifactId", projectIdentity.getArtifactId())
+                .validMavenIdentifier()
+                .matches(model.getArtifactId());
+        field(publication, "version", projectIdentity.getVersion())
+                .notEmpty()
+                .validInFileName()
+                .matches(model.getVersion());
+    }
+
+    private Model parsePomFileIntoMavenModel(MavenNormalizedPublication publication) {
+        try {
+            FileReader reader = new FileReader(publication.getPomFile());
+            Model model = new MavenXpp3Reader().read(reader);
+            model.setPomFile(publication.getPomFile());
+            return model;
+        } catch (XmlPullParserException parseException) {
+            throw new InvalidMavenPublicationException(publication.getName(),
+                    "POM file is invalid. Check any modifications you have made to the POM file.",
+                    parseException);
+        } catch (Exception ex) {
+            throw UncheckedException.throwAsUncheckedException(ex);
+        }
+    }
+
+    private void validateArtifacts(MavenNormalizedPublication publication) {
+        for (MavenArtifact artifact : publication.getArtifacts()) {
+            field(publication, "artifact extension", artifact.getExtension())
+                    .notNull()
+                    .validInFileName();
+            field(publication, "artifact classifier", artifact.getClassifier())
+                    .optionalNotEmpty()
+                    .validInFileName();
+
+            checkCanPublish(publication.getName(), artifact);
+        }
+    }
+
+    private void checkNoDuplicateArtifacts(MavenNormalizedPublication publication) {
+        Set<MavenArtifact> verified = new HashSet<MavenArtifact>();
+
+        for (MavenArtifact artifact : publication.getArtifacts()) {
+            checkNotDuplicate(publication, verified, artifact.getExtension(), artifact.getClassifier());
+            verified.add(artifact);
+        }
+
+        // Check that the pom file isn't duplicated
+        checkNotDuplicate(publication, verified, "pom", null);
+    }
+
+    private void checkNotDuplicate(MavenNormalizedPublication publication, Set<MavenArtifact> artifacts, String extension, String classifier) {
+        for (MavenArtifact artifact : artifacts) {
+            if (ObjectUtils.equals(artifact.getExtension(), extension) && ObjectUtils.equals(artifact.getClassifier(), classifier)) {
+                String message = String.format(
+                        "multiple artifacts with the identical extension and classifier ('%s', '%s').", extension, classifier
+                );
+                throw new InvalidMavenPublicationException(publication.getName(), message);
+            }
+        }
+    }
+
+    private void checkCanPublish(String publicationName, MavenArtifact artifact) {
+        File artifactFile = artifact.getFile();
+        if (artifactFile == null || !artifactFile.exists()) {
+            throw new InvalidMavenPublicationException(publicationName, String.format("artifact file does not exist: '%s'", artifactFile));
+        }
+        if (artifactFile.isDirectory()) {
+            throw new InvalidMavenPublicationException(publicationName, String.format("artifact file is a directory: '%s'", artifactFile));
+        }
+    }
+
+    private MavenFieldValidator field(MavenNormalizedPublication publication, String name, String value) {
+        return new MavenFieldValidator(publication.getName(), name, value);
+    }
+
+    private static class MavenFieldValidator extends PublicationFieldValidator<MavenFieldValidator> {
+
+        private MavenFieldValidator(String publicationName, String name, String value) {
+            super(MavenFieldValidator.class, publicationName, name, value);
+        }
+
+        public MavenFieldValidator validMavenIdentifier() {
+            notEmpty();
+            if (!value.matches(ID_REGEX)) {
+                throw failure(String.format("%s is not a valid Maven identifier (%s).", name, ID_REGEX));
+            }
+            return this;
+        }
+
+        public MavenFieldValidator matches(String expectedValue) {
+            if (!value.equals(expectedValue)) {
+                throw failure(String.format("supplied %s does not match POM file (cannot edit %1$s directly in the POM file).", name));
+            }
+            return this;
+        }
+
+        @Override
+        protected InvalidMavenPublicationException failure(String message) {
+            return new InvalidMavenPublicationException(publicationName, message);
+        }
+    }
+}
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
new file mode 100644
index 0000000..4de704a
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGenerator.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.tasks;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.XmlProvider;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.xml.XmlTransformer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+
+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 XmlTransformer xmlTransformer = new XmlTransformer();
+
+    public MavenPomFileGenerator() {
+        mavenProject.setModelVersion(POM_VERSION);
+    }
+
+    public MavenPomFileGenerator setGroupId(String groupId) {
+        getModel().setGroupId(groupId);
+        return this;
+    }
+
+    public MavenPomFileGenerator setArtifactId(String artifactId) {
+        getModel().setArtifactId(artifactId);
+        return this;
+    }
+
+    public MavenPomFileGenerator setVersion(String version) {
+        getModel().setVersion(version);
+        return this;
+    }
+
+    public MavenPomFileGenerator setPackaging(String packaging) {
+        getModel().setPackaging(packaging);
+        return this;
+    }
+
+    public MavenPomFileGenerator withXml(final Action<XmlProvider> action) {
+        xmlTransformer.addAction(action);
+        return this;
+    }
+
+    private Model getModel() {
+        return mavenProject.getModel();
+    }
+
+    public MavenPomFileGenerator writeTo(File file) {
+        xmlTransformer.transform(file, POM_FILE_ENCODING, new Action<Writer>() {
+            public void execute(Writer writer) {
+                try {
+                    mavenProject.writeModel(writer);
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        });
+        return this;
+    }
+
+    public void addRuntimeDependency(org.gradle.api.artifacts.Dependency dependency) {
+        if (dependency instanceof ModuleDependency) {
+            addDependency((ModuleDependency) dependency, "runtime");
+        }
+    }
+
+    private void addDependency(ModuleDependency moduleDependency, String scope) {
+        if (moduleDependency.getArtifacts().size() == 0) {
+            getModel().addDependency(createMavenDependency(moduleDependency, moduleDependency.getName(), null, scope, null));
+        } else {
+            for (DependencyArtifact artifact : moduleDependency.getArtifacts()) {
+                getModel().addDependency(createMavenDependency(moduleDependency, artifact.getName(), artifact.getType(), scope, artifact.getClassifier()));
+            }
+        }
+    }
+
+    private Dependency createMavenDependency(ModuleDependency dependency, String name, String type, String scope, String classifier) {
+        Dependency mavenDependency =  new Dependency();
+        mavenDependency.setGroupId(dependency.getGroup());
+        if (dependency instanceof ProjectDependency) {
+            mavenDependency.setArtifactId(determineProjectDependencyArtifactId((ProjectDependency) dependency));
+        } else {
+            mavenDependency.setArtifactId(name);
+        }
+        mavenDependency.setVersion(dependency.getVersion());
+        mavenDependency.setType(type);
+        mavenDependency.setScope(scope);
+        mavenDependency.setOptional(false);
+        mavenDependency.setClassifier(classifier);
+        return mavenDependency;
+    }
+
+    private String determineProjectDependencyArtifactId(ProjectDependency dependency) {
+        return dependency.getDependencyProject().getName();
+    }
+
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPlugin.java
index a702af7..3ebb01f 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
@@ -17,24 +17,27 @@
 package org.gradle.api.publish.maven.plugins;
 
 import org.gradle.api.*;
-import org.gradle.api.component.SoftwareComponent;
-import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.artifacts.Module;
 import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
-import org.gradle.api.internal.component.SoftwareComponentInternal;
-import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.notations.api.NotationParser;
 import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.internal.PublicationContainerInternal;
+import org.gradle.api.publish.internal.PublicationFactory;
+import org.gradle.api.publish.maven.MavenArtifact;
 import org.gradle.api.publish.maven.MavenPublication;
-import org.gradle.api.publish.maven.internal.DefaultMavenPublication;
-import org.gradle.api.publish.maven.internal.MavenPublishDynamicTaskCreator;
-import org.gradle.api.publish.maven.internal.MavenPublishLocalDynamicTaskCreator;
-import org.gradle.api.publish.maven.internal.ModuleBackedMavenProjectIdentity;
+import org.gradle.api.publish.maven.internal.artifact.MavenArtifactNotationParserFactory;
+import org.gradle.api.publish.maven.internal.plugins.GeneratePomTaskCreator;
+import org.gradle.api.publish.maven.internal.plugins.MavenPublishDynamicTaskCreator;
+import org.gradle.api.publish.maven.internal.plugins.MavenPublishLocalDynamicTaskCreator;
+import org.gradle.api.publish.maven.internal.publication.DefaultMavenProjectIdentity;
+import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication;
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
 import org.gradle.api.publish.plugins.PublishingPlugin;
 import org.gradle.api.tasks.TaskContainer;
 import org.gradle.internal.reflect.Instantiator;
 
 import javax.inject.Inject;
-import java.io.File;
-import java.util.concurrent.Callable;
 
 /**
  * Adds the ability to publish in the Maven format to Maven repositories.
@@ -48,60 +51,65 @@ public class MavenPublishPlugin implements Plugin<Project> {
 
     private final Instantiator instantiator;
     private final DependencyMetaDataProvider dependencyMetaDataProvider;
+    private final FileResolver fileResolver;
 
     @Inject
-    public MavenPublishPlugin(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider) {
+    public MavenPublishPlugin(Instantiator instantiator, DependencyMetaDataProvider dependencyMetaDataProvider, FileResolver fileResolver) {
         this.instantiator = instantiator;
         this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+        this.fileResolver = fileResolver;
     }
 
     public void apply(final Project project) {
         project.getPlugins().apply(PublishingPlugin.class);
-        final PublishingExtension extension = project.getExtensions().getByType(PublishingExtension.class);
-
-        // Create the default publication for any components
-        project.getComponents().all(new Action<SoftwareComponent>() {
-            public void execute(SoftwareComponent softwareComponent) {
-                if (!extension.getPublications().withType(MavenPublication.class).isEmpty()) {
-                    throw new IllegalStateException("Cannot publish multiple components to Maven : need to fix this before we add another softwareComponent");
-                }
-                extension.getPublications().add(createPublication("maven", project, softwareComponent));
-            }
-        });
 
-        TaskContainer tasks = project.getTasks();
+        final TaskContainer tasks = project.getTasks();
+        final Task publishLifecycleTask = tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
+        final Task publishLocalLifecycleTask = tasks.add(PUBLISH_LOCAL_LIFECYCLE_TASK_NAME);
+        publishLocalLifecycleTask.setDescription("Publishes all Maven publications produced by this project to the local Maven cache.");
+        publishLocalLifecycleTask.setGroup("publishing");
 
-        // Create publish tasks automatically for any Maven publication and repository combinations
-        Task publishLifecycleTask = tasks.getByName(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME);
-        MavenPublishDynamicTaskCreator publishTaskCreator = new MavenPublishDynamicTaskCreator(tasks, publishLifecycleTask);
-        publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
+        project.getExtensions().configure(PublishingExtension.class, new Action<PublishingExtension>() {
+            public void execute(PublishingExtension extension) {
+                final PublicationContainerInternal publicationContainer = (PublicationContainerInternal) extension.getPublications();
+                publicationContainer.registerFactory(MavenPublication.class, new MavenPublicationFactory(dependencyMetaDataProvider, instantiator, fileResolver));
 
-        // Create install tasks automatically for any Maven publication
-        Task publishLocalLifecycleTask = tasks.add(PUBLISH_LOCAL_LIFECYCLE_TASK_NAME);
-        MavenPublishLocalDynamicTaskCreator publishLocalTaskCreator = new MavenPublishLocalDynamicTaskCreator(tasks, publishLocalLifecycleTask);
-        publishLocalTaskCreator.monitor(extension.getPublications());
-    }
+                // Create generatePom tasks for any Maven publication
+                GeneratePomTaskCreator descriptorGenerationTaskCreator = new GeneratePomTaskCreator(project);
+                descriptorGenerationTaskCreator.monitor(extension.getPublications());
 
-    private MavenPublication createPublication(final String name, final Project project, SoftwareComponent component) {
-        SoftwareComponentInternal componentInternal = (SoftwareComponentInternal) component;
+                // Create publish tasks automatically for any Maven publication and repository combinations
+                MavenPublishDynamicTaskCreator publishTaskCreator = new MavenPublishDynamicTaskCreator(tasks, publishLifecycleTask);
+                publishTaskCreator.monitor(extension.getPublications(), extension.getRepositories());
 
-        Callable<Object> pomDirCallable = new Callable<Object>() {
-            public Object call() {
-                return new File(project.getBuildDir(), "publications/" + name);
+                // Create install tasks automatically for any Maven publication
+                MavenPublishLocalDynamicTaskCreator publishLocalTaskCreator = new MavenPublishLocalDynamicTaskCreator(tasks, publishLocalLifecycleTask);
+                publishLocalTaskCreator.monitor(extension.getPublications());
             }
-        };
+        });
+    }
 
-        ModuleBackedMavenProjectIdentity projectIdentity = new ModuleBackedMavenProjectIdentity(dependencyMetaDataProvider.getModule());
+    private class MavenPublicationFactory implements PublicationFactory {
+        private final Instantiator instantiator;
+        private final DependencyMetaDataProvider dependencyMetaDataProvider;
+        private final FileResolver fileResolver;
 
-        DefaultMavenPublication publication = instantiator.newInstance(
-                DefaultMavenPublication.class,
-                name, instantiator, projectIdentity, componentInternal, null
-        );
+        private MavenPublicationFactory(DependencyMetaDataProvider dependencyMetaDataProvider, Instantiator instantiator, FileResolver fileResolver) {
+            this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+            this.instantiator = instantiator;
+            this.fileResolver = fileResolver;
+        }
 
-        ConventionMapping descriptorConventionMapping = new DslObject(publication).getConventionMapping();
-        descriptorConventionMapping.map("pomDir", pomDirCallable);
+        public MavenPublication create(final String name) {
 
-        return publication;
-    }
+            Module module = dependencyMetaDataProvider.getModule();
+            MavenProjectIdentity projectIdentity = new DefaultMavenProjectIdentity(module.getGroup(), module.getName(), module.getVersion());
+            NotationParser<MavenArtifact> artifactNotationParser = new MavenArtifactNotationParserFactory(instantiator, fileResolver).create();
 
+            return instantiator.newInstance(
+                    DefaultMavenPublication.class,
+                    name, projectIdentity, artifactNotationParser, instantiator
+            );
+        }
+    }
 }
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/GenerateMavenPom.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/GenerateMavenPom.java
new file mode 100644
index 0000000..280fc47
--- /dev/null
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/GenerateMavenPom.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.tasks;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.publish.maven.MavenPom;
+import org.gradle.api.publish.maven.internal.tasks.MavenPomFileGenerator;
+import org.gradle.api.publish.maven.internal.publication.MavenPomInternal;
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.Set;
+
+/**
+ * Generates an Ivy XML Module Descriptor file.
+ *
+ * @since 1.4
+ */
+ at Incubating
+public class GenerateMavenPom extends DefaultTask {
+
+    private final FileResolver fileResolver;
+    private MavenPom pom;
+    private Object destination;
+
+    @Inject
+    public GenerateMavenPom(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+
+        // Never up to date; we don't understand the data structures.
+        getOutputs().upToDateWhen(Specs.satisfyNone());
+    }
+
+    /**
+     * The Maven pom.
+     *
+     * @return The Maven pom.
+     */
+    public MavenPom getPom() {
+        return pom;
+    }
+
+    public void setPom(MavenPom pom) {
+        this.pom = pom;
+    }
+
+    /**
+     * The file the pom will be written to.
+     *
+     * @return The file the pom will be written to
+     */
+    @OutputFile
+    public File getDestination() {
+        return destination == null ? null : fileResolver.resolve(destination);
+    }
+
+    /**
+     * Sets the destination the descriptor will be written to.
+     *
+     * The value is resolved with {@link org.gradle.api.Project#file(Object)}
+     *
+     * @param destination The file the descriptor will be written to.
+     */
+    public void setDestination(Object destination) {
+        this.destination = destination;
+    }
+
+    @TaskAction
+    public void doGenerate() {
+        MavenPomInternal pomInternal = (MavenPomInternal) getPom();
+
+        MavenPomFileGenerator pomGenerator = new MavenPomFileGenerator();
+        copyIdentity(pomInternal, pomGenerator);
+        copyDependencies(pomInternal.getRuntimeDependencies(), pomGenerator);
+        pomGenerator.withXml(pomInternal.getXmlAction());
+
+        pomGenerator.writeTo(getDestination());
+    }
+
+    private void copyIdentity(MavenPomInternal pomInternal, MavenPomFileGenerator pom) {
+        MavenProjectIdentity projectIdentity = pomInternal.getProjectIdentity();
+        pom.setArtifactId(projectIdentity.getArtifactId());
+        pom.setGroupId(projectIdentity.getGroupId());
+        pom.setVersion(projectIdentity.getVersion());
+        pom.setPackaging(pomInternal.getPackaging());
+    }
+
+    private void copyDependencies(Set<Dependency> runtimeDependencies, MavenPomFileGenerator pom) {
+        for (Dependency runtimeDependency : runtimeDependencies) {
+            pom.addRuntimeDependency(runtimeDependency);
+        }
+    }
+}
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java
index ac83b32..a5d2b35 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenLocal.java
@@ -19,10 +19,8 @@ package org.gradle.api.publish.maven.tasks;
 import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.ArtifactRepositoryContainer;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
-import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
 import org.gradle.api.internal.artifacts.BaseRepositoryFactory;
 import org.gradle.api.internal.artifacts.DependencyResolutionServices;
-import org.gradle.api.internal.file.FileResolver;
 import org.gradle.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
 
@@ -38,9 +36,8 @@ public class PublishToMavenLocal extends PublishToMavenRepository {
     private final BaseRepositoryFactory baseRepositoryFactory;
 
     @Inject
-    public PublishToMavenLocal(ArtifactPublicationServices publicationServices, Factory<LoggingManagerInternal> loggingManagerFactory,
-                               FileResolver fileResolver, DependencyResolutionServices dependencyResolutionServices) {
-        super(publicationServices, loggingManagerFactory, fileResolver);
+    public PublishToMavenLocal(Factory<LoggingManagerInternal> loggingManagerFactory, DependencyResolutionServices dependencyResolutionServices) {
+        super(loggingManagerFactory);
         this.baseRepositoryFactory = dependencyResolutionServices.getBaseRepositoryFactory();
     }
 
diff --git a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java
index b9465f1..62a5166 100644
--- a/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java
+++ b/subprojects/maven/src/main/groovy/org/gradle/api/publish/maven/tasks/PublishToMavenRepository.java
@@ -19,24 +19,20 @@ package org.gradle.api.publish.maven.tasks;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.Incubating;
 import org.gradle.api.InvalidUserDataException;
-import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
 import org.gradle.api.file.FileCollection;
-import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
-import org.gradle.api.internal.file.FileResolver;
-import org.gradle.api.publication.maven.internal.*;
-import org.gradle.api.publication.maven.internal.ant.NoInstallDeployTaskFactory;
 import org.gradle.api.publish.internal.PublishOperation;
 import org.gradle.api.publish.maven.MavenPublication;
-import org.gradle.api.publish.maven.internal.MavenNormalizedPublication;
-import org.gradle.api.publish.maven.internal.MavenPublicationInternal;
-import org.gradle.api.publish.maven.internal.MavenPublisher;
+import org.gradle.api.publish.maven.internal.publication.MavenPublicationInternal;
+import org.gradle.api.publish.maven.internal.publisher.AntTaskBackedMavenPublisher;
+import org.gradle.api.publish.maven.internal.publisher.MavenPublisher;
+import org.gradle.api.publish.maven.internal.publisher.StaticLockingMavenPublisher;
+import org.gradle.api.publish.maven.internal.publisher.ValidatingMavenPublisher;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.internal.Factory;
 import org.gradle.logging.LoggingManagerInternal;
 
 import javax.inject.Inject;
-import java.io.File;
 import java.util.concurrent.Callable;
 
 /**
@@ -51,14 +47,10 @@ public class PublishToMavenRepository extends DefaultTask {
     private MavenArtifactRepository repository;
 
     private final Factory<LoggingManagerInternal> loggingManagerFactory;
-    private final FileResolver fileResolver;
-    private final ArtifactPublicationServices publicationServices;
 
     @Inject
-    public PublishToMavenRepository(ArtifactPublicationServices publicationServices, Factory<LoggingManagerInternal> loggingManagerFactory, FileResolver fileResolver) {
+    public PublishToMavenRepository(Factory<LoggingManagerInternal> loggingManagerFactory) {
         this.loggingManagerFactory = loggingManagerFactory;
-        this.fileResolver = fileResolver;
-        this.publicationServices = publicationServices;
 
 
         // Allow the publication to participate in incremental build
@@ -151,32 +143,12 @@ public class PublishToMavenRepository extends DefaultTask {
         new PublishOperation(publication, repository) {
             @Override
             protected void publish() throws Exception {
-                Factory<Configuration> configurationFactory = new Factory<Configuration>() {
-                    public Configuration create() {
-                        return getProject().getConfigurations().detachedConfiguration();
-                    }
-                };
-                MavenPublisher publisher = new MavenPublisher(createDeployerFactory(), configurationFactory, publicationServices.createArtifactPublisher());
-                MavenNormalizedPublication normalizedPublication = publication.asNormalisedPublication();
-                publisher.publish(normalizedPublication, repository);
+                // TODO:DAZ inject this
+                MavenPublisher antBackedPublisher = new AntTaskBackedMavenPublisher(loggingManagerFactory, getTemporaryDirFactory());
+                MavenPublisher staticLockingPublisher = new StaticLockingMavenPublisher(antBackedPublisher);
+                MavenPublisher validatingPublisher = new ValidatingMavenPublisher(staticLockingPublisher);
+                validatingPublisher.publish(publication.asNormalisedPublication(), repository);
             }
         }.run();
     }
-
-    private DeployerFactory createDeployerFactory() {
-        return new CustomTaskFactoryDeployerFactory(
-                new DefaultMavenFactory(),
-                loggingManagerFactory,
-                fileResolver,
-                new MavenPomMetaInfoProvider() {
-                    public File getMavenPomDir() {
-                        return publication.getPomDir();
-                    }
-                },
-                getProject().getConfigurations(), // these won't actually be used, but it's the easiest way to get a ConfigurationContainer.
-                new DefaultConf2ScopeMappingContainer(),
-                new NoInstallDeployTaskFactory(getTemporaryDirFactory())
-        );
-    }
-
 }
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriterTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriterTest.groovy
new file mode 100644
index 0000000..3af2c89
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/plugins/maven/internal/MavenProjectXmlWriterTest.groovy
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins.maven.internal
+
+import spock.lang.Specification
+
+/**
+ * by Szczepan Faber, created at: 1/21/13
+ */
+class MavenProjectXmlWriterTest extends Specification {
+
+    def writer = new MavenProjectXmlWriter()
+
+    def "removes xml element"() {
+        expect:
+        writer.prepareXml('<?xml encoding="UTF-8"?><project/>') == "<project/>"
+        writer.prepareXml('<?xml version="1.0" encoding="UTF-8"?><project/>') == "<project/>"
+        writer.prepareXml('<?xml  version="1.0"  encoding="UTF-8"  ?><project/>') == "<project/>"
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy
index 33570c3..d3701ec 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publication/maven/internal/ant/ProjectDependencyArtifactIdExtractorHackTest.groovy
@@ -27,7 +27,7 @@ import spock.lang.Issue
 @Issue("GRADLE-443")
 class ProjectDependencyArtifactIdExtractorHackTest extends Specification {
     def project = HelperUtil.createRootProject()
-    def extractor = new ProjectDependencyArtifactIdExtractorHack(new DefaultProjectDependency(project, null))
+    def extractor = new ProjectDependencyArtifactIdExtractorHack(new DefaultProjectDependency(project, null, true))
 
     def "artifact ID defaults to project name if neither archivesBaseName nor mavenDeployer.pom.artifactId is configured"() {
         expect:
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentityTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentityTest.groovy
deleted file mode 100644
index 73eb492..0000000
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/ModuleBackedMavenProjectIdentityTest.groovy
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.maven.internal
-
-import org.gradle.api.artifacts.Module
-import spock.lang.Specification
-
-class ModuleBackedMavenProjectIdentityTest extends Specification {
-
-    def "attributes"() {
-        when:
-        def module = new MutableModule(group: "group", name: "name", version: "version", status: "status")
-        def identity = identity(module)
-
-        then:
-        identity.artifactId == "name"
-        identity.groupId == "group"
-        identity.version == "version"
-        identity.packaging == null
-
-        when:
-        module.name = "changed-name"
-        module.group = "changed-group"
-        module.version = "changed-version"
-
-        then:
-        identity.artifactId == "changed-name"
-        identity.groupId == "changed-group"
-        identity.version == "changed-version"
-        identity.packaging == null
-    }
-
-    MavenProjectIdentity identity(Module module) {
-        new ModuleBackedMavenProjectIdentity(module)
-    }
-
-    private static class MutableModule implements Module {
-        String name
-        String group
-        String version
-        String status
-    }
-}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactoryTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactoryTest.groovy
new file mode 100644
index 0000000..1161a7e
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/artifact/MavenArtifactNotationParserFactoryTest.groovy
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.api.publish.maven.internal.artifact
+import org.gradle.api.Task
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.publish.maven.MavenArtifact
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.internal.reflect.Instantiator
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+public class MavenArtifactNotationParserFactoryTest extends Specification {
+    Instantiator instantiator = new DirectInstantiator()
+    def taskDependency = Mock(TaskDependency)
+    def fileNotationParser = Mock(NotationParser)
+    def publishArtifact = Stub(PublishArtifact) {
+        getExtension() >> 'extension'
+        getClassifier() >> 'classifier'
+        getFile() >> new File('foo')
+        getBuildDependencies() >> taskDependency
+    }
+    def task = Mock(Task)
+    def dependencies = Collections.singleton(Mock(Task))
+
+    NotationParser<MavenArtifact> parser
+
+    def "setup"() {
+        def fileResolver = Stub(FileResolver) {
+            asNotationParser() >> fileNotationParser
+        }
+        parser = new MavenArtifactNotationParserFactory(instantiator, fileResolver).create()
+    }
+
+    def "directly returns MavenArtifact input"() {
+        when:
+        MavenArtifact mavenArtifact = Mock()
+        def output = parser.parseNotation(mavenArtifact)
+
+        then:
+        output == mavenArtifact
+    }
+
+    def "creates MavenArtifact for PublishArtifact"() {
+        when:
+        taskDependency.getDependencies(task) >> dependencies
+        def mavenArtifact = parser.parseNotation(publishArtifact)
+
+        then:
+        mavenArtifact.extension == publishArtifact.extension
+        mavenArtifact.classifier == publishArtifact.classifier
+        mavenArtifact.file == publishArtifact.file
+
+        and:
+        mavenArtifact.buildDependencies.getDependencies(task) == dependencies
+    }
+
+    def "creates MavenArtifact for source map notation"() {
+        when:
+        taskDependency.getDependencies(task) >> dependencies
+        MavenArtifact mavenArtifact = parser.parseNotation(source: publishArtifact)
+
+        then:
+        mavenArtifact.extension == publishArtifact.extension
+        mavenArtifact.classifier == publishArtifact.classifier
+        mavenArtifact.file == publishArtifact.file
+
+        and:
+        mavenArtifact.buildDependencies.getDependencies(task) == dependencies
+    }
+
+    def "creates and configures MavenArtifact for source map notation"() {
+        when:
+        taskDependency.getDependencies(task) >> dependencies
+        MavenArtifact mavenArtifact = parser.parseNotation(source: publishArtifact, extension: "ext", classifier: "classy")
+
+        then:
+        mavenArtifact.file == publishArtifact.file
+        mavenArtifact.extension == "ext"
+        mavenArtifact.classifier == "classy"
+
+        and:
+        mavenArtifact.buildDependencies.getDependencies(task) == dependencies
+    }
+
+    def "creates MavenArtifact for source map notation with file"() {
+        given:
+        File file = new File('some-file-1.2-classifier.zip')
+
+        when:
+        MavenArtifact mavenArtifact = parser.parseNotation(source: 'some-file')
+
+        then:
+        fileNotationParser.parseNotation('some-file') >> file
+
+        and:
+        mavenArtifact.extension == "zip"
+        mavenArtifact.file == file
+        mavenArtifact.classifier == null
+    }
+
+    def "creates MavenArtifact for ArchivePublishArtifact"() {
+        when:
+        def rootProject = HelperUtil.createRootProject()
+        def archive = rootProject.task(type: Jar, {})
+        archive.setBaseName("baseName")
+        archive.setExtension(archiveExtension)
+        archive.setClassifier(archiveClassifier)
+
+        MavenArtifact mavenArtifact = parser.parseNotation(archive)
+
+        then:
+        mavenArtifact.extension == artifactExtension
+        mavenArtifact.classifier == artifactClassifier
+        mavenArtifact.file == archive.archivePath
+        mavenArtifact.buildDependencies.getDependencies(null) == [archive] as Set
+
+        where:
+        archiveClassifier | artifactClassifier | archiveExtension | artifactExtension
+        "classifier"      | "classifier"       | "extension"      | "extension"
+        null              | null               | null             | null
+        ""                | null               | ""               | ""
+    }
+
+    def "creates MavenArtifact for file notation"() {
+        when:
+        File file = new File(fileName)
+        fileNotationParser.parseNotation('some-file') >> file
+
+        and:
+        MavenArtifact mavenArtifact = parser.parseNotation('some-file')
+
+        then:
+        mavenArtifact.extension == extension
+        mavenArtifact.file == file
+        mavenArtifact.classifier == null
+
+        where:
+        fileName                       | extension
+        "some-file"                    | ""
+        "some-file.zip"                | "zip"
+        "some-file-1.2-classifier.zip" | "zip"
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy
new file mode 100644
index 0000000..06a9c32
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.maven.internal.publication
+import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Task
+import org.gradle.api.artifacts.DependencySet
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.artifacts.PublishArtifactSet
+import org.gradle.api.internal.component.SoftwareComponentInternal
+import org.gradle.api.internal.component.Usage
+import org.gradle.api.internal.file.collections.SimpleFileCollection
+import org.gradle.api.internal.notations.api.NotationParser
+import org.gradle.api.publish.maven.MavenArtifact
+import org.gradle.api.publish.maven.internal.publisher.MavenProjectIdentity
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.internal.reflect.DirectInstantiator
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import spock.lang.Shared
+import spock.lang.Specification
+
+public class DefaultMavenPublicationTest extends Specification {
+    @Shared TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+    def module = Mock(MavenProjectIdentity)
+    NotationParser<MavenArtifact> notationParser = Mock(NotationParser)
+    TestFile pomDir
+    TestFile pomFile
+    File artifactFile
+
+    def "setup"() {
+        pomDir = testDirectoryProvider.testDirectory
+        pomFile = pomDir.createFile("pom-file")
+        artifactFile = pomDir.createFile("artifact-file")
+        artifactFile << "some content"
+    }
+
+    def "name and identity properties are passed through"() {
+        when:
+        module.artifactId >> "name"
+        module.groupId >> "group"
+        module.version >> "version"
+
+        and:
+        def publication = createPublication()
+
+        then:
+        publication.name == "pub-name"
+        publication.mavenProjectIdentity == module
+    }
+
+    def "packaging is taken from first added artifact without extension"() {
+        when:
+        def mavenArtifact = Mock(MavenArtifact)
+        notationParser.parseNotation("artifact") >> mavenArtifact
+        mavenArtifact.extension >> "ext"
+
+        and:
+        def publication = createPublication()
+        publication.artifact "artifact"
+
+        then:
+        publication.pom.packaging == "ext"
+    }
+
+    def "empty publishableFiles and artifacts when no component is added"() {
+        when:
+        def publication = createPublication()
+
+        then:
+        publication.publishableFiles.files == [pomFile] as Set
+        publication.artifacts.empty
+        publication.runtimeDependencies.empty
+    }
+
+    def "artifacts and dependencies are taken from added component"() {
+        given:
+        def publication = createPublication()
+        def component = Mock(SoftwareComponentInternal)
+        def usage = Mock(Usage)
+        def artifact = Mock(PublishArtifact)
+        def dependency = Mock(ModuleDependency)
+        def publishArtifactDependencies = Mock(TaskDependency)
+
+        def mavenArtifact = Mock(MavenArtifact)
+
+        when:
+        component.usages >> [usage]
+        usage.artifacts >> [artifact]
+        usage.dependencies >> [dependency]
+
+        notationParser.parseNotation(artifact) >> mavenArtifact
+        mavenArtifact.file >> artifactFile
+
+        and:
+        publication.from(component)
+
+        then:
+        publication.publishableFiles.files == [pomFile, artifactFile] as Set
+        publication.artifacts == [mavenArtifact] as Set
+        publication.runtimeDependencies == [dependency] as Set
+
+        when:
+        def task = Mock(Task)
+        mavenArtifact.buildDependencies >> publishArtifactDependencies
+        publishArtifactDependencies.getDependencies(task) >> [task]
+
+        then:
+        publication.publishableFiles.buildDependencies.getDependencies(task) == [task] as Set
+    }
+
+    def "cannot add multiple components"() {
+        given:
+        def publication = createPublication()
+        def component = Mock(SoftwareComponentInternal)
+        def usage = Mock(Usage)
+        def publishArtifactSet = Mock(PublishArtifactSet)
+        def dependencySet = Mock(DependencySet)
+
+        when:
+        publication.from(component)
+
+        then:
+        component.usages >> [usage]
+        usage.artifacts >> publishArtifactSet
+        publishArtifactSet.iterator() >> [].iterator()
+        usage.dependencies >> dependencySet
+        dependencySet.iterator() >> [].iterator()
+
+        when:
+        publication.from(Mock(SoftwareComponentInternal))
+
+        then:
+        def e = thrown(InvalidUserDataException)
+        e.message == "Maven publication 'pub-name' cannot include multiple components"
+    }
+
+    def "attaches artifacts parsed by notation parser"() {
+        given:
+        def publication = createPublication()
+        Object notation = new Object();
+        def mavenArtifact = Mock(MavenArtifact)
+
+        when:
+        notationParser.parseNotation(notation) >> mavenArtifact
+        mavenArtifact.file >> artifactFile
+
+        and:
+        publication.artifact notation
+
+        then:
+        publication.artifacts == [mavenArtifact] as Set
+        publication.publishableFiles.files == [pomFile, artifactFile] as Set
+    }
+
+    def "attaches and configures artifacts parsed by notation parser"() {
+        given:
+        def publication = createPublication()
+        Object notation = new Object();
+        def mavenArtifact = Mock(MavenArtifact)
+
+        when:
+        notationParser.parseNotation(notation) >> mavenArtifact
+        mavenArtifact.file >> artifactFile
+        mavenArtifact.classifier >> null
+        1 * mavenArtifact.setExtension('changed')
+        _ * mavenArtifact.getExtension() >> 'changed'
+        0 * mavenArtifact._
+
+        and:
+        publication.artifact(notation, new Action<MavenArtifact>() {
+            void execute(MavenArtifact t) {
+                t.extension = 'changed'
+            }
+        })
+
+        then:
+        publication.artifacts == [mavenArtifact] as Set
+        publication.publishableFiles.files == [pomFile, artifactFile] as Set
+    }
+
+    def "can use setter to replace existing artifacts set"() {
+        given:
+        def publication = createPublication()
+        def mavenArtifact1 = Mock(MavenArtifact)
+        def mavenArtifact2 = Mock(MavenArtifact)
+
+        when:
+        publication.artifact "notation"
+
+        then:
+        notationParser.parseNotation("notation") >> Mock(MavenArtifact)
+
+        when:
+        publication.artifacts = ["notation1", "notation2"]
+
+        then:
+        notationParser.parseNotation("notation1") >> mavenArtifact1
+        notationParser.parseNotation("notation2") >> mavenArtifact2
+
+        and:
+        publication.artifacts.size() == 2
+        publication.artifacts == [mavenArtifact1, mavenArtifact2] as Set
+    }
+
+    def createPublication() {
+        def publication = new DefaultMavenPublication("pub-name", module, notationParser, new DirectInstantiator())
+        publication.setPomFile(new SimpleFileCollection(pomFile))
+        return publication;
+    }
+
+    def createArtifact() {
+        def artifact = Mock(MavenArtifact) {
+            getFile() >> artifactFile
+        }
+        return artifact
+    }
+}
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
new file mode 100644
index 0000000..9d3831a
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publisher/ValidatingMavenPublisherTest.groovy
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.publisher
+import org.gradle.api.Action
+import org.gradle.api.XmlProvider
+import org.gradle.api.artifacts.repositories.MavenArtifactRepository
+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.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import static java.util.Collections.emptySet
+import static org.gradle.util.CollectionUtils.toSet
+
+public class ValidatingMavenPublisherTest extends Specification {
+    @Shared TestNameTestDirectoryProvider testDir = new TestNameTestDirectoryProvider()
+    def delegate = Mock(MavenPublisher)
+    def publisher = new ValidatingMavenPublisher(delegate)
+
+    def "delegates when publication is valid"() {
+        when:
+        def projectIdentity = projectIdentity("the-group", "the-artifact", "the-version")
+        def publication = new MavenNormalizedPublication("pub-name", createPomFile("the-group", "the-artifact", "the-version"), projectIdentity, emptySet())
+        def repository = Mock(MavenArtifactRepository)
+
+        and:
+        publisher.publish(publication, repository)
+
+        then:
+        delegate.publish(publication, repository)
+    }
+
+    def "validates project coordinates"() {
+        given:
+        def projectIdentity = projectIdentity(groupId, artifactId, version)
+        def publication = new MavenNormalizedPublication("pub-name", createPomFile(groupId, artifactId, version), projectIdentity, emptySet())
+
+        def repository = Mock(MavenArtifactRepository)
+
+        when:
+        publisher.publish(publication, repository)
+
+        then:
+        def e = thrown InvalidMavenPublicationException
+        e.message == "Invalid publication 'pub-name': $message."
+
+        where:
+        groupId             | artifactId             | version     | message
+        ""                  | "artifact"             | "version"   | "groupId cannot be empty"
+        "group"             | ""                     | "version"   | "artifactId cannot be empty"
+        "group"             | "artifact"             | ""          | "version cannot be empty"
+        "group with spaces" | "artifact"             | "version"   | "groupId is not a valid Maven identifier ([A-Za-z0-9_\\-.]+)"
+        "group-₦ガき∆"        | "artifact"            | "version"   | "groupId is not a valid Maven identifier ([A-Za-z0-9_\\-.]+)"
+        "group"             | "artifact with spaces" | "version"   | "artifactId is not a valid Maven identifier ([A-Za-z0-9_\\-.]+)"
+        "group"             | "artifact-₦ガき∆"       | "version"   | "artifactId is not a valid Maven identifier ([A-Za-z0-9_\\-.]+)"
+        "group"             | "artifact"             | "vers/ion"  | "version cannot contain '/'"
+        "group"             | "artifact"             | "vers\\ion"  | "version cannot contain '\\'"
+        "group"             | "artifact"             | "version\t" | "version cannot contain ISO control character '\\u0009'"
+    }
+
+    def "project coordinates must match POM file"() {
+        given:
+        def projectIdentity = projectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile(groupId, artifactId, version)
+        def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, emptySet())
+
+        def repository = Mock(MavenArtifactRepository)
+
+        when:
+        publisher.publish(publication, repository)
+
+        then:
+        def e = thrown InvalidMavenPublicationException
+        e.message == "Invalid publication 'pub-name': $message"
+
+        where:
+        groupId     | artifactId     | version       | message
+        "group-mod" | "artifact"     | "version"     | "supplied groupId does not match POM file (cannot edit groupId directly in the POM file)."
+        "group"     | "artifact-mod" | "version"     | "supplied artifactId does not match POM file (cannot edit artifactId directly in the POM file)."
+        "group"     | "artifact"     | "version-mod" | "supplied version does not match POM file (cannot edit version directly in the POM file)."
+    }
+
+    def "validates artifact attributes"() {
+        def projectIdentity = projectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile("group", "artifact", "version")
+        def mavenArtifact = Stub(MavenArtifact) {
+            getExtension() >> extension
+            getClassifier() >> classifier
+        }
+        def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, toSet([mavenArtifact]))
+
+        when:
+        publisher.publish(publication, Mock(MavenArtifactRepository))
+
+        then:
+        def t = thrown InvalidMavenPublicationException
+        t.message == "Invalid publication 'pub-name': artifact ${message}."
+
+        where:
+        extension | classifier     | message
+        null      | "classifier"   | "extension cannot be null"
+        "ext"     | ""             | "classifier cannot be an empty string. Use null instead"
+        "ex\r"    | "classifier"   | "extension cannot contain ISO control character '\\u000d'"
+        "ex/"     | "classifier"   | "extension cannot contain '/'"
+        "ext"     | "classi\u0090fier" | "classifier cannot contain ISO control character '\\u0090'"
+        "ext"     | "class\\ifier" | "classifier cannot contain '\\'"
+    }
+
+    @Unroll
+    def "cannot publish with file that #message"() {
+        def projectIdentity = projectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile("group", "artifact", "version")
+        def mavenArtifact = Mock(MavenArtifact)
+        def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, toSet([mavenArtifact]))
+
+        when:
+        publisher.publish(publication, Mock(MavenArtifactRepository))
+
+        then:
+        mavenArtifact.extension >> "ext"
+        mavenArtifact.file >> theFile
+
+        and:
+        def t = thrown InvalidMavenPublicationException
+        t.message == "Invalid publication 'pub-name': artifact file ${message}: '${theFile}'"
+
+        where:
+        theFile                                                         | message
+        new File(testDir.testDirectory, 'does-not-exist') | 'does not exist'
+        testDir.testDirectory.createDir('sub_directory')  | 'is a directory'
+    }
+
+    def "cannot publish with duplicate artifacts"() {
+        given:
+        MavenArtifact artifact1 = Stub() {
+            getExtension() >> "ext1"
+            getClassifier() >> "classified"
+            getFile() >> testDir.createFile('artifact1')
+        }
+        MavenArtifact artifact2 = Stub() {
+            getExtension() >> "ext1"
+            getClassifier() >> "classified"
+            getFile() >> testDir.createFile('artifact2')
+        }
+        def projectIdentity = projectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile("group", "artifact", "version")
+        def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, toSet([artifact1, artifact2]))
+
+        when:
+        publisher.publish(publication, Mock(MavenArtifactRepository))
+
+        then:
+        def t = thrown InvalidMavenPublicationException
+        t.message == "Invalid publication 'pub-name': multiple artifacts with the identical extension and classifier ('ext1', 'classified')."
+    }
+
+    def "cannot publish extra artifact with same attributes as POM"() {
+        given:
+        MavenArtifact artifact1 = Stub() {
+            getExtension() >> "pom"
+            getClassifier() >> null
+            getFile() >> testDir.createFile('artifact1')
+        }
+        def projectIdentity = projectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile("group", "artifact", "version")
+        def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, toSet([artifact1]))
+
+        when:
+        publisher.publish(publication, Mock(MavenArtifactRepository))
+
+        then:
+        def t = thrown InvalidMavenPublicationException
+        t.message == "Invalid publication 'pub-name': multiple artifacts with the identical extension and classifier ('pom', 'null')."
+    }
+
+    def "supplied POM file must be valid"() {
+        given:
+        def projectIdentity = projectIdentity("group", "artifact", "version")
+        def pomFile = createPomFile("group", "artifact", "version", new Action<XmlProvider>() {
+            void execute(XmlProvider xml) {
+                xml.asNode().appendNode("invalid", "This is not a valid pomFile element")
+            }
+        })
+        def publication = new MavenNormalizedPublication("pub-name", pomFile, projectIdentity, emptySet())
+
+        def repository = Mock(MavenArtifactRepository)
+
+        when:
+        publisher.publish(publication, repository)
+
+        then:
+        def e = thrown InvalidMavenPublicationException
+        e.message == "Invalid publication 'pub-name': POM file is invalid. Check any modifications you have made to the POM file."
+        e.cause instanceof XmlPullParserException
+        e.cause.message =~ "Unrecognised tag: 'invalid' .*"
+    }
+
+    private def projectIdentity(def groupId, def artifactId, def version) {
+        return Stub(MavenProjectIdentity) {
+            getGroupId() >> groupId
+            getArtifactId() >> artifactId
+            getVersion() >> version
+        }
+    }
+
+    private def createPomFile(def groupId, def artifactId, def version, Action<XmlProvider> withXmlAction = null) {
+        def pomFile = testDir.file("pom")
+        MavenPomFileGenerator pomFileGenerator = new MavenPomFileGenerator();
+        pomFileGenerator.groupId = groupId
+        pomFileGenerator.artifactId = artifactId
+        pomFileGenerator.version = version
+        if (withXmlAction != null) {
+            pomFileGenerator.withXml(withXmlAction)
+        }
+        pomFileGenerator.writeTo(pomFile)
+        return pomFile
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGeneratorTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGeneratorTest.groovy
new file mode 100644
index 0000000..4f9de23
--- /dev/null
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/tasks/MavenPomFileGeneratorTest.groovy
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.maven.internal.tasks
+
+import org.gradle.api.Action
+import org.gradle.api.Project
+import org.gradle.api.XmlProvider
+import org.gradle.api.artifacts.DependencyArtifact
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.artifacts.ProjectDependency
+import org.gradle.test.fixtures.file.TestDirectoryProvider
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.CollectionUtils
+import org.gradle.util.TextUtil
+import spock.lang.Specification
+
+class MavenPomFileGeneratorTest extends Specification {
+    TestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider()
+    MavenPomFileGenerator generator = new MavenPomFileGenerator()
+
+    def "writes correct prologue and schema declarations"() {
+        expect:
+        pomFile.text.startsWith(TextUtil.toPlatformLineSeparators(
+"""<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+"""))
+    }
+
+    def "writes empty pom with default values"() {
+        expect:
+        with (pom) {
+            modelVersion == "4.0.0"
+            groupId == "unknown"
+            artifactId == "empty-project"
+            version == "0"
+            dependencies.empty
+        }
+    }
+
+    def "writes configured coordinates"() {
+        when:
+        generator.groupId = "group-id"
+        generator.artifactId = "artifact-id"
+        generator.version = "1.0"
+        generator.packaging = "pom"
+
+        then:
+        with (pom) {
+            groupId == "group-id"
+            artifactId == "artifact-id"
+            version == "1.0"
+            packaging == "pom"
+        }
+    }
+
+    def "encodes coordinates for XML and unicode"() {
+        when:
+        generator.groupId = 'group-ぴ₦ガき∆ç√∫'
+        generator.artifactId = 'artifact-<tag attrib="value"/>-markup'
+        generator.version = 'version-&"'
+
+        then:
+        with (pom) {
+            groupId == 'group-ぴ₦ガき∆ç√∫'
+            artifactId == 'artifact-<tag attrib="value"/>-markup'
+            version == 'version-&"'
+        }
+    }
+
+    def "writes regular dependency"() {
+        def dependency = Mock(ModuleDependency)
+        when:
+        generator.addRuntimeDependency(dependency)
+
+        then:
+        dependency.artifacts >> new HashSet<DependencyArtifact>()
+        dependency.group >> "dep-group"
+        dependency.name >> "dep-name"
+        dependency.version >> "dep-version"
+
+        and:
+        with (pom) {
+            dependencies.dependency.size() == 1
+            with (dependencies[0].dependency[0]) {
+                groupId == "dep-group"
+                artifactId == "dep-name"
+                version == "dep-version"
+                scope == "runtime"
+            }
+        }
+    }
+
+    def "writes project dependency"() {
+        def dependency = Mock(ProjectDependency)
+        when:
+        generator.addRuntimeDependency(dependency)
+
+        then:
+        dependency.artifacts >> new HashSet<DependencyArtifact>()
+        dependency.group >> "dep-group"
+        dependency.version >> "dep-version"
+        dependency.dependencyProject >> Stub(Project) {
+            getName() >> "project-name"
+        }
+
+        and:
+        with (pom) {
+            dependencies.dependency.size() == 1
+            with (dependencies[0].dependency[0]) {
+                groupId == "dep-group"
+                artifactId == "project-name"
+                version == "dep-version"
+                scope == "runtime"
+            }
+        }
+    }
+
+    def "writes dependency with artifacts"() {
+        def dependency = Mock(ModuleDependency)
+        def artifact1 = Mock(DependencyArtifact)
+        def artifact2 = Mock(DependencyArtifact)
+        
+        when:
+        generator.addRuntimeDependency(dependency)
+
+        then:
+        dependency.artifacts >> CollectionUtils.toSet([artifact1, artifact2])
+        dependency.group >> "dep-group"
+        dependency.version >> "dep-version"
+        artifact1.name >> "artifact-1"
+        artifact1.type >> "type-1"
+        artifact1.classifier >> "classifier-1"
+        artifact2.name >> "artifact-2"
+        artifact2.type >> null
+        artifact2.classifier >> null
+
+        and:
+        with (pom) {
+            dependencies.dependency.size() == 2
+            with (dependencies[0].dependency[0]) {
+                groupId == "dep-group"
+                artifactId == "artifact-1"
+                version == "dep-version"
+                type == "type-1"
+                classifier == "classifier-1"
+                scope == "runtime"
+            }
+            with (dependencies[0].dependency[1]) {
+                groupId == "dep-group"
+                artifactId == "artifact-2"
+                version == "dep-version"
+                type.empty
+                classifier.empty
+                scope == "runtime"
+            }
+        }
+    }
+
+    def "applies withXml actions"() {
+        when:
+        generator.withXml(new Action<XmlProvider>() {
+            void execute(XmlProvider t) {
+                t.asNode().groupId[0].value = "new-group"
+            }
+        })
+        generator.withXml(new Action<XmlProvider>() {
+            void execute(XmlProvider t) {
+                t.asNode().appendNode("description", "custom-description-ぴ₦ガき∆ç√∫")
+            }
+        })
+
+        then:
+        with (pom) {
+            groupId == "new-group"
+            description == "custom-description-ぴ₦ガき∆ç√∫"
+        }
+    }
+
+    private def getPom() {
+        return new XmlSlurper().parse(pomFile);
+    }
+
+    private TestFile getPomFile() {
+        def pomFile = testDirectoryProvider.testDirectory.file("pom.xml")
+        generator.writeTo(pomFile)
+        return pomFile
+    }
+}
diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy
index f65d759..46a1c02 100644
--- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy
+++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/plugins/MavenPublishPluginTest.groovy
@@ -16,11 +16,13 @@
 
 package org.gradle.api.publish.maven.plugins
 import org.gradle.api.artifacts.ArtifactRepositoryContainer
+import org.gradle.api.artifacts.PublishArtifactSet
+import org.gradle.api.file.FileCollection
 import org.gradle.api.internal.artifacts.DependencyResolutionServices
-import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.internal.component.SoftwareComponentInternal
 import org.gradle.api.publish.PublishingExtension
-import org.gradle.api.publish.maven.internal.DefaultMavenPublication
-import org.gradle.api.publish.maven.internal.MavenPublicationInternal
+import org.gradle.api.publish.maven.MavenPublication
+import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication
 import org.gradle.api.publish.maven.tasks.PublishToMavenLocal
 import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
 import org.gradle.util.HelperUtil
@@ -30,66 +32,61 @@ class MavenPublishPluginTest extends Specification {
 
     def project = HelperUtil.createRootProject()
     PublishingExtension publishing
+    def componentArtifacts = Mock(FileCollection)
+    def component = Stub(SoftwareComponentInternal)
 
     def setup() {
         project.plugins.apply(MavenPublishPlugin)
         publishing = project.extensions.getByType(PublishingExtension)
+        project.components.add(component)
+
+        PublishArtifactSet artifactSet = Stub() {
+            getFiles() >> componentArtifacts
+        }
+
+        component.name >> "test-component"
+        component.artifacts >> artifactSet
     }
 
-    def "no publication without component"() {
+    def "no publication by default"() {
         expect:
         publishing.publications.empty
     }
 
-    def "default publication with java plugin"() {
+    def "publication can be added"() {
         when:
-        javaPluginApplied()
+        publishing.publications.add("test", MavenPublication)
 
         then:
         publishing.publications.size() == 1
-        publishing.publications.maven instanceof DefaultMavenPublication
+        publishing.publications.test instanceof DefaultMavenPublication
     }
 
-    def "creates publish tasks"() {
+    def "creates publish tasks for publication and repository"() {
         when:
-        javaPluginApplied()
+        publishing.publications.add("test", MavenPublication)
         publishing.repositories { maven { url = "http://foo.com" } }
 
         then:
-        project.tasks["publishMavenPublicationToMavenRepository"] != null
-        project.tasks["publishMavenPublicationToMavenLocal"] != null
-    }
-
-    def "java publication always has jar artifact"() {
-        when:
-        javaPluginApplied()
-
-        then:
-        mainPublication.publishableFiles.singleFile.name ==~ /.*\.jar/;
-    }
-
-    protected MavenPublicationInternal getMainPublication() {
-        publishing.publications.maven
-    }
-
-    def javaPluginApplied() {
-        project.plugins.apply(JavaPlugin)
+        project.tasks["publishTestPublicationToMavenRepository"] != null
+        project.tasks["publishTestPublicationToMavenLocal"] != null
+        project.tasks["generatePomFileForTestPublication"] != null
     }
 
     def "task is created for publishing to mavenLocal"() {
         given:
-        javaPluginApplied()
+        publishing.publications.add("test", MavenPublication)
 
         expect:
         publishLocalTasks.size() == 1
-        publishLocalTasks.first().name == "publishMavenPublicationToMavenLocal"
+        publishLocalTasks.first().name == "publishTestPublicationToMavenLocal"
         publishLocalTasks.first().repository.name == ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME
         publishLocalTasks.first().repository.url == project.getServices().get(DependencyResolutionServices).baseRepositoryFactory.createMavenLocalRepository().url
     }
 
     def "can explicitly add mavenLocal as a publishing repository"() {
         given:
-        javaPluginApplied()
+        publishing.publications.add("test", MavenPublication)
 
         when:
         def mavenLocal = publishing.repositories.mavenLocal()
@@ -104,7 +101,7 @@ class MavenPublishPluginTest extends Specification {
 
     def "tasks are created for compatible publication / repo"() {
         given:
-        javaPluginApplied()
+        publishing.publications.add("test", MavenPublication)
 
         expect:
         publishTasks.size() == 0
@@ -115,7 +112,7 @@ class MavenPublishPluginTest extends Specification {
         then:
         publishTasks.size() == 1
         publishTasks.last().repository.is(repo1)
-        publishTasks.last().name == "publishMavenPublicationToMavenRepository"
+        publishTasks.last().name == "publishTestPublicationToMavenRepository"
 
         when:
         publishing.repositories.ivy {}
@@ -129,7 +126,7 @@ class MavenPublishPluginTest extends Specification {
         then:
         publishTasks.size() == 2
         publishTasks.last().repository.is(repo2)
-        publishTasks.last().name == "publishMavenPublicationToOtherRepository"
+        publishTasks.last().name == "publishTestPublicationToOtherRepository"
     }
 
     List<PublishToMavenLocal> getPublishLocalTasks() {
@@ -142,14 +139,16 @@ class MavenPublishPluginTest extends Specification {
         return allTasks
     }
 
-    def "publication identity is live wrt project properties"() {
-        given:
-        javaPluginApplied()
+    def "publication identity is a snapshot of project properties"() {
+        when:
         project.group = "group"
         project.version = "version"
 
-        expect:
-        with(mainPublication.asNormalisedPublication()) {
+        and:
+        publishing.publications.add("test", MavenPublication)
+
+        then:
+        with(publishing.publications.test.mavenProjectIdentity) {
             groupId == "group"
             version == "version"
         }
@@ -159,23 +158,24 @@ class MavenPublishPluginTest extends Specification {
         project.version = "changed-version"
 
         then:
-        with(mainPublication.asNormalisedPublication()) {
-            groupId == "changed-group"
-            version == "changed-version"
+        with(publishing.publications.test.mavenProjectIdentity) {
+            groupId == "group"
+            version == "version"
         }
     }
 
     def "pom dir moves with build dir"() {
-        given:
-        javaPluginApplied()
+        when:
+        publishing.publications.add("test", MavenPublication)
 
-        expect:
-        mainPublication.pomDir == new File(project.buildDir, "publications/${mainPublication.name}")
+        then:
+        project.tasks["generatePomFileForTestPublication"].destination == new File(project.buildDir, "publications/test/pom-default.xml")
 
         when:
-        project.buildDir = project.file("changed")
+        def newBuildDir = project.file("changed")
+        project.buildDir = newBuildDir
 
         then:
-        mainPublication.pomDir == new File(project.buildDir, "publications/${mainPublication.name}")
+        project.tasks["generatePomFileForTestPublication"].destination == new File(newBuildDir, "publications/test/pom-default.xml")
     }
 }
diff --git a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy b/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
deleted file mode 100644
index 22f385e..0000000
--- a/subprojects/messaging/src/test/groovy/org/gradle/messaging/remote/internal/inet/TcpConnectorConcurrencyTest.groovy
+++ /dev/null
@@ -1,85 +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.messaging.remote.internal.inet
-
-import java.util.concurrent.atomic.AtomicInteger
-import org.gradle.api.Action
-import org.gradle.internal.id.UUIDGenerator
-import org.gradle.messaging.remote.internal.DefaultMessageSerializer
-import org.gradle.util.ConcurrentSpecification
-import org.slf4j.LoggerFactory
-import spock.lang.Ignore
-import spock.lang.Timeout
-import static java.util.Collections.synchronizedList
-
-class TcpConnectorConcurrencyTest extends ConcurrentSpecification {
-
-    final static LOGGER = LoggerFactory.getLogger(TcpConnectorConcurrencyTest)
-
-    //sharing serializer adds extra flavor...
-    final serializer = new DefaultMessageSerializer<Object>(getClass().classLoader)
-    final outgoingConnector = new TcpOutgoingConnector()
-    final incomingConnector = new TcpIncomingConnector(executorFactory, new InetAddressFactory(), new UUIDGenerator())
-
-    @Timeout(60)
-    @Ignore
-    //TODO SF exposes concurrency issue
-    def "can dispatch from multiple threads"() {
-        def number = new AtomicInteger(1)
-        def threads = 20
-        def messages = synchronizedList([])
-
-        Action action = new Action() {
-            void execute(event) {
-                while (true) {
-                    def message = event.connection.receive()
-                    LOGGER.debug("*** received: $message")
-                    messages << message
-                    if (messages.size() == threads || message == null) {
-                        break;
-                    }
-                }
-            }
-        }
-
-        def address = incomingConnector.accept(action, getClass().classLoader, false)
-        def connection = outgoingConnector.connect(address, getClass().classLoader)
-
-        when:
-        def all = []
-        threads.times {
-            all << start {
-                //exceptions carry lots of information so serialization/deserialization is slower
-                //and hence better chance of reproducing the concurrency bugs
-                def message = new RuntimeException("Message #" + number.getAndIncrement())
-                connection.dispatch(message)
-                LOGGER.debug("*** dispatched: $message")
-            }
-        }
-
-        all*.completed()
-
-        then:
-        //let's give some time for the messages to arrive to the sink
-        poll(20) {
-            messages.size() == threads
-            messages.each { it.toString().contains("Message #") }
-        }
-
-        cleanup:
-        incomingConnector.requestStop()
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java
deleted file mode 100644
index 8340530..0000000
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/AbstractProcessEnvironment.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.internal.nativeplatform.jna;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import org.gradle.internal.nativeplatform.NativeIntegrationException;
-import org.gradle.internal.nativeplatform.ProcessEnvironment;
-import org.gradle.internal.nativeplatform.ReflectiveEnvironment;
-
-import java.io.File;
-import java.util.List;
-import java.util.Map;
-
-public abstract class AbstractProcessEnvironment implements ProcessEnvironment {
-    //for updates to private JDK caches of the environment state
-    private final ReflectiveEnvironment reflectiveEnvironment = new ReflectiveEnvironment();
-
-    public boolean maybeSetEnvironment(Map<String, String> source) {
-        // need to take copy to prevent ConcurrentModificationException
-        List<String> keysToRemove = Lists.newArrayList(Sets.difference(System.getenv().keySet(), source.keySet()));
-        for (String key : keysToRemove) {
-            removeEnvironmentVariable(key);
-        }
-        for (Map.Entry<String, String> entry : source.entrySet()) {
-            setEnvironmentVariable(entry.getKey(), entry.getValue());
-        }
-        return true;
-    }
-
-    public void removeEnvironmentVariable(String name) throws NativeIntegrationException {
-        removeNativeEnvironmentVariable(name);
-        reflectiveEnvironment.unsetenv(name);
-    }
-
-    protected abstract void removeNativeEnvironmentVariable(String name);
-
-    public boolean maybeRemoveEnvironmentVariable(String name) {
-        removeEnvironmentVariable(name);
-        return true;
-    }
-
-    public void setEnvironmentVariable(String name, String value) throws NativeIntegrationException {
-        if (value == null) {
-            removeEnvironmentVariable(name);
-            return;
-        }
-
-        setNativeEnvironmentVariable(name, value);
-        reflectiveEnvironment.setenv(name, value);
-    }
-
-    protected abstract void setNativeEnvironmentVariable(String name, String value);
-
-    public boolean maybeSetEnvironmentVariable(String name, String value) {
-        setEnvironmentVariable(name, value);
-        return true;
-    }
-
-    public void setProcessDir(File processDir) throws NativeIntegrationException {
-        if (!processDir.exists()) {
-            return;
-        }
-
-        setNativeProcessDir(processDir);
-        System.setProperty("user.dir", processDir.getAbsolutePath());
-    }
-
-    protected abstract void setNativeProcessDir(File processDir);
-
-    public boolean maybeSetProcessDir(File processDir) {
-        setProcessDir(processDir);
-        return true;
-    }
-
-    public Long maybeGetPid() {
-        return getPid();
-    }
-}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java
index 71fdc80..bf3cd41 100644
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibC.java
@@ -23,7 +23,6 @@ import org.jruby.ext.posix.FileStat;
 public interface LibC extends Library {
     //CHECKSTYLE:OFF
     public int setenv(String name, String value, int overwrite) throws LastErrorException;
-    public int unsetenv(String name) throws LastErrorException;
     public String getcwd(byte[] out, int size) throws LastErrorException;
     public int chdir(String dirAbsolutePath) throws LastErrorException;
     public int getpid();
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java
old mode 100644
new mode 100755
index 49ee1ce..95e75a4
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/LibCBackedProcessEnvironment.java
@@ -18,6 +18,7 @@ package org.gradle.internal.nativeplatform.jna;
 import com.sun.jna.LastErrorException;
 import com.sun.jna.Native;
 import org.gradle.internal.nativeplatform.NativeIntegrationException;
+import org.gradle.internal.nativeplatform.processenvironment.AbstractProcessEnvironment;
 
 import java.io.File;
 
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java
index b2f77b8..27b7576 100755
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/jna/WindowsProcessEnvironment.java
@@ -17,6 +17,7 @@ package org.gradle.internal.nativeplatform.jna;
 
 import com.sun.jna.Native;
 import org.gradle.internal.nativeplatform.NativeIntegrationException;
+import org.gradle.internal.nativeplatform.processenvironment.AbstractProcessEnvironment;
 
 import java.io.File;
 
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/processenvironment/AbstractProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/processenvironment/AbstractProcessEnvironment.java
new file mode 100755
index 0000000..078f1a6
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/processenvironment/AbstractProcessEnvironment.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.nativeplatform.processenvironment;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.gradle.internal.nativeplatform.NativeIntegrationException;
+import org.gradle.internal.nativeplatform.ProcessEnvironment;
+import org.gradle.internal.nativeplatform.ReflectiveEnvironment;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+public abstract class AbstractProcessEnvironment implements ProcessEnvironment {
+    //for updates to private JDK caches of the environment state
+    private final ReflectiveEnvironment reflectiveEnvironment = new ReflectiveEnvironment();
+
+    public boolean maybeSetEnvironment(Map<String, String> source) {
+        // need to take copy to prevent ConcurrentModificationException
+        List<String> keysToRemove = Lists.newArrayList(Sets.difference(System.getenv().keySet(), source.keySet()));
+        for (String key : keysToRemove) {
+            removeEnvironmentVariable(key);
+        }
+        for (Map.Entry<String, String> entry : source.entrySet()) {
+            setEnvironmentVariable(entry.getKey(), entry.getValue());
+        }
+        return true;
+    }
+
+    public void removeEnvironmentVariable(String name) throws NativeIntegrationException {
+        removeNativeEnvironmentVariable(name);
+        reflectiveEnvironment.unsetenv(name);
+    }
+
+    protected abstract void removeNativeEnvironmentVariable(String name);
+
+    public boolean maybeRemoveEnvironmentVariable(String name) {
+        removeEnvironmentVariable(name);
+        return true;
+    }
+
+    public void setEnvironmentVariable(String name, String value) throws NativeIntegrationException {
+        if (value == null) {
+            removeEnvironmentVariable(name);
+            return;
+        }
+
+        setNativeEnvironmentVariable(name, value);
+        reflectiveEnvironment.setenv(name, value);
+    }
+
+    protected abstract void setNativeEnvironmentVariable(String name, String value);
+
+    public boolean maybeSetEnvironmentVariable(String name, String value) {
+        setEnvironmentVariable(name, value);
+        return true;
+    }
+
+    public void setProcessDir(File processDir) throws NativeIntegrationException {
+        if (!processDir.exists()) {
+            return;
+        }
+
+        setNativeProcessDir(processDir);
+        System.setProperty("user.dir", processDir.getAbsolutePath());
+    }
+
+    protected abstract void setNativeProcessDir(File processDir);
+
+    public boolean maybeSetProcessDir(File processDir) {
+        setProcessDir(processDir);
+        return true;
+    }
+
+    public Long maybeGetPid() {
+        return getPid();
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/processenvironment/NativePlatformBackedProcessEnvironment.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/processenvironment/NativePlatformBackedProcessEnvironment.java
new file mode 100755
index 0000000..4215dc5
--- /dev/null
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/processenvironment/NativePlatformBackedProcessEnvironment.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.nativeplatform.processenvironment;
+
+import net.rubygrapefruit.platform.Process;
+
+import java.io.File;
+
+public class NativePlatformBackedProcessEnvironment extends AbstractProcessEnvironment {
+    private final Process process;
+
+    public NativePlatformBackedProcessEnvironment(Process process) {
+        this.process = process;
+    }
+
+    @Override
+    protected void removeNativeEnvironmentVariable(String name) {
+        process.setEnvironmentVariable(name, null);
+    }
+
+    @Override
+    protected void setNativeEnvironmentVariable(String name, String value) {
+        process.setEnvironmentVariable(name, value);
+    }
+
+    @Override
+    protected void setNativeProcessDir(File processDir) {
+        process.setWorkingDirectory(processDir);
+    }
+
+    public File getProcessDir() {
+        return process.getWorkingDirectory();
+    }
+
+    public Long getPid() {
+        return Long.valueOf(process.getProcessId());
+    }
+}
diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java
index 4e0c820..5b2f029 100755
--- a/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java
+++ b/subprojects/native/src/main/java/org/gradle/internal/nativeplatform/services/NativeServices.java
@@ -16,9 +16,9 @@
 package org.gradle.internal.nativeplatform.services;
 
 import com.sun.jna.Native;
-import net.rubygrapefruit.platform.NativeException;
+import net.rubygrapefruit.platform.*;
 import net.rubygrapefruit.platform.NativeIntegrationUnavailableException;
-import net.rubygrapefruit.platform.Terminals;
+import net.rubygrapefruit.platform.Process;
 import org.gradle.internal.SystemProperties;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.internal.nativeplatform.*;
@@ -29,6 +29,7 @@ import org.gradle.internal.nativeplatform.console.WindowsConsoleDetector;
 import org.gradle.internal.nativeplatform.filesystem.FileSystem;
 import org.gradle.internal.nativeplatform.filesystem.FileSystems;
 import org.gradle.internal.nativeplatform.jna.*;
+import org.gradle.internal.nativeplatform.processenvironment.NativePlatformBackedProcessEnvironment;
 import org.gradle.internal.os.OperatingSystem;
 import org.gradle.internal.service.DefaultServiceRegistry;
 import org.slf4j.Logger;
@@ -41,22 +42,24 @@ import java.io.File;
  */
 public class NativeServices extends DefaultServiceRegistry {
     private static final Logger LOGGER = LoggerFactory.getLogger(NativeServices.class);
-    private static final boolean USE_NATIVE_PLATFORM = "true".equalsIgnoreCase(System.getProperty("org.gradle.native", "true"));
+    private static boolean useNativePlatform = "true".equalsIgnoreCase(System.getProperty("org.gradle.native", "true"));
     private static final NativeServices INSTANCE = new NativeServices();
 
     /**
      * Initializes the native services to use the given user home directory to store native libs and other resources. Does nothing if already initialized. Will be implicitly initialized on first usage
-     * of a native service. Also initializes the Native-Platform library using the passed user home directory.
+     * of a native service. Also initializes the Native-Platform library using the given user home directory.
      */
     public static void initialize(File userHomeDir) {
         File nativeDir = new File(userHomeDir, "native");
-        if (USE_NATIVE_PLATFORM) {
+        if (useNativePlatform) {
             try {
                 net.rubygrapefruit.platform.Native.init(nativeDir);
             } catch (NativeIntegrationUnavailableException ex) {
                 LOGGER.debug("Native-platform is not available.");
+                useNativePlatform = false;
             } catch (NativeException ex) {
                 LOGGER.debug("Unable to initialize native-platform. Failure: {}", format(ex));
+                useNativePlatform = false;
             }
         }
         new JnaBootPathConfigurer().configure(nativeDir);
@@ -87,14 +90,23 @@ public class NativeServices extends DefaultServiceRegistry {
     }
 
     protected ProcessEnvironment createProcessEnvironment() {
-        ProcessEnvironment environment;
-
         OperatingSystem operatingSystem = get(OperatingSystem.class);
+        if (useNativePlatform) {
+            try {
+                net.rubygrapefruit.platform.Process process = net.rubygrapefruit.platform.Native.get(Process.class);
+                return new NativePlatformBackedProcessEnvironment(process);
+            } catch (NativeIntegrationUnavailableException ex) {
+                LOGGER.debug("Native-platform process integration is not available. Continuing with fallback.");
+            } catch (NativeException ex) {
+                LOGGER.debug("Unable to load from native-platform backed ProcessEnvironment. Continuing with fallback. Failure: {}", format(ex));
+            }
+        }
+
         try {
             if (operatingSystem.isUnix()) {
-                environment = new LibCBackedProcessEnvironment(get(LibC.class));
+                return new LibCBackedProcessEnvironment(get(LibC.class));
             } else if (operatingSystem.isWindows()) {
-                environment = new WindowsProcessEnvironment();
+                return new WindowsProcessEnvironment();
             } else {
                 return new UnsupportedEnvironment();
             }
@@ -103,22 +115,21 @@ public class NativeServices extends DefaultServiceRegistry {
             LOGGER.debug("Unable to load native library. Continuing with fallback. Failure: {}", format(e));
             return new UnsupportedEnvironment();
         }
-
-        return environment;
     }
 
     protected ConsoleDetector createConsoleDetector() {
-        OperatingSystem operatingSystem = get(OperatingSystem.class);
-        if (USE_NATIVE_PLATFORM) {
+        if (useNativePlatform) {
             try {
                 Terminals terminals = net.rubygrapefruit.platform.Native.get(Terminals.class);
                 return new NativePlatformConsoleDetector(terminals);
             } catch (NativeIntegrationUnavailableException ex) {
-                LOGGER.debug("Native-platform terminal is not available. Continuing with fallback.");
+                LOGGER.debug("Native-platform terminal integration is not available. Continuing with fallback.");
             } catch (NativeException ex) {
                 LOGGER.debug("Unable to load from native-platform backed ConsoleDetector. Continuing with fallback. Failure: {}", format(ex));
             }
         }
+
+        OperatingSystem operatingSystem = get(OperatingSystem.class);
         try {
             if (operatingSystem.isWindows()) {
                 return new WindowsConsoleDetector();
@@ -145,4 +156,5 @@ public class NativeServices extends DefaultServiceRegistry {
         }
         return builder.toString();
     }
+
 }
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy
deleted file mode 100644
index 4565199..0000000
--- a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/jna/ProcessEnvironmentTest.groovy
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.gradle.internal.nativeplatform.jna
-
-import org.gradle.internal.nativeplatform.ProcessEnvironment
-import org.gradle.internal.nativeplatform.services.NativeServices
-import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
-import org.gradle.util.Requires
-import org.gradle.util.SetSystemProperties
-import org.gradle.util.TestPrecondition
-import org.junit.Rule
-import spock.lang.Specification
-
-class ProcessEnvironmentTest extends Specification {
-    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
-    @Rule final SetSystemProperties systemProperties = new SetSystemProperties()
-    final ProcessEnvironment env = NativeServices.getInstance().get(ProcessEnvironment)
-
-    @Requires(TestPrecondition.SET_ENV_VARIABLE)
-    def "can set and remove environment variable"() {
-        when:
-        env.setEnvironmentVariable("TEST_ENV_1", "value")
-        env.maybeSetEnvironmentVariable("TEST_ENV_2", "value")
-
-        then:
-        System.getenv("TEST_ENV_1") == "value"
-        System.getenv("TEST_ENV_2") == "value"
-
-        when:
-        env.removeEnvironmentVariable("TEST_ENV_1")
-        env.maybeRemoveEnvironmentVariable("TEST_ENV_2")
-
-        then:
-        System.getenv("TEST_ENV_1") == null
-        System.getenv("TEST_ENV_2") == null
-    }
-
-    @Requires(TestPrecondition.WORKING_DIR)
-    def "can get working directory of current process"() {
-        expect:
-        env.processDir.canonicalFile == new File('.').canonicalFile
-    }
-
-    @Requires(TestPrecondition.WORKING_DIR)
-    def "can get set working directory of current process"() {
-        File originalDir = new File(System.getProperty("user.dir"))
-
-        when:
-        env.setProcessDir(tmpDir.testDirectory)
-
-        then:
-        env.processDir.canonicalFile == tmpDir.testDirectory
-        new File(".").canonicalFile == tmpDir.testDirectory
-
-        cleanup:
-        System.setProperty("user.dir", originalDir.absolutePath)
-        env.setProcessDir(originalDir)
-    }
-
-    @Requires(TestPrecondition.PROCESS_ID)
-    def "can get pid of current process"() {
-        expect:
-        env.pid != null
-        env.maybeGetPid() == env.pid
-    }
-}
diff --git a/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/processenvironment/ProcessEnvironmentTest.groovy b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/processenvironment/ProcessEnvironmentTest.groovy
new file mode 100755
index 0000000..dd816ee
--- /dev/null
+++ b/subprojects/native/src/test/groovy/org/gradle/internal/nativeplatform/processenvironment/ProcessEnvironmentTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.internal.nativeplatform.processenvironment
+
+import org.gradle.internal.nativeplatform.ProcessEnvironment
+import org.gradle.internal.nativeplatform.services.NativeServices
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.Requires
+import org.gradle.util.SetSystemProperties
+import org.gradle.util.TestPrecondition
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProcessEnvironmentTest extends Specification {
+    @Rule final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+    @Rule final SetSystemProperties systemProperties = new SetSystemProperties()
+    final ProcessEnvironment env = NativeServices.getInstance().get(ProcessEnvironment)
+
+    @Requires(TestPrecondition.SET_ENV_VARIABLE)
+    def "can set and remove environment variable"() {
+        when:
+        env.setEnvironmentVariable("TEST_ENV_1", "value")
+        env.maybeSetEnvironmentVariable("TEST_ENV_2", "value")
+
+        then:
+        System.getenv("TEST_ENV_1") == "value"
+        System.getenv("TEST_ENV_2") == "value"
+
+        when:
+        env.removeEnvironmentVariable("TEST_ENV_1")
+        env.maybeRemoveEnvironmentVariable("TEST_ENV_2")
+
+        then:
+        System.getenv("TEST_ENV_1") == null
+        System.getenv("TEST_ENV_2") == null
+    }
+
+    @Requires(TestPrecondition.WORKING_DIR)
+    def "can get working directory of current process"() {
+        expect:
+        env.processDir.canonicalFile == new File('.').canonicalFile
+    }
+
+    @Requires(TestPrecondition.WORKING_DIR)
+    def "can get set working directory of current process"() {
+        File originalDir = new File(System.getProperty("user.dir"))
+
+        when:
+        env.setProcessDir(tmpDir.testDirectory)
+
+        then:
+        env.processDir.canonicalFile == tmpDir.testDirectory
+        new File(".").canonicalFile == tmpDir.testDirectory
+
+        cleanup:
+        System.setProperty("user.dir", originalDir.absolutePath)
+        env.setProcessDir(originalDir)
+    }
+
+    @Requires(TestPrecondition.PROCESS_ID)
+    def "can get pid of current process"() {
+        expect:
+        env.pid != null
+        env.maybeGetPid() == env.pid
+    }
+}
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
index 2bc10f7..4fec1b2 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/CrossVersionCompatibilityIntegrationTest.groovy
@@ -30,7 +30,7 @@ import org.slf4j.LoggerFactory
 @Requires(TestPrecondition.SWING)
 class CrossVersionCompatibilityIntegrationTest extends CrossVersionIntegrationSpec {
     private final Logger logger = LoggerFactory.getLogger(CrossVersionCompatibilityIntegrationTest)
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(temporaryFolder)
 
     public void canUseOpenApiFromCurrentVersionToBuildUsingAnOlderVersion() {
         expect:
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
index 66cc235..a2b9402 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/GradleRunnerTest.groovy
@@ -41,7 +41,7 @@ class GradleRunnerTest {
 
   @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
   final GradleDistribution dist = new UnderDevelopmentGradleDistribution()
-  @Rule public final TestResources resources = new TestResources('testproject')
+  @Rule public final TestResources resources = new TestResources(temporaryFolder, 'testproject')
 
   @Before
   void setUp() {
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
index 16ea49f..8dd1280 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiFixture.java
@@ -21,7 +21,6 @@ import org.gradle.openapi.external.ui.DualPaneUIVersion1;
 import org.gradle.openapi.external.ui.SinglePaneUIVersion1;
 import org.gradle.openapi.external.ui.UIFactory;
 import org.gradle.test.fixtures.file.TestDirectoryProvider;
-import org.gradle.test.fixtures.file.TestDirectoryProviderFinder;
 import org.junit.Assert;
 import org.junit.rules.MethodRule;
 import org.junit.runners.model.FrameworkMethod;
@@ -34,14 +33,17 @@ import java.util.List;
 
 public class OpenApiFixture implements MethodRule {
     private IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
-    private TestDirectoryProvider testDirectoryProvider;
+    private final TestDirectoryProvider testDirectoryProvider;
     private final List<JFrame> frames = new ArrayList<JFrame>();
 
+    public OpenApiFixture(TestDirectoryProvider testDirectoryProvider) {
+        this.testDirectoryProvider = testDirectoryProvider;
+    }
+
     public Statement apply(final Statement base, FrameworkMethod method, final Object target) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                testDirectoryProvider = new TestDirectoryProviderFinder().findFor(target);
                 try {
                     base.evaluate();
                 } finally {
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
index 7bf9f3e..4bce8a3 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OpenApiUiTest.groovy
@@ -52,8 +52,8 @@ class OpenApiUiTest {
 
     @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
     private IntegrationTestBuildContext buildContext = new IntegrationTestBuildContext();
-    @Rule public TestResources resources = new TestResources('testproject')
-    @Rule public OpenApiFixture openApi = new OpenApiFixture()
+    @Rule public TestResources resources = new TestResources(temporaryFolder, 'testproject')
+    @Rule public OpenApiFixture openApi = new OpenApiFixture(temporaryFolder)
     @ClassRule public static PreconditionVerifier verifier = new PreconditionVerifier()
 
     /**
diff --git a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
index 43fb64f..d3c6aeb 100644
--- a/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
+++ b/subprojects/open-api/src/integTest/groovy/org/gradle/integtests/openapi/OutputUILordTest.groovy
@@ -43,8 +43,8 @@ class OutputUILordTest {
 
     @Rule public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider()
 
-    @Rule public OpenApiFixture openApi = new OpenApiFixture()
-    @Rule public TestResources resources = new TestResources('testProject')
+    @Rule public OpenApiFixture openApi = new OpenApiFixture(temporaryFolder)
+    @Rule public TestResources resources = new TestResources(temporaryFolder, 'testProject')
     @ClassRule public static PreconditionVerifier verifier = new PreconditionVerifier()
 
     /**
diff --git a/subprojects/performance/performance.gradle b/subprojects/performance/performance.gradle
index 62ed02b..e954b00 100644
--- a/subprojects/performance/performance.gradle
+++ b/subprojects/performance/performance.gradle
@@ -58,7 +58,7 @@ task withVerboseJUnit(type: ProjectGeneratorTask) {
     subProjectTemplates << 'with-verbose-junit'
 }
 
-task multiGroovy(type: ProjectGeneratorTask, description: 'Generates a multi-project groovy build') {
+task multiGroovy(type: ProjectGeneratorTask, description: 'Generates a multi-project Groovy build') {
     projects = 25
     groovyProject = true
 }
@@ -68,6 +68,12 @@ task multiScala(type: ProjectGeneratorTask, description: 'Generates a multi-proj
     scalaProject = true
 }
 
+task multiGroovyScala(type: ProjectGeneratorTask, description: 'Generates a multi-project Groovy & Scala build') {
+    projects = 25
+    groovyProject = true
+    scalaProject = true
+}
+
 task largeMulti(type: ProjectGeneratorTask, description: 'Generates a large multi-project build') {
     projects = 800
     sourceFiles = 100
@@ -143,8 +149,7 @@ integTestTasks.all {
     if (buildTypes.isActive('performanceTest') || buildTypes.isActive('localPerformanceTest')) {
         dependsOn prepareSamples
     } else {
-        enabled = false
-        dependsOn = []
+        gradle.startParameter.excludedTaskNames << it.path
     }
     maxParallelForks = 1
 }
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy
index b89eb00..6863035 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/CleanBuildPerformanceTest.groovy
@@ -16,8 +16,7 @@
 
 package org.gradle.performance
 
-import org.gradle.performance.fixture.PerformanceTestRunner
-import spock.lang.Specification
+import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
 import static org.gradle.performance.fixture.DataAmount.kbytes
@@ -26,19 +25,19 @@ import static org.gradle.performance.fixture.Duration.millis
 /**
  * by Szczepan Faber, created at: 2/9/12
  */
-class CleanBuildPerformanceTest extends Specification {
-
+class CleanBuildPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' clean build")
     def "clean build"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['clean', 'build'],
-                runs: 5,
-                warmUpRuns: 1,
-                targetVersions: ['1.0', 'last'],
-                maxExecutionTimeRegression: [maxExecutionTimeRegression, maxExecutionTimeRegression],
-                maxMemoryRegression: [kbytes(3000), kbytes(3000)]
-        ).run()
+        given:
+        runner.testProject = testProject
+        runner.tasksToRun = ['clean', 'build']
+        runner.maxExecutionTimeRegression = maxExecutionTimeRegression
+        runner.maxMemoryRegression = kbytes(3000)
+
+        when:
+        def result = runner.run()
+
+        then:
         result.assertCurrentVersionHasNotRegressed()
 
         where:
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy
index 1c9346c..5b8aaff 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyReportPerformanceTest.groovy
@@ -16,8 +16,7 @@
 
 package org.gradle.performance
 
-import org.gradle.performance.fixture.PerformanceTestRunner
-import spock.lang.Specification
+import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
 import static org.gradle.performance.fixture.DataAmount.kbytes
@@ -26,22 +25,23 @@ import static org.gradle.performance.fixture.Duration.millis
 /**
  * by Szczepan Faber, created at: 2/9/12
  */
-class DependencyReportPerformanceTest extends Specification {
+class DependencyReportPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' dependency report")
     def "dependency report"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['dependencyReport'],
-                runs: 5,
-                warmUpRuns: 1,
-                targetVersions: ['1.0', '1.1', '1.2', 'last'],
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: maxMemoryRegression
-        ).run()
+        given:
+        runner.testProject = testProject
+        runner.tasksToRun = ['dependencyReport']
+        runner.maxExecutionTimeRegression = maxExecutionTimeRegression
+        runner.maxMemoryRegression = maxMemoryRegression
+
+        when:
+        def result = runner.run()
+
+        then:
         result.assertCurrentVersionHasNotRegressed()
 
         where:
         testProject       | maxExecutionTimeRegression | maxMemoryRegression
-        "lotDependencies" | [millis(7000), millis(1000), millis(1000), millis(1000)] | [kbytes(6000), kbytes(3000), kbytes(3000), kbytes(3000)]
+        "lotDependencies" | millis(7000)               | kbytes(3000)
     }
 }
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy
index 392e727..ab5f057 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/DependencyResolutionStressTest.groovy
@@ -79,7 +79,7 @@ task check << {
         """
 
                 GradleExecuter executer = distribution.executer(workspace)
-                executer.requireGradleHome(true).requireOwnGradleUserHomeDir()
+                executer.requireGradleHome().requireOwnGradleUserHomeDir()
                 10.times {
                     executer.inDirectory(buildDir).withArgument("--refresh-dependencies").withTasks('check').run()
                 }
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy
index d0a756c..1a8b65f 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/IdeIntegrationPerformanceTest.groovy
@@ -16,8 +16,7 @@
 
 package org.gradle.performance
 
-import org.gradle.performance.fixture.PerformanceTestRunner
-import spock.lang.Specification
+import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
 import static org.gradle.performance.fixture.DataAmount.kbytes
@@ -26,44 +25,46 @@ import static org.gradle.performance.fixture.Duration.millis
 /**
  * by Szczepan Faber, created at: 2/9/12
  */
-class IdeIntegrationPerformanceTest extends Specification {
+class IdeIntegrationPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' eclipse")
     def "eclipse"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['eclipse'],
-                runs: 5,
-                warmUpRuns: 1,
-                targetVersions: ['1.0', '1.1', '1.2', 'last'],
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: [kbytes(3000), kbytes(3000), kbytes(3000), kbytes(3000)]
-        ).run()
+        given:
+        runner.testProject = testProject
+        runner.tasksToRun = ['eclipse']
+        runner.maxExecutionTimeRegression = maxExecutionTimeRegression
+        runner.maxMemoryRegression = kbytes(3000)
+
+        when:
+        def result = runner.run()
+
+        then:
         result.assertCurrentVersionHasNotRegressed()
 
         where:
         testProject       | maxExecutionTimeRegression
-        "small"           | [millis(500), millis(500), millis(500), millis(500)]
-        "multi"           | [millis(1500), millis(1000), millis(1000), millis(1000)]
-        "lotDependencies" | [millis(3000), millis(1000), millis(1000), millis(1000)]
+        "small"           | millis(500)
+        "multi"           | millis(1500)
+        "lotDependencies" | millis(3000)
     }
 
     @Unroll("Project '#testProject' idea")
     def "idea"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['idea'],
-                runs: 5,
-                warmUpRuns: 1,
-                targetVersions: ['1.0', '1.1', '1.2', 'last'],
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: [kbytes(3000), kbytes(3000), kbytes(3000), kbytes(3000)]
-        ).run()
+        given:
+        runner.testProject = testProject
+        runner.tasksToRun = ['idea']
+        runner.maxExecutionTimeRegression = maxExecutionTimeRegression
+        runner.maxMemoryRegression = kbytes(3000)
+
+        when:
+        def result = runner.run()
+
+        then:
         result.assertCurrentVersionHasNotRegressed()
 
         where:
         testProject       | maxExecutionTimeRegression
-        "small"           | [millis(500), millis(500), millis(500), millis(500)]
-        "multi"           | [millis(1500), millis(1000), millis(1000), millis(1000)]
-        "lotDependencies" | [millis(3000), millis(1000), millis(1000), millis(1000)]
+        "small"           | millis(500)
+        "multi"           | millis(1500)
+        "lotDependencies" | millis(3000)
     }
 }
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy
index afb55c9..6676d3a 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/TestExecutionPerformanceTest.groovy
@@ -16,8 +16,7 @@
 
 package org.gradle.performance
 
-import org.gradle.performance.fixture.PerformanceTestRunner
-import spock.lang.Specification
+import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
 import static org.gradle.performance.fixture.DataAmount.kbytes
@@ -26,28 +25,27 @@ import static org.gradle.performance.fixture.Duration.millis
 /**
  * by Szczepan Faber, created at: 2/9/12
  */
-class TestExecutionPerformanceTest extends Specification {
+class TestExecutionPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject'")
     def "test execution"() {
+        given:
+        runner.testProject = testProject
+        runner.tasksToRun = ['cleanTest', 'test']
+        runner.args = ['-q']
+        runner.maxExecutionTimeRegression = maxExecutionTimeRegression
+        runner.maxMemoryRegression = kbytes(3000)
+
         when:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['cleanTest', 'test'],
-                runs: 4,
-                args: ['-q'],
-                warmUpRuns: 1,
-                targetVersions: ['1.0', '1.2', 'last'],
-                maxExecutionTimeRegression: maxExecutionTimeRegression,
-                maxMemoryRegression: [kbytes(500), kbytes(3000), kbytes(3000)]
-        ).run()
+        def result = runner.run()
 
         then:
         result.assertCurrentVersionHasNotRegressed()
 
         where:
         testProject         | maxExecutionTimeRegression
-        "withTestNG"        | [millis(1000), millis(1000), millis(500)]
-        "withJUnit"         | [millis(500), millis(500), millis(500)]
-        "withVerboseTestNG" | [millis(500), millis(500), millis(500)]
-        "withVerboseJUnit"  | [millis(500), millis(500), millis(500)]
+        "withTestNG"        | millis(1000)
+        "withJUnit"         | millis(500)
+        "withVerboseTestNG" | millis(500)
+        "withVerboseJUnit"  | millis(500)
     }
 }
\ No newline at end of file
diff --git a/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy b/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy
index 604e9c9..2441900 100644
--- a/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy
+++ b/subprojects/performance/src/integTest/groovy/org/gradle/performance/UpToDateBuildPerformanceTest.groovy
@@ -16,8 +16,7 @@
 
 package org.gradle.performance
 
-import org.gradle.performance.fixture.PerformanceTestRunner
-import spock.lang.Specification
+import org.gradle.performance.fixture.AbstractPerformanceTest
 import spock.lang.Unroll
 
 import static org.gradle.performance.fixture.DataAmount.kbytes
@@ -26,18 +25,19 @@ import static org.gradle.performance.fixture.Duration.millis
 /**
  * by Szczepan Faber, created at: 2/9/12
  */
-class UpToDateBuildPerformanceTest extends Specification {
+class UpToDateBuildPerformanceTest extends AbstractPerformanceTest {
     @Unroll("Project '#testProject' up-to-date build")
     def "build"() {
-        expect:
-        def result = new PerformanceTestRunner(testProject: testProject,
-                tasksToRun: ['build'],
-                runs: 5,
-                warmUpRuns: 1,
-                targetVersions: ['1.0', 'last'],
-                maxExecutionTimeRegression: [maxExecutionTimeRegression, maxExecutionTimeRegression],
-                maxMemoryRegression: [kbytes(3000), kbytes(3000)]
-        ).run()
+        given:
+        runner.testProject = testProject
+        runner.tasksToRun = ['build']
+        runner.maxExecutionTimeRegression = maxExecutionTimeRegression
+        runner.maxMemoryRegression = kbytes(3000)
+
+        when:
+        def result = runner.run()
+
+        then:
         result.assertCurrentVersionHasNotRegressed()
 
         where:
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/AbstractPerformanceTest.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/AbstractPerformanceTest.groovy
new file mode 100644
index 0000000..6abbd60
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/AbstractPerformanceTest.groovy
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+import spock.lang.Specification
+
+class AbstractPerformanceTest extends Specification {
+    final def runner = new PerformanceTestRunner(
+            runs: 5,
+            warmUpRuns: 1,
+            targetVersions: ['1.0', '1.4', 'last']
+    )
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataReporter.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataReporter.groovy
new file mode 100644
index 0000000..7611b4b
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/DataReporter.groovy
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+interface DataReporter {
+    void report(PerformanceResults results)
+}
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy
index 2293c43..4dca19b 100644
--- a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/PerformanceTestRunner.groovy
@@ -16,7 +16,6 @@
 
 package org.gradle.performance.fixture
 
-import org.gradle.api.logging.Logging
 import org.gradle.integtests.fixtures.executer.GradleDistribution
 import org.gradle.integtests.fixtures.executer.GradleExecuter
 import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext
@@ -25,9 +24,6 @@ import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 
 public class PerformanceTestRunner {
-
-    private final static LOGGER = Logging.getLogger(PerformanceTestRunner.class)
-
     def testDirectoryProvider = new TestNameTestDirectoryProvider()
     def current = new UnderDevelopmentGradleDistribution()
     def buildContext = new IntegrationTestBuildContext()
@@ -38,26 +34,26 @@ public class PerformanceTestRunner {
 
     List<String> tasksToRun = []
     DataCollector dataCollector = new MemoryInfoCollector(outputFileName: "build/totalMemoryUsed.txt")
+    DataReporter  reporter = new TextFileDataReporter()
     List<String> args = []
 
-    List<String> targetVersions = ['last']
-    List<Amount<Duration>> maxExecutionTimeRegression = [Duration.millis(0)]
-    List<Amount<DataAmount>> maxMemoryRegression = [DataAmount.bytes(0)]
+    List<String> targetVersions = []
+    Amount<Duration> maxExecutionTimeRegression = Duration.millis(0)
+    Amount<DataAmount> maxMemoryRegression = DataAmount.bytes(0)
 
     PerformanceResults results
 
     PerformanceResults run() {
-        assert targetVersions.size() == maxExecutionTimeRegression.size()
-        assert targetVersions.size() == maxMemoryRegression.size()
+        assert !targetVersions.empty
 
+        def mostRecentFinalRelease = new ReleasedVersionDistributions().mostRecentFinalRelease.version.version
+        def allVersions = targetVersions.collect { (it == 'last') ? mostRecentFinalRelease : it }.unique()
         def baselineVersions = []
-        targetVersions.eachWithIndex { it, idx ->
-            def mostRecentFinalRelease = new ReleasedVersionDistributions().mostRecentFinalRelease.version.version
-            def ver = (it == 'last') ? mostRecentFinalRelease : it
-            baselineVersions << new BaselineVersion(version: ver,
-                    maxExecutionTimeRegression: maxExecutionTimeRegression[idx],
-                    maxMemoryRegression: maxMemoryRegression[idx],
-                    results: new MeasuredOperationList(name: "Gradle $ver")
+        allVersions.each { it ->
+            baselineVersions << new BaselineVersion(version: it,
+                    maxExecutionTimeRegression: maxExecutionTimeRegression,
+                    maxMemoryRegression: maxMemoryRegression,
+                    results: new MeasuredOperationList(name: "Gradle $it")
             )
         }
 
@@ -75,6 +71,7 @@ public class PerformanceTestRunner {
             println "Executing test run #${it + 1}"
             runOnce()
         }
+        reporter.report(results)
         results
     }
 
@@ -100,7 +97,7 @@ public class PerformanceTestRunner {
 
     GradleExecuter executer(GradleDistribution dist, File projectDir) {
         dist.executer(testDirectoryProvider).
-                requireGradleHome(true).
+                requireGradleHome().
                 withDeprecationChecksDisabled().
                 withStackTraceChecksDisabled().
                 withArguments('-u').
diff --git a/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TextFileDataReporter.groovy b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TextFileDataReporter.groovy
new file mode 100644
index 0000000..ca16730
--- /dev/null
+++ b/subprojects/performance/src/testFixtures/groovy/org/gradle/performance/fixture/TextFileDataReporter.groovy
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.performance.fixture
+
+class TextFileDataReporter implements DataReporter {
+    void report(PerformanceResults results) {
+        File outFile = new File("build/performance-tests/results.txt")
+        outFile.parentFile.mkdirs()
+        results.baselineVersions.each {
+            outFile << it.getSpeedStatsAgainst(results.displayName, results.current)
+        }
+        results.baselineVersions.each {
+            outFile << it.getMemoryStatsAgainst(results.displayName, results.current)
+        }
+    }
+}
diff --git a/subprojects/plugins/plugins.gradle b/subprojects/plugins/plugins.gradle
index b163af0..e1b00be 100644
--- a/subprojects/plugins/plugins.gradle
+++ b/subprojects/plugins/plugins.gradle
@@ -44,7 +44,7 @@ dependencies {
 
     runtime libraries.commons_cli
 
-    testCompile libraries.nekohtml
+    testCompile libraries.jsoup
 }
 
 evaluationDependsOn(":wrapper")
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 b3016fe..9f08e3f 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy
@@ -24,7 +24,7 @@ class BasePluginIntegrationTest extends AbstractIntegrationSpec {
     @Requires(TestPrecondition.MANDATORY_FILE_LOCKING)
     def "clean failure message indicates file"() {
         given:
-        executer.requireGradleHome(true)
+        executer.requireGradleHome()
         buildFile << """
             apply plugin: 'base'
         """
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/DistributionPluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/DistributionPluginIntegrationTest.groovy
new file mode 100644
index 0000000..9f00c88
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/DistributionPluginIntegrationTest.groovy
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+
+import static org.hamcrest.Matchers.containsString
+
+class DistributionPluginIntegrationTest extends WellBehavedPluginTest {
+
+    @Override
+    String getPluginId() {
+        "distribution"
+    }
+
+    String getMainTask() {
+        return "distZip"
+    }
+
+    def setup() {
+        file("settings.gradle").text = "rootProject.name='TestProject'"
+        file("someFile").createFile()
+    }
+
+    def createTaskForCustomDistribution() {
+        when:
+        buildFile << """
+            apply plugin:'distribution'
+
+            distributions {
+                custom{
+                    contents {
+                        from { "someFile" }
+                    }
+                }
+            }
+
+            """
+        then:
+        succeeds('customDistZip')
+        and:
+        file('build/distributions/TestProject-custom.zip').usingNativeTools().unzipTo(file("unzip"))
+        file("unzip/TestProject-custom/someFile").assertIsFile()
+    }
+
+    def createTaskForCustomDistributionWithCustomName() {
+        when:
+        buildFile << """
+            apply plugin:'distribution'
+
+            distributions {
+                custom{
+                    baseName='customName'
+                    contents {
+                        from { "someFile" }
+                    }
+                }
+            }
+            """
+        then:
+        succeeds('customDistZip')
+        and:
+        file('build/distributions/customName.zip').usingNativeTools().unzipTo(file("unzip"))
+        file("unzip/customName/someFile").assertIsFile()
+    }
+
+
+    def createTaskForCustomDistributionWithEmptyCustomName() {
+        when:
+        buildFile << """
+            apply plugin:'distribution'
+            distributions {
+                custom{
+                    baseName=''
+                    contents {
+                        from { "someFile" }
+                    }
+                }
+            }
+
+
+            """
+        then:
+        runAndFail('customDistZip')
+        failure.assertThatDescription(containsString("Distribution baseName must not be null or empty! Check your configuration of the distribution plugin."))
+    }
+
+
+
+    def createDistributionWithoutVersion() {
+        given:
+        createDir('src/main/dist') {
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+            }
+        }
+        and:
+        buildFile << """
+            apply plugin:'distribution'
+
+
+            distributions {
+                main{
+                    baseName='myDistribution'
+                }
+            }
+            """
+        when:
+        run('distZip')
+        then:
+        file('build/distributions/myDistribution.zip').exists()
+    }
+
+    def createDistributionWithVersion() {
+        given:
+        createDir('src/main/dist') {
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+            }
+        }
+        and:
+        buildFile << """
+            apply plugin:'distribution'
+
+            version = '1.2'
+            distributions {
+                main{
+                    baseName='myDistribution'
+                }
+            }
+            distZip{
+
+            }
+            """
+        when:
+        run('distZip')
+        then:
+        file('build/distributions/myDistribution-1.2.zip').exists()
+    }
+
+    def createDistributionWithoutBaseNameAndVersion() {
+        given:
+        createDir('src/main/dist') {
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+            }
+        }
+        and:
+        buildFile << """
+            apply plugin:'distribution'
+
+            """
+        when:
+        run('distZip')
+        then:
+        file('build/distributions/TestProject.zip').exists()
+    }
+
+    def createDistributionWithoutBaseNameAndWithVersion() {
+        given:
+        createDir('src/main/dist') {
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+            }
+        }
+        and:
+        buildFile << """
+            apply plugin:'distribution'
+
+            version = 1.2
+            """
+        when:
+        run('distZip')
+        then:
+        file('build/distributions/TestProject-1.2.zip').exists()
+    }
+
+    def createCreateArchiveForCustomDistribution(){
+        given:
+        createDir('src/custom/dist') {
+            file 'file1.txt'
+            dir2 {
+                file 'file2.txt'
+            }
+        }
+        and:
+        buildFile << """
+            apply plugin:'distribution'
+
+            distributions{
+                custom
+            }
+            """
+        when:
+        run('customDistZip')
+        then:
+        file('build/distributions/TestProject-custom.zip').exists()
+    }
+
+
+    def includeFileFromSrcMainCustom() {
+        given:
+        createDir('src/custom/dist'){
+            file 'file1.txt'
+            dir {
+                file 'file2.txt'
+            }
+        }
+        and:
+        buildFile << """
+            apply plugin:'distribution'
+
+            version = 1.2
+
+            distributions{
+                custom
+            }
+            """
+        when:
+        run('customDistZip')
+        then:
+        file('build/distributions/TestProject-custom-1.2.zip').usingNativeTools().unzipTo(file("unzip"))
+        file("unzip").assertHasDescendants(
+                'TestProject-custom-1.2/file1.txt',
+                'TestProject-custom-1.2/dir/file2.txt')
+    }
+
+    def includeFileFromDistContent() {
+        given:
+        createDir('src/custom/dist'){
+            file 'file1.txt'
+            dir {
+                file 'file2.txt'
+            }
+        }
+        createDir("docs"){
+            file 'file3.txt'
+            dir2 {
+                file 'file4.txt'
+            }
+        }
+        and:
+        buildFile << """
+            apply plugin:'distribution'
+
+            version = 1.2
+
+            distributions{
+                custom {
+                    contents {
+                        from ( 'docs' ){
+                            into 'docs'
+                        }
+
+
+                    }
+                }
+            }
+            """
+        when:
+        run('customDistZip')
+        then:
+        file('build/distributions/TestProject-custom-1.2.zip').usingNativeTools().unzipTo(file("unzip"))
+        file("unzip").assertHasDescendants(
+                'TestProject-custom-1.2/file1.txt',
+                'TestProject-custom-1.2/dir/file2.txt',
+                'TestProject-custom-1.2/docs/file3.txt',
+                'TestProject-custom-1.2/docs/dir2/file4.txt')
+    }
+
+    def installFromDistContent() {
+        given:
+        createDir('src/custom/dist'){
+            file 'file1.txt'
+            dir {
+                file 'file2.txt'
+            }
+        }
+        createDir("docs"){
+            file 'file3.txt'
+            dir2 {
+                file 'file4.txt'
+            }
+        }
+        and:
+        buildFile << """
+            apply plugin:'distribution'
+
+            version = 1.2
+
+            distributions{
+                custom {
+                    contents {
+                        from ( 'docs' ){
+                            into 'docs'
+                        }
+                    }
+                }
+            }
+            """
+        when:
+        run('installCustomDist')
+
+        then:
+        file('build/install/TestProject-custom').exists()
+        file('build/install/TestProject-custom').assertHasDescendants(
+                'file1.txt',
+                'dir/file2.txt',
+                'docs/file3.txt',
+                'docs/dir2/file4.txt')
+    }
+
+    def createTarTaskForCustomDistribution() {
+        when:
+        buildFile << """
+            apply plugin:'distribution'
+
+            distributions {
+                custom{
+                    contents {
+                        from { "someFile" }
+                    }
+                }
+            }
+
+            """
+        then:
+        succeeds('customDistTar')
+        and:
+        file('build/distributions/TestProject-custom.tar').usingNativeTools().untarTo(file("untar"))
+        file("untar/TestProject-custom/someFile").assertIsFile()
+    }
+}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/JavaLibraryDistributionIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/JavaLibraryDistributionIntegrationTest.groovy
index 847b7f2..967076a 100755
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/JavaLibraryDistributionIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/JavaLibraryDistributionIntegrationTest.groovy
@@ -27,115 +27,119 @@ class JavaLibraryDistributionIntegrationTest extends WellBehavedPluginTest {
         "java-library-distribution"
     }
 
-    def canCreateADistributionWithSrcDistRuntime() {
-        given:
-        createDir('libs') {
-            file 'a.jar'
-        }
-        createDir('src/dist') {
-            file 'file1.txt'
-            dir2 {
-                file 'file2.txt'
-            }
-        }
-        and:
-        settingsFile << "rootProject.name='canCreateADistributionWithSrcDistRuntime'"
-        and:
-        buildFile << """
-		apply plugin:'java-library-distribution'
-            distribution{
-				name ='SuperApp'
-			}
-
-			dependencies {
-				runtime files('libs/a.jar')
-			}
-        """
-        when:
-        run 'distZip'
-        then:
-        def expandDir = file('expanded')
-        file('build/distributions/SuperApp.zip').unzipTo(expandDir)
-        expandDir.assertHasDescendants('lib/a.jar', 'file1.txt', 'dir2/file2.txt', 'canCreateADistributionWithSrcDistRuntime.jar')
+    @Override
+    String getMainTask() {
+        return "distZip"
     }
 
-    def canCreateADistributionWithReasonableDefaults() {
+    def "distribution includes project jar and runtime dependencies"() {
         given:
-        createDir('libs') {
-            file 'a.jar'
-        }
         settingsFile << "rootProject.name = 'DefaultJavaDistribution'"
+
         and:
         buildFile << """
-        apply plugin:'java-library-distribution'
+        apply plugin: 'java-library-distribution'
+
+        repositories {
+            mavenCentral()
+        }
         dependencies {
-            runtime files('libs/a.jar')
+            compile 'commons-collections:commons-collections:3.1'
+            runtime 'commons-lang:commons-lang:2.6'
         }
         """
+
         when:
         run 'distZip'
 
         then:
         def expandDir = file('expanded')
         file('build/distributions/DefaultJavaDistribution.zip').unzipTo(expandDir)
-        expandDir.assertHasDescendants('lib/a.jar', 'DefaultJavaDistribution.jar')
-    }
-
-    def failWithNullConfiguredDistributionName() {
-        when:
-        buildFile << """
-            apply plugin:'java-library-distribution'
-            distribution{
-                name = null
-            }
-            """
-        then:
-        runAndFail 'distZip'
-        failure.assertThatDescription(containsString("Distribution name must not be null! Check your configuration of the java-library-distribution plugin."))
+        expandDir.assertHasDescendants(
+                'DefaultJavaDistribution/lib/commons-collections-3.1.jar',
+                'DefaultJavaDistribution/lib/commons-lang-2.6.jar',
+                'DefaultJavaDistribution/DefaultJavaDistribution.jar')
+        expandDir.file('DefaultJavaDistribution/DefaultJavaDistribution.jar').assertIsCopyOf(file('build/libs/DefaultJavaDistribution.jar'))
     }
 
-    def canCreateADistributionIncludingOtherFile() {
+    def "can include additional source files in distribution"() {
         given:
-        createDir('libs') {
-            file 'a.jar'
-        }
-        createDir('src/dist') {
+        createDir('src/main/dist') {
             file 'file1.txt'
             dir2 {
                 file 'file2.txt'
             }
         }
-        createDir('other') {
-            file 'file3.txt'
+        createDir('src/dist') {
+            file 'dist1.txt'
+            dir2 {
+                file 'dist2.txt'
+            }
         }
-        createDir('other2') {
-            file 'file4.txt'
+        createDir('others/dist') {
+            file 'other1.txt'
+            dir2 {
+                file 'other2.txt'
+            }
         }
+
         and:
-        settingsFile << "rootProject.name='canCreateADistributionIncludingOtherFile'"
+        settingsFile << "rootProject.name='canCreateADistributionWithSrcDistRuntime'"
+
         and:
         buildFile << """
 		apply plugin:'java-library-distribution'
-            distribution{
-				name ='SuperApp'
-			}
-
-			dependencies {
-				runtime files('libs/a.jar')
-			}
-
-			distZip{
-				from('other')
-				from('other2'){
-					into('other2')
-				}
-			}
+
+        version = 1.2
+
+        distributions {
+            main {
+                baseName 'SuperApp'
+                contents {
+                    from 'others/dist'
+                }
+            }
+        }
+
+        repositories {
+            mavenCentral()
+        }
+        dependencies {
+            runtime 'commons-lang:commons-lang:2.6'
+        }
         """
+
         when:
         run 'distZip'
+
         then:
         def expandDir = file('expanded')
-        file('build/distributions/SuperApp.zip').unzipTo(expandDir)
-        expandDir.assertHasDescendants('lib/a.jar', 'file1.txt', 'dir2/file2.txt', 'canCreateADistributionIncludingOtherFile.jar', 'file3.txt', 'other2/file4.txt')
+        file('build/distributions/SuperApp-1.2.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'SuperApp-1.2/lib/commons-lang-2.6.jar',
+                'SuperApp-1.2/file1.txt',
+                'SuperApp-1.2/dist1.txt',
+                'SuperApp-1.2/other1.txt',
+                'SuperApp-1.2/dir2/file2.txt',
+                'SuperApp-1.2/dir2/dist2.txt',
+                'SuperApp-1.2/dir2/other2.txt',
+                'SuperApp-1.2/canCreateADistributionWithSrcDistRuntime-1.2.jar')
+    }
+
+    def "fails when distribution baseName is null"() {
+        given:
+        buildFile << """
+            apply plugin:'java-library-distribution'
+
+            distributions{
+                main{
+                    baseName = null
+                }
+            }
+            """
+
+        expect:
+        runAndFail 'distZip'
+        failure.assertThatDescription(containsString("Distribution baseName must not be null or empty! Check your configuration of the distribution plugin."))
     }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest.groovy
index dc05c76..07fcba3 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/compile/daemon/ParallelCompilerDaemonIntegrationTest.groovy
@@ -20,7 +20,7 @@ import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 
 class ParallelCompilerDaemonIntegrationTest extends AbstractIntegrationSpec {
-    @Rule TestResources resources
+    @Rule TestResources resources = new TestResources(temporaryFolder)
 
     def "daemon compiler can handle --parallel"() {
         generateProjects(10, 10, 10)
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
index 4ba2e6e..20cc575 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/AntInProcessGroovyCompilerIntegrationTest.groovy
@@ -22,7 +22,7 @@ import org.gradle.integtests.fixtures.TargetVersions
 @TargetVersions(['1.6.9', '1.7.10', '1.7.11' , '1.8.6', '1.8.8', '2.0.5'])
 class AntInProcessGroovyCompilerIntegrationTest extends BasicGroovyCompilerIntegrationSpec {
     def setup() {
-        executer.requireGradleHome(true)
+        executer.requireGradleHome()
     }
 
     @Override
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
index 19f6bce..fb20e74 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/ApiGroovyCompilerIntegrationSpec.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.groovy.compile
 
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 
 abstract class ApiGroovyCompilerIntegrationSpec extends GroovyCompilerIntegrationSpec {
     def canEnableAndDisableIntegerOptimization() {
@@ -53,7 +53,7 @@ abstract class ApiGroovyCompilerIntegrationSpec extends GroovyCompilerIntegratio
 
         then:
         noExceptionThrown()
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted("Person")
         result.testClass("Person").assertTestPassed("testMe")
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/BasicGroovyCompilerIntegrationSpec.groovy
index ce6a14d..6ccc619 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
@@ -15,19 +15,18 @@
  */
 package org.gradle.groovy.compile
 
+import com.google.common.collect.Ordering
 import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
-import org.gradle.integtests.fixtures.TestResources
 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.util.VersionNumber
 import org.junit.Rule
 
-import com.google.common.collect.Ordering
-
 @TargetVersions(['1.5.8', '1.6.9', '1.7.11', '1.8.8', '2.0.5'])
 abstract class BasicGroovyCompilerIntegrationSpec extends MultiVersionIntegrationSpec {
-    @Rule TestResources resources = new TestResources()
+    @Rule TestResources resources = new TestResources(temporaryFolder)
 
     String groovyDependency = "org.codehaus.groovy:groovy-all:$version"
 
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy
index 61ccb1c..ee7ce8e 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/compile/IncrementalGroovyCompileIntegrationTest.groovy
@@ -23,7 +23,7 @@ import org.junit.Test
 
 class IncrementalGroovyCompileIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
 
     @Test
     public void recompilesSourceWhenPropertiesChange() {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy
index e5feead..2ba87a7 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy
@@ -23,7 +23,7 @@ import org.junit.Test
 
 class IncrementalJavaCompileIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(testDirectoryProvider)
 
     @Test
     public void recompilesSourceWhenPropertiesChange() {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy
index daa93e8..53c3169 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/javadoc/JavadocIntegrationTest.groovy
@@ -21,7 +21,7 @@ import org.junit.Rule
 import spock.lang.Issue
 
 class JavadocIntegrationTest extends AbstractIntegrationSpec {
-    @Rule TestResources testResources = new TestResources()
+    @Rule TestResources testResources = new TestResources(temporaryFolder)
 
     @Issue("GRADLE-1563")
     def handlesTagsAndTaglets() {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
index 2451ced..ca02d4e 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestEnvironmentIntegrationTest.groovy
@@ -17,19 +17,19 @@
 package org.gradle.testing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 
 class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(temporaryFolder)
 
     def canRunTestsWithCustomSystemClassLoader() {
         when:
         run 'test'
 
         then:
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySystemClassLoaderIsUsed')
     }
@@ -39,7 +39,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySystemClassLoaderIsUsed')
     }
@@ -49,7 +49,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JUnitTest')
         result.testClass('org.gradle.JUnitTest').assertTestPassed('mySecurityManagerIsUsed')
     }
@@ -59,7 +59,7 @@ class TestEnvironmentIntegrationTest extends AbstractIntegrationSpec {
         run 'test'
 
         then:
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.JMockitTest')
         result.testClass('org.gradle.JMockitTest').assertTestPassed('testOk')
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
index dae72cd..4d792aa 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestOutputListenerIntegrationTest.groovy
@@ -17,18 +17,18 @@ package org.gradle.testing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.junit.Before
 import spock.lang.Issue
 
 @Issue("GRADLE-1009")
 public class TestOutputListenerIntegrationTest extends AbstractIntegrationSpec {
-    @Rule public final TestResources resources = new TestResources()
+    @Rule public final TestResources resources = new TestResources(temporaryFolder)
 
     @Before
     public void before() {
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
     }
 
     @Test
@@ -199,7 +199,7 @@ test {
         !result.output.contains('output from foo')
 
         when: "run with lifecycle"
-        result = executer.setAllowExtraLogging(false).withTasks('cleanTest', 'test').run()
+        result = executer.noExtraLogging().withTasks('cleanTest', 'test').run()
 
         then:
         result.output.contains('output from foo')
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy
index 3e8c66e..7216d7e 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestReportIntegrationTest.groovy
@@ -17,14 +17,16 @@
 package org.gradle.testing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.junit.Rule
+import org.gradle.integtests.fixtures.HtmlTestExecutionResult
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.integtests.fixtures.UsesSample
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.junit.Rule
+
+import static org.hamcrest.Matchers.contains
 import static org.hamcrest.Matchers.equalTo
 
 class TestReportIntegrationTest extends AbstractIntegrationSpec {
-    @Rule Sample sample
+    @Rule Sample sample = new Sample(temporaryFolder)
 
     def "report includes results of each invocation"() {
         given:
@@ -55,7 +57,7 @@ public class LoggingTest {
         run "test"
 
         then:
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new HtmlTestExecutionResult(testDirectory)
         result.testClass("LoggingTest").assertStdout(equalTo("This is stdout."))
         result.testClass("LoggingTest").assertStderr(equalTo("This is stderr."))
 
@@ -77,9 +79,8 @@ public class LoggingTest {
         run "testReport"
 
         then:
-        def reportDir = sample.dir.file("build/reports/allTests")
-        reportDir.file("index.html").assertIsFile()
-        reportDir.file("org.gradle.sample.CoreTest.html").text.contains("hello from CoreTest.")
-        reportDir.file("org.gradle.sample.UtilTest.html").text.contains("hello from UtilTest.")
+        def htmlReport = new HtmlTestExecutionResult(sample.dir, "allTests")
+        htmlReport.testClass("org.gradle.sample.CoreTest").assertTestCount(1, 0, 0).assertTestPassed("ok").assertStdout(contains("hello from CoreTest."))
+        htmlReport.testClass("org.gradle.sample.UtilTest").assertTestCount(1, 0, 0).assertTestPassed("ok").assertStdout(contains("hello from UtilTest."))
     }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
index 66760f1..b32c75c 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/TestingIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.testing
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import spock.lang.Issue
 import spock.lang.Timeout
 import spock.lang.Unroll
@@ -126,7 +126,7 @@ class TestingIntegrationTest extends AbstractIntegrationSpec {
         when:
         run "othertestsTest"
         then:
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted("TestCaseExtendsAbstractClass")
     }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy
index 6d673e4..2a5e8ae 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitCrossVersionIntegrationSpec.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.testing.junit
 
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec
 import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.integtests.fixtures.TestResources
@@ -26,7 +26,7 @@ import org.junit.Test
 @TargetVersions(['4,0', '4.4', '4.8.2', '4.11'])
 class JUnitCrossVersionIntegrationSpec extends MultiVersionIntegrationSpec {
     @Rule
-    public final TestResources resources = new TestResources()
+    public final TestResources resources = new TestResources(temporaryFolder)
 
 
     String junitDependency = "junit:junit:$version"
@@ -41,13 +41,13 @@ class JUnitCrossVersionIntegrationSpec extends MultiVersionIntegrationSpec {
         when:
         executer.withTasks('check').run()
         then:
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.Junit3Test', 'org.gradle.Junit4Test', 'org.gradle.IgnoredTest', 'org.gradle.CustomIgnoredTest')
         result.testClass('org.gradle.Junit3Test').assertTestsExecuted('testRenamesItself')
         result.testClass('org.gradle.Junit3Test').assertTestPassed('testRenamesItself')
         result.testClass('org.gradle.Junit4Test').assertTestsExecuted('ok')
         result.testClass('org.gradle.Junit4Test').assertTestPassed('ok')
-        result.testClass('org.gradle.Junit4Test').assertTestsSkipped('broken')
+        result.testClass('org.gradle.Junit4Test').assertTestsSkipped('broken', 'assumptionFailed')
         result.testClass('org.gradle.IgnoredTest').assertTestsSkipped('testIgnored')
         result.testClass('org.gradle.CustomIgnoredTest').assertTestCount(3, 0, 0).assertTestsSkipped("first test run", "second test run", "third test run")
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
index e9cb334..698e91d 100755
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitIntegrationTest.groovy
@@ -17,6 +17,7 @@ package org.gradle.testing.junit
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
 import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.test.fixtures.file.TestFile
@@ -31,11 +32,11 @@ import static org.junit.Assert.assertThat
 
 public class JUnitIntegrationTest extends AbstractIntegrationTest {
     @Rule
-    public final TestResources resources = new TestResources()
+    public final TestResources resources = new TestResources(testDirectoryProvider)
 
     @Before
     public void before() {
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
     }
 
     @Test
@@ -72,20 +73,28 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
 
     @Test
     public void suitesOutputIsVisible() {
-        executer.withTasks('test').withArguments('-i').run();
-
+        executer.withTasks('test').run();
         DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
-        result.assertTestClassesExecuted('org.gradle.ASuite')
+        result.assertTestClassesExecuted('org.gradle.ASuite', 'org.gradle.OkTest', 'org.gradle.OtherTest')
         result.testClass('org.gradle.ASuite').assertStdout(containsString('suite class loaded'))
-        result.testClass('org.gradle.ASuite').assertStderr(containsString('This is test stderr'))
-        result.testClass('org.gradle.ASuite').assertStdout(containsString('sys out from another test method'))
-        result.testClass('org.gradle.ASuite').assertStderr(containsString('sys err from another test method'))
-        result.testClass('org.gradle.ASuite').assertStdout(containsString('This is other stdout'))
         result.testClass('org.gradle.ASuite').assertStdout(containsString('before suite class out'))
         result.testClass('org.gradle.ASuite').assertStdout(containsString('non-asci char: ż'))
         result.testClass('org.gradle.ASuite').assertStderr(containsString('before suite class err'))
         result.testClass('org.gradle.ASuite').assertStdout(containsString('after suite class out'))
         result.testClass('org.gradle.ASuite').assertStderr(containsString('after suite class err'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('This is test stderr'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('sys out from another test method'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('sys err from another test method'))
+        result.testClass('org.gradle.OtherTest').assertStdout(containsString('This is other stdout'))
+    }
+
+    @Test
+    public void testClassesCanBeSharedByMultipleSuites() {
+        executer.withTasks('test').run();
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.SomeTest')
+        result.testClass("org.gradle.SomeTest").assertTestCount(2, 0, 0)
+        result.testClass("org.gradle.SomeTest").assertTestsExecuted("ok", "ok")
     }
 
     @Test
@@ -101,10 +110,10 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
                 .assertTestsExecuted('testRenamesItself')
                 .assertTestPassed('testRenamesItself')
         result.testClass('org.gradle.Junit4Test')
-                .assertTestCount(2, 0, 0)
+                .assertTestCount(3, 0, 0)
                 .assertTestsExecuted('ok')
                 .assertTestPassed('ok')
-                .assertTestsSkipped('broken')
+                .assertTestsSkipped('broken', 'assumptionFailed')
         result.testClass('org.gradle.IgnoredTest').assertTestCount(1, 0, 0).assertTestsSkipped("testIgnored")
         result.testClass('org.gradle.CustomIgnoredTest').assertTestCount(3, 0, 0).assertTestsSkipped("first test run", "second test run", "third test run")
     }
@@ -408,7 +417,7 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
     public void canHaveMultipleTestTaskInstances() {
         executer.withTasks('check').run()
 
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.Test1', 'org.gradle.Test2')
         result.testClass('org.gradle.Test1').assertTestPassed('ok')
         result.testClass('org.gradle.Test2').assertTestPassed('ok')
@@ -441,4 +450,21 @@ public class JUnitIntegrationTest extends AbstractIntegrationTest {
         testClass.assertStderr(containsText("thread 1 err"))
         testClass.assertStderr(containsText("thread 2 err"))
     }
+
+    @Test
+    public void supportsJunit3Suites() {
+        executer.withTasks('test').run();
+        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
+
+        result.assertTestClassesExecuted('org.gradle.SomeTest1', 'org.gradle.SomeTest2', 'org.gradle.SomeSuite')
+        result.testClass("org.gradle.SomeTest1").assertTestCount(1, 0, 0)
+        result.testClass("org.gradle.SomeTest1").assertTestsExecuted("testOk1")
+        result.testClass("org.gradle.SomeTest2").assertTestCount(1, 0, 0)
+        result.testClass("org.gradle.SomeTest2").assertTestsExecuted("testOk2")
+        result.testClass("org.gradle.SomeSuite").assertTestCount(0, 0, 0)
+        result.testClass("org.gradle.SomeSuite").assertStdout(containsString("stdout in TestSetup#setup"))
+        result.testClass("org.gradle.SomeSuite").assertStdout(containsString("stdout in TestSetup#teardown"))
+        result.testClass("org.gradle.SomeSuite").assertStderr(containsString("stderr in TestSetup#setup"))
+        result.testClass("org.gradle.SomeSuite").assertStderr(containsString("stderr in TestSetup#teardown"))
+    }
 }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
index 048f924..46c3f4f 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/junit/JUnitLoggingIntegrationTest.groovy
@@ -17,7 +17,7 @@
 package org.gradle.testing.junit
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.executer.ExecutionResult
 import org.gradle.util.TextUtil
@@ -28,11 +28,11 @@ import static org.hamcrest.Matchers.equalTo
 
 // cannot make assumptions about order in which test methods of JUnit4Test get executed
 class JUnitLoggingIntegrationTest extends AbstractIntegrationSpec {
-    @Rule TestResources resources
+    @Rule TestResources resources = new TestResources(temporaryFolder)
     ExecutionResult result
 
     def setup() {
-        executer.setAllowExtraLogging(false).withStackTraceChecksDisabled().withTasks("test")
+        executer.noExtraLogging().withStackTraceChecksDisabled().withTasks("test")
     }
 
     def "defaultLifecycleLogging"() {
@@ -104,7 +104,7 @@ public class EncodingTest {
         executer.withTasks("test").runWithFailure()
 
         then:
-        new DefaultTestExecutionResult(testDirectory)
+        new JUnitXmlTestExecutionResult(testDirectory)
                 .testClass("EncodingTest")
                 .assertTestPassed("encodesCdata")
                 .assertTestFailed("encodesAttributeValues", equalTo('java.lang.RuntimeException: html: <> cdata: ]]>'))
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
index ce6399e..2d40e17 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/SampleTestNGIntegrationTest.groovy
@@ -24,13 +24,13 @@ import org.junit.Test
  */
 public class SampleTestNGIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public final Sample sample = new Sample()
+    @Rule public final Sample sample = new Sample(testDirectoryProvider)
 
     @Test @UsesSample('testing/testng/suitexmlbuilder')
     public void suiteXmlBuilder() {
         executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
 
-        def result = new DefaultTestExecutionResult(sample.dir)
+        def result = new JUnitXmlTestExecutionResult(sample.dir)
         result.assertTestClassesExecuted('org.gradle.testng.UserImplTest')
         result.testClass('org.gradle.testng.UserImplTest').assertTestsExecuted('testOkFirstName')
         result.testClass('org.gradle.testng.UserImplTest').assertTestPassed('testOkFirstName')
@@ -40,7 +40,7 @@ public class SampleTestNGIntegrationTest extends AbstractIntegrationTest {
     public void javaJdk14Passing() {
         executer.inDirectory(sample.dir).withTasks('clean', 'test').run()
 
-        def result = new DefaultTestExecutionResult(sample.dir)
+        def result = new JUnitXmlTestExecutionResult(sample.dir)
         result.assertTestClassesExecuted('org.gradle.OkTest')
         result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
     }
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
index e42336d..5b315f7 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGIntegrationTest.groovy
@@ -16,7 +16,7 @@
 package org.gradle.testing.testng
 
 import org.gradle.integtests.fixtures.AbstractIntegrationTest
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestNGExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.gradle.integtests.fixtures.executer.ExecutionResult
@@ -34,11 +34,11 @@ import static org.junit.Assert.assertThat
  */
 class TestNGIntegrationTest extends AbstractIntegrationTest {
 
-    @Rule public TestResources resources = new TestResources()
+    @Rule public TestResources resources = new TestResources(testDirectoryProvider)
 
     @Before
     public void before() {
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
     }
 
     @Test
@@ -49,7 +49,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
         assertThat(result.error, not(containsString('stderr')))
         assertThat(result.error, not(containsString('a warning')))
 
-        new DefaultTestExecutionResult(testDirectory).testClass('org.gradle.OkTest').assertTestPassed('ok')
+        new JUnitXmlTestExecutionResult(testDirectory).testClass('org.gradle.OkTest').assertTestPassed('ok')
     }
 
     @Test
@@ -76,7 +76,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
     void groovyJdk15Failing() {
         executer.withTasks("test").runWithFailure().assertTestsFailed()
 
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest')
         result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
     }
@@ -85,7 +85,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
     void groovyJdk15Passing() {
         executer.withTasks("test").run()
 
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.OkTest')
         result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
     }
@@ -94,7 +94,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
     void javaJdk14Failing() {
         executer.withTasks("test").runWithFailure().assertTestsFailed()
 
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest')
         result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
     }
@@ -109,7 +109,7 @@ class TestNGIntegrationTest extends AbstractIntegrationTest {
     private void doJavaJdk15Failing(String testNGVersion) {
         executer.withTasks("test").withArguments("-PtestNGVersion=$testNGVersion").runWithFailure().assertTestsFailed()
 
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.BadTest', 'org.gradle.TestWithBrokenSetup', 'org.gradle.BrokenAfterSuite', 'org.gradle.TestWithBrokenMethodDependency')
         result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('java.lang.IllegalArgumentException: broken'))
         result.testClass('org.gradle.TestWithBrokenMethodDependency').assertTestFailed('broken', equalTo('java.lang.RuntimeException: broken'))
@@ -160,8 +160,19 @@ test {
     @Test
     void supportsTestGroups() {
         executer.withTasks("test").run()
-        def result = new DefaultTestExecutionResult(testDirectory)
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted('org.gradle.groups.SomeTest')
         result.testClass('org.gradle.groups.SomeTest').assertTestsExecuted("databaseTest")
     }
+
+    @Test
+    void supportsTestFactory() {
+        executer.withTasks("test").run()
+        def result = new JUnitXmlTestExecutionResult(testDirectory)
+        result.assertTestClassesExecuted('org.gradle.factory.FactoryTest')
+        result.testClass('org.gradle.factory.FactoryTest').assertTestCount(2, 0, 0)
+        result.testClass('org.gradle.factory.FactoryTest').assertStdout(containsString('TestingFirst'))
+        result.testClass('org.gradle.factory.FactoryTest').assertStdout(containsString('TestingSecond'))
+        result.testClass('org.gradle.factory.FactoryTest').assertStdout(not(containsString('Default test name')))
+    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
index 286dfeb..10246a1 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGLoggingIntegrationTest.groovy
@@ -25,11 +25,11 @@ import org.junit.Rule
 // can make assumptions about order in which test methods of TestNGTest get executed
 // because the methods are chained with 'methodDependsOn'
 class TestNGLoggingIntegrationTest extends AbstractIntegrationSpec {
-    @Rule TestResources resources
+    @Rule TestResources resources = new TestResources(temporaryFolder)
     ExecutionResult result
 
     def setup() {
-        executer.setAllowExtraLogging(false).withStackTraceChecksDisabled().withTasks("test")
+        executer.noExtraLogging().withStackTraceChecksDisabled().withTasks("test")
     }
 
     def "defaultLifecycleLogging"() {
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy
deleted file mode 100644
index a5284a2..0000000
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesJUnitXmlResultsIntegrationTest.groovy
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package org.gradle.testing.testng
-
-import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
-
-import static org.hamcrest.Matchers.*
-
-public class TestNGProducesJUnitXmlResultsIntegrationTest extends
-        AbstractIntegrationSpec {
-    def setup() {
-        executer.allowExtraLogging = false
-    }
-
-    def "produces JUnit xml results"() {
-        expect:
-        assertProducesXmlResults "useTestNG()"
-    }
-
-    def "produces JUnit xml results when running tests in parallel"() {
-        expect:
-        assertProducesXmlResults "useTestNG(); maxParallelForks 2"
-    }
-
-    def "produces JUnit xml results with aggressive forking"() {
-        expect:
-        assertProducesXmlResults "useTestNG(); forkEvery 1"
-    }
-
-    def assertProducesXmlResults(String testConfiguration) {
-        file("src/test/java/org/MixedMethodsTest.java") << """package org;
-import org.testng.annotations.*;
-import static org.testng.Assert.*;
-
-public class MixedMethodsTest {
-    @Test public void passing() {
-        System.out.println("out.pass");
-        System.err.println("err.pass");
-    }
-    @Test public void failing() {
-        System.out.println("out.fail");
-        System.err.println("err.fail");
-        fail("failing!");
-    }
-    @Test public void passing2() {
-        System.out.println("out.pass2");
-        System.err.println("err.pass2");
-    }
-    @Test public void failing2() {
-        System.out.println("out.fail2");
-        System.err.println("err.fail2");
-        fail("failing2!");
-    }
-    @Test(enabled = false) public void skipped() {}
-}
-"""
-        file("src/test/java/org/PassingTest.java") << """package org;
-import org.testng.annotations.*;
-
-public class PassingTest {
-    @Test public void passing() {
-        System.out.println("out" );
-    }
-    @Test public void passing2() {}
-}
-"""
-        file("src/test/java/org/FailingTest.java") << """package org;
-import org.testng.annotations.*;
-import static org.testng.Assert.*;
-
-public class FailingTest {
-    @Test public void failing() {
-        System.err.println("err");
-        fail();
-    }
-    @Test public void failing2() {
-        fail();
-    }
-}
-"""
-        file("src/test/java/org/NoOutputsTest.java") << """package org;
-import org.testng.annotations.*;
-
-public class NoOutputsTest {
-    @Test(enabled=false) public void skipped() {}
-    @Test public void passing() {}
-}
-"""
-
-        file("src/test/java/org/EncodingTest.java") << """package org;
-import org.testng.annotations.*;
-
-public class EncodingTest {
-    @Test public void encodesCdata() {
-        System.out.println("< html allowed, cdata closing token ]]> encoded!");
-        System.out.print("no EOL, ");
-        System.out.println("non-ascii char: ż");
-        System.out.println("xml entity: &");
-        System.err.println("< html allowed, cdata closing token ]]> encoded!");
-    }
-    @Test public void encodesAttributeValues() {
-        throw new RuntimeException("html: <> cdata: ]]> non-ascii: ż");
-    }
-}
-"""
-
-        def buildFile = file('build.gradle')
-        buildFile << """
-apply plugin: 'java'
-repositories { mavenCentral() }
-dependencies { testCompile 'org.testng:testng:6.3.1' }
-
-test {
-    $testConfiguration
-}
-"""
-        //when
-        executer.withTasks('test').runWithFailure().assertTestsFailed()
-
-        //then
-        def junitResult = new DefaultTestExecutionResult(file("."));
-        junitResult
-            .assertTestClassesExecuted("org.FailingTest","org.PassingTest", "org.MixedMethodsTest", "org.NoOutputsTest", "org.EncodingTest")
-
-        junitResult.testClass("org.MixedMethodsTest")
-            .assertTestCount(4, 2, 0)
-            .assertTestsExecuted("passing", "passing2", "failing", "failing2")
-            .assertTestFailed("failing", equalTo('java.lang.AssertionError: failing!'))
-            .assertTestFailed("failing2", equalTo('java.lang.AssertionError: failing2!'))
-            .assertTestPassed("passing")
-            .assertTestPassed("passing2")
-            .assertTestsSkipped()
-            .assertStderr(allOf(containsString("err.fail"), containsString("err.fail2"), containsString("err.pass"), containsString("err.pass2")))
-            .assertStderr(not(containsString("out.")))
-            .assertStdout(allOf(containsString("out.fail"), containsString("out.fail2"), containsString("out.pass"), containsString("out.pass2")))
-            .assertStdout(not(containsString("err.")))
-
-        junitResult.testClass("org.PassingTest")
-            .assertTestCount(2, 0, 0)
-            .assertTestsExecuted("passing", "passing2")
-            .assertTestPassed("passing").assertTestPassed("passing2")
-            .assertStdout(equalTo("out\n"))
-            .assertStderr(equalTo(""))
-
-        junitResult.testClass("org.FailingTest")
-            .assertTestCount(2, 2, 0)
-            .assertTestsExecuted("failing", "failing2")
-            .assertTestFailed("failing", anything()).assertTestFailed("failing2", anything())
-            .assertStdout(equalTo(""))
-            .assertStderr(equalTo("err\n"))
-
-        junitResult.testClass("org.NoOutputsTest")
-            .assertTestCount(1, 0, 0)
-            .assertTestsExecuted("passing").assertTestPassed("passing")
-            .assertStdout(equalTo(""))
-            .assertStderr(equalTo(""))
-
-        junitResult.testClass("org.EncodingTest")
-            .assertTestCount(2, 1, 0)
-            .assertTestPassed("encodesCdata")
-            .assertTestFailed("encodesAttributeValues", equalTo('java.lang.RuntimeException: html: <> cdata: ]]> non-ascii: ż'))
-            .assertStdout(equalTo("""< html allowed, cdata closing token ]]> encoded!
-no EOL, non-ascii char: ż
-xml entity: &
-"""))
-            .assertStderr(equalTo("< html allowed, cdata closing token ]]> encoded!\n"))
-    }
-}
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
index 316102d..dcd8ee0 100644
--- a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGProducesOldReportsIntegrationTest.groovy
@@ -20,12 +20,12 @@
 package org.gradle.testing.testng
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestNGExecutionResult
 
 public class TestNGProducesOldReportsIntegrationTest extends AbstractIntegrationSpec {
     def setup() {
-        executer.allowExtraLogging = false
+        executer.noExtraLogging()
     }
 
     def "always produces the new xml reports"() {
@@ -59,7 +59,7 @@ test {
 
         then:
         !new TestNGExecutionResult(file(".")).hasTestNGXmlResults()
-        new DefaultTestExecutionResult(file(".")).hasJUnitXmlResults()
+        new JUnitXmlTestExecutionResult(file(".")).hasJUnitXmlResults()
     }
 
     def "can generate the old xml reports"() {
@@ -87,7 +87,7 @@ test {
         executer.withTasks('test').run()
 
         then:
-        new DefaultTestExecutionResult(file(".")).hasJUnitXmlResults()
+        new JUnitXmlTestExecutionResult(file(".")).hasJUnitXmlResults()
 
         def testNG = new TestNGExecutionResult(file("."))
         testNG.hasTestNGXmlResults()
diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGXmlResultAndHtmlReportIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGXmlResultAndHtmlReportIntegrationTest.groovy
new file mode 100644
index 0000000..28e1974
--- /dev/null
+++ b/subprojects/plugins/src/integTest/groovy/org/gradle/testing/testng/TestNGXmlResultAndHtmlReportIntegrationTest.groovy
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.gradle.testing.testng
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.HtmlTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
+import org.gradle.integtests.fixtures.TestExecutionResult
+
+import static org.hamcrest.Matchers.*
+
+public class TestNGXmlResultAndHtmlReportIntegrationTest extends
+        AbstractIntegrationSpec {
+
+    def setup() {
+        executer.noExtraLogging()
+        setupTestCases()
+    }
+
+    def "produces JUnit xml results"() {
+        when:
+        runWithTestConfig("useTestNG()")
+        then:
+        verifyTestResultWith(new JUnitXmlTestExecutionResult(file(".")))
+        verifyTestResultWith(new HtmlTestExecutionResult(file(".")))
+    }
+
+    def "produces JUnit xml results when running tests in parallel"() {
+        when:
+        runWithTestConfig("useTestNG(); maxParallelForks 2")
+        then:
+        verifyTestResultWith(new JUnitXmlTestExecutionResult(file(".")))
+        verifyTestResultWith(new HtmlTestExecutionResult(file(".")))
+    }
+
+    def "produces JUnit xml results with aggressive forking"() {
+        when:
+        runWithTestConfig("useTestNG(); forkEvery 1")
+        then:
+        verifyTestResultWith(new JUnitXmlTestExecutionResult(file(".")))
+        verifyTestResultWith(new HtmlTestExecutionResult(file(".")))
+    }
+
+    def runWithTestConfig(String testConfiguration) {
+        def buildFile = file('build.gradle')
+        buildFile << """
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'org.testng:testng:6.3.1' }
+
+            test {
+                $testConfiguration
+            }
+            """
+        //when
+        executer.withTasks('test').runWithFailure().assertTestsFailed()
+    }
+
+    def verifyTestResultWith(TestExecutionResult executionResult) {
+        executionResult.assertTestClassesExecuted("org.FailingTest", "org.PassingTest", "org.MixedMethodsTest", "org.NoOutputsTest", "org.EncodingTest")
+
+        executionResult.testClass("org.MixedMethodsTest")
+                .assertTestCount(4, 2, 0)
+                .assertTestsExecuted("passing", "passing2", "failing", "failing2")
+                .assertTestFailed("failing", equalTo('java.lang.AssertionError: failing!'))
+                .assertTestFailed("failing2", equalTo('java.lang.AssertionError: failing2!'))
+                .assertTestPassed("passing")
+                .assertTestPassed("passing2")
+                .assertTestsSkipped()
+                .assertStderr(allOf(containsString("err.fail"), containsString("err.fail2"), containsString("err.pass"), containsString("err.pass2")))
+                .assertStderr(not(containsString("out.")))
+                .assertStdout(allOf(containsString("out.fail"), containsString("out.fail2"), containsString("out.pass"), containsString("out.pass2")))
+                .assertStdout(not(containsString("err.")))
+
+        executionResult.testClass("org.PassingTest")
+                .assertTestCount(2, 0, 0)
+                .assertTestsExecuted("passing", "passing2")
+                .assertTestPassed("passing").assertTestPassed("passing2")
+                .assertStdout(equalTo("out\n"))
+                .assertStderr(equalTo(""))
+
+        executionResult.testClass("org.FailingTest")
+                .assertTestCount(2, 2, 0)
+                .assertTestsExecuted("failing", "failing2")
+                .assertTestFailed("failing", anything()).assertTestFailed("failing2", anything())
+                .assertStdout(equalTo(""))
+                .assertStderr(equalTo("err\n"))
+
+        executionResult.testClass("org.NoOutputsTest")
+                .assertTestCount(1, 0, 0)
+                .assertTestsExecuted("passing").assertTestPassed("passing")
+                .assertStdout(equalTo(""))
+                .assertStderr(equalTo(""))
+
+        executionResult.testClass("org.EncodingTest")
+                .assertTestCount(2, 1, 0)
+                .assertTestPassed("encodesCdata")
+                .assertTestFailed("encodesAttributeValues", equalTo('java.lang.RuntimeException: html: <> cdata: ]]> non-ascii: ż'))
+                .assertStdout(equalTo("""< html allowed, cdata closing token ]]> encoded!
+no EOL, non-ascii char: ż
+xml entity: &
+"""))
+                .assertStderr(equalTo("< html allowed, cdata closing token ]]> encoded!\n"))
+    }
+
+
+    private void setupTestCases() {
+        file("src/test/java/org/MixedMethodsTest.java") << """package org;
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class MixedMethodsTest {
+    @Test public void passing() {
+        System.out.println("out.pass");
+        System.err.println("err.pass");
+    }
+    @Test public void failing() {
+        System.out.println("out.fail");
+        System.err.println("err.fail");
+        fail("failing!");
+    }
+    @Test public void passing2() {
+        System.out.println("out.pass2");
+        System.err.println("err.pass2");
+    }
+    @Test public void failing2() {
+        System.out.println("out.fail2");
+        System.err.println("err.fail2");
+        fail("failing2!");
+    }
+    @Test(enabled = false) public void skipped() {}
+}
+"""
+        file("src/test/java/org/PassingTest.java") << """package org;
+import org.testng.annotations.*;
+
+public class PassingTest {
+    @Test public void passing() {
+        System.out.println("out" );
+    }
+    @Test public void passing2() {}
+}
+"""
+        file("src/test/java/org/FailingTest.java") << """package org;
+import org.testng.annotations.*;
+import static org.testng.Assert.*;
+
+public class FailingTest {
+    @Test public void failing() {
+        System.err.println("err");
+        fail();
+    }
+    @Test public void failing2() {
+        fail();
+    }
+}
+"""
+        file("src/test/java/org/NoOutputsTest.java") << """package org;
+import org.testng.annotations.*;
+
+public class NoOutputsTest {
+    @Test(enabled=false) public void skipped() {}
+    @Test public void passing() {}
+}
+"""
+
+        file("src/test/java/org/EncodingTest.java") << """package org;
+import org.testng.annotations.*;
+
+public class EncodingTest {
+    @Test public void encodesCdata() {
+        System.out.println("< html allowed, cdata closing token ]]> encoded!");
+        System.out.print("no EOL, ");
+        System.out.println("non-ascii char: ż");
+        System.out.println("xml entity: &");
+        System.err.println("< html allowed, cdata closing token ]]> encoded!");
+    }
+    @Test public void encodesAttributeValues() {
+        throw new RuntimeException("html: <> cdata: ]]> non-ascii: ż");
+    }
+}
+"""
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
index b56604a..857793d 100644
--- a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/Junit4Test.java
@@ -1,5 +1,6 @@
 package org.gradle;
 
+import org.junit.Assume;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -12,4 +13,12 @@ public class Junit4Test {
     public void broken() {
         throw new RuntimeException();
     }
+
+    public void helpermethod(){
+	}
+	
+    @Test
+    public void assumptionFailed() {
+        Assume.assumeTrue(false);
+    }
 }
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/NoTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/NoTest.java
new file mode 100644
index 0000000..32dea4b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/junit4Tests/src/test/java/org/gradle/NoTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.testing.junit.JUnitIntegrationTest.junit4Tests.src.test.java.org.gradle;
+
+public class NoTest {
+    public void notATest() {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/build.gradle
new file mode 100644
index 0000000..b091c5e
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/build.gradle
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies {
+    testCompile "junit:junit:3.8"
+}
+
+test {
+    include '**/*Suite.class'
+    exclude '**/*Test.class'
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeSuite.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeSuite.java
new file mode 100644
index 0000000..7953c04
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeSuite.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import junit.extensions.TestSetup;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+public class SomeSuite extends TestCase {
+
+    public static Test suite() {
+        final TestSuite suite = new TestSuite();
+        suite.addTestSuite(SomeTest1.class);
+        suite.addTestSuite(SomeTest2.class);
+        TestSetup wrappedSuite = new junit.extensions.TestSetup(suite) {
+
+            protected void setUp() {
+                System.out.println("stdout in TestSetup#setup");
+                System.err.println("stderr in TestSetup#setup");
+            }
+
+            protected void tearDown() {
+                System.out.println("stdout in TestSetup#teardown");
+                System.err.println("stderr in TestSetup#teardown");
+            }
+        };
+
+        return wrappedSuite;
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeTest1.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeTest1.java
new file mode 100644
index 0000000..42de49f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeTest1.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import junit.framework.TestCase;
+
+public class SomeTest1 extends TestCase {
+    public void testOk1(){
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeTest2.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeTest2.java
new file mode 100644
index 0000000..4e5105b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/supportsJunit3Suites/src/test/java/org/gradle/SomeTest2.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import junit.framework.TestCase;
+
+public class SomeTest2 extends TestCase{
+    public void testOk2(){
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/build.gradle
new file mode 100644
index 0000000..d03384a
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'junit:junit:4.11'}
+test {
+    include '**/*Suite.class'
+    exclude '**/*Test.class'
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeOtherTestSuite.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeOtherTestSuite.java
new file mode 100644
index 0000000..98bbbdc
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeOtherTestSuite.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+ at RunWith(Suite.class)
+ at Suite.SuiteClasses({SomeTest.class})
+public class SomeOtherTestSuite {
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeTest.java
new file mode 100644
index 0000000..c349393
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeTest.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+
+public class SomeTest {
+    @org.junit.Test
+    public void ok() throws Exception {
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeTestSuite.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeTestSuite.java
new file mode 100644
index 0000000..30610ec
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/junit/JUnitIntegrationTest/testClassesCanBeSharedByMultipleSuites/src/test/java/org/gradle/SomeTestSuite.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle;
+import  org.junit.runner.RunWith;
+import  org.junit.runners.Suite;
+
+ at RunWith(Suite.class)
+ at Suite.SuiteClasses({SomeTest.class})
+public class SomeTestSuite {
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/build.gradle b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/build.gradle
new file mode 100644
index 0000000..e1d6c9b
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/build.gradle
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+apply plugin: "java"
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile "org.testng:testng:6.3.1"
+}
+
+test {
+    useTestNG()
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/src/test/java/org/gradle/factory/FactoryTest.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/src/test/java/org/gradle/factory/FactoryTest.java
new file mode 100644
index 0000000..e7eb763
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/src/test/java/org/gradle/factory/FactoryTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.factory;
+
+import org.testng.annotations.Test;
+
+public class FactoryTest {
+
+    private final String name;
+
+    public FactoryTest(String name){
+        this.name = name;
+    }
+
+    @Test
+    public void printMethod(){
+       System.out.println("Testing" + name);
+    }
+}
diff --git a/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/src/test/java/org/gradle/factory/TestNGFactory.java b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/src/test/java/org/gradle/factory/TestNGFactory.java
new file mode 100644
index 0000000..65db72f
--- /dev/null
+++ b/subprojects/plugins/src/integTest/resources/org/gradle/testing/testng/TestNGIntegrationTest/supportsTestFactory/src/test/java/org/gradle/factory/TestNGFactory.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.factory;
+
+import org.testng.annotations.Factory;
+
+public class TestNGFactory {
+    @Factory
+    public Object[] factory() {
+        return new Object[]{
+                new FactoryTest("First"),
+                new FactoryTest("Second")
+        };
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/Distribution.java b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/Distribution.java
new file mode 100644
index 0000000..2b63d98
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/Distribution.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.distribution;
+
+import org.gradle.api.Action;
+import org.gradle.api.Named;
+import org.gradle.api.Incubating;
+import org.gradle.api.file.CopySpec;
+
+/**
+ * A distribution allow to bundle an application or a library including dependencies,sources...
+ *
+ * @author scogneau
+ */
+ at Incubating
+public interface Distribution extends Named {
+    /**
+     * The name of this distribution.
+     */
+    String getName();
+
+    /**
+     * Returns the baseName of the distribution. This is used in file names for the distribution.
+     */
+    String getBaseName();
+
+    /**
+     * Set the baseName of the distribution. This is used in file names for the distribution.
+     */
+    void setBaseName(String baseName);
+
+    /**
+     * The contents of the distribution.
+     */
+    CopySpec getContents();
+
+    /**
+     * Configures the contents of the distribution.
+     */
+    CopySpec contents(Action<? super CopySpec> action);
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/DistributionContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/DistributionContainer.java
new file mode 100644
index 0000000..cc70185
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/DistributionContainer.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.distribution;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectContainer;
+
+/**
+ * A {@code DistributionContainer} manages a set of {@link Distribution} objects.
+ */
+ at Incubating
+public interface DistributionContainer extends NamedDomainObjectContainer<Distribution> {
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistribution.java b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistribution.java
new file mode 100644
index 0000000..ead3990
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistribution.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.distribution.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.distribution.Distribution;
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.copy.CopySpecImpl;
+
+/**
+ * Allow user to declare a distribution.
+ *
+ * @author scogneau
+ */
+public class DefaultDistribution implements Distribution {
+    private final String name;
+    private String baseName;
+    private final CopySpec contents;
+
+    public DefaultDistribution(String name, FileResolver fileResolver) {
+        this.name = name;
+        this.contents = new CopySpecImpl(fileResolver);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getBaseName() {
+        return baseName;
+    }
+
+    public void setBaseName(String baseName) {
+        this.baseName = baseName;
+    }
+
+    public CopySpec getContents() {
+        return contents;
+    }
+
+    public CopySpec contents(Action<? super CopySpec> action) {
+        action.execute(contents);
+        return contents;
+    }
+}
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
new file mode 100644
index 0000000..182d10e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/internal/DefaultDistributionContainer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.distribution.internal;
+
+import org.gradle.api.distribution.Distribution;
+import org.gradle.api.distribution.DistributionContainer;
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.internal.reflect.Instantiator;
+
+/**
+ * Default implementation for {@link org.gradle.api.distribution.DistributionContainer}
+ *
+ * @author scogneau
+ */
+public class DefaultDistributionContainer extends AbstractNamedDomainObjectContainer<Distribution> implements DistributionContainer {
+    private final FileResolver fileResolver;
+
+    public DefaultDistributionContainer(Class<Distribution> type, Instantiator instantiator, FileResolver fileResolver) {
+        super(type, instantiator);
+        this.fileResolver = fileResolver;
+    }
+
+    protected Distribution doCreate(String name) {
+        return getInstantiator().newInstance(DefaultDistribution.class, name, fileResolver);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/package-info.java b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/package-info.java
new file mode 100644
index 0000000..1716293
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * The main interfaces and classes of the distribution plugin.
+ */
+package org.gradle.api.distribution;
\ No newline at end of file
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
new file mode 100644
index 0000000..208d9ee
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/distribution/plugins/DistributionPlugin.groovy
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.distribution.plugins
+
+import org.gradle.api.GradleException
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.distribution.Distribution
+import org.gradle.api.distribution.DistributionContainer
+import org.gradle.api.distribution.internal.DefaultDistributionContainer
+import org.gradle.api.tasks.Sync
+import org.gradle.api.tasks.bundling.Zip
+import org.gradle.api.tasks.bundling.Tar
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.gradle.internal.reflect.Instantiator
+
+import javax.inject.Inject
+import org.gradle.api.plugins.BasePlugin
+
+/**
+ * <p>A {@link Plugin} to package project as a distribution.</p>
+ *
+ * @author scogneau
+ *
+ */
+ at Incubating
+class DistributionPlugin implements Plugin<Project> {
+    /**
+     * Name of the main distribution
+     */
+    static final String MAIN_DISTRIBUTION_NAME = "main"
+
+    static final String DISTRIBUTION_GROUP = "distribution"
+    static final String TASK_DIST_ZIP_NAME = "distZip"
+    static final String TASK_DIST_TAR_NAME = "distTar"
+    static final String TASK_INSTALL_NAME = "installDist"
+
+    private DistributionContainer extension
+    private Project project
+    private Instantiator instantiator
+
+    @Inject
+    public DistributionPlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    public void apply(Project project) {
+        this.project = project
+        project.plugins.apply(BasePlugin)
+        addPluginExtension()
+    }
+
+    void addPluginExtension() {
+        extension = project.extensions.create("distributions", DefaultDistributionContainer.class, Distribution.class, instantiator, project.fileResolver)
+        extension.all { dist ->
+            dist.baseName = dist.name == MAIN_DISTRIBUTION_NAME ? project.name : String.format("%s-%s", project.name, dist.name)
+            dist.contents.from("src/${dist.name}/dist")
+            addZipTask(dist)
+            addTarTask(dist)
+            addInstallTask(dist)
+        }
+        extension.create(DistributionPlugin.MAIN_DISTRIBUTION_NAME)
+    }
+
+    void addZipTask(Distribution distribution) {
+        def taskName = TASK_DIST_ZIP_NAME
+        if (!MAIN_DISTRIBUTION_NAME.equals(distribution.name)) {
+            taskName = distribution.name + "DistZip"
+        }
+        configureArchiveTask(taskName, distribution, Zip)
+    }
+
+    void addTarTask(Distribution distribution) {
+        def taskName = TASK_DIST_TAR_NAME
+        if (!MAIN_DISTRIBUTION_NAME.equals(distribution.name)) {
+            taskName = distribution.name + "DistTar"
+        }
+        configureArchiveTask(taskName, distribution, Tar)
+    }
+
+    private <T extends AbstractArchiveTask> void configureArchiveTask(String taskName, Distribution distribution, Class<T> type) {
+        def archiveTask = project.tasks.add(taskName, type)
+        archiveTask.description = "Bundles the project as a distribution."
+        archiveTask.group = DISTRIBUTION_GROUP
+        archiveTask.conventionMapping.baseName = {
+            if (distribution.baseName == null || distribution.baseName.equals("")) {
+                throw new GradleException("Distribution baseName must not be null or empty! Check your configuration of the distribution plugin.")
+            }
+            distribution.baseName
+        }
+        def baseDir = { archiveTask.archiveName - ".${archiveTask.extension}" }
+        archiveTask.into(baseDir) {
+            with(distribution.contents)
+        }
+    }
+
+    private void addInstallTask(Distribution distribution) {
+        def taskName = TASK_INSTALL_NAME
+        if (!MAIN_DISTRIBUTION_NAME.equals(distribution.name)) {
+            taskName = "install"+ distribution.name.capitalize() + "Dist"
+        }
+        def installTask = project.tasks.add(taskName, Sync)
+        installTask.description = "Installs the project as a JVM application along with libs and OS specific scripts."
+        installTask.group = DISTRIBUTION_GROUP
+        installTask.with distribution.contents
+        installTask.into { project.file("${project.buildDir}/install/${distribution.baseName}") }
+        installTask.doFirst {
+            if (destinationDir.directory) {
+                if (!new File(destinationDir, 'lib').directory || !new File(destinationDir, 'bin').directory) {
+                    throw new GradleException("The specified installation directory '${destinationDir}' is neither empty nor does it contain an installation for '${distribution.name}'.\n" +
+                            "If you really want to install to this directory, delete it and run the install task again.\n" +
+                            "Alternatively, choose a different installation directory."
+                    )
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/JavaLibrary.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/JavaLibrary.java
index e4544a6..5fd5bab 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/JavaLibrary.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/JavaLibrary.java
@@ -16,31 +16,48 @@
 
 package org.gradle.api.internal.java;
 
-import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.DependencySet;
-import org.gradle.api.artifacts.PublishArtifactSet;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.internal.component.SoftwareComponentInternal;
+import org.gradle.api.internal.component.Usage;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
 
 /**
  * A SoftwareComponent representing a library that runs on a java virtual machine.
  */
 public class JavaLibrary implements SoftwareComponentInternal {
+    private final Usage runtimeUsage = new RuntimeUsage();
+    private final LinkedHashSet<PublishArtifact> artifacts = new LinkedHashSet<PublishArtifact>();
+    private final DependencySet runtimeDependencies;
 
-    private final Configuration runtimeConfiguration;
-
-    public JavaLibrary(Configuration runtimeConfiguration) {
-        this.runtimeConfiguration = runtimeConfiguration;
+    public JavaLibrary(PublishArtifact jarArtifact, DependencySet runtimeDependencies) {
+        artifacts.add(jarArtifact);
+        this.runtimeDependencies = runtimeDependencies;
     }
 
     public String getName() {
         return "java";
     }
 
-    public PublishArtifactSet getArtifacts() {
-        return runtimeConfiguration.getAllArtifacts();
+    public Set<Usage> getUsages() {
+        return Collections.singleton(runtimeUsage);
     }
 
-    public DependencySet getRuntimeDependencies() {
-        return runtimeConfiguration.getAllDependencies();
+    private class RuntimeUsage implements Usage {
+        public String getName() {
+            return "runtime";
+        }
+
+        public Set<PublishArtifact> getArtifacts() {
+            return artifacts;
+        }
+
+        public Set<ModuleDependency> getDependencies() {
+            return runtimeDependencies.withType(ModuleDependency.class);
+        }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/WebApplication.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/WebApplication.java
new file mode 100644
index 0000000..55d1647
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/java/WebApplication.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.java;
+
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.component.SoftwareComponentInternal;
+import org.gradle.api.internal.component.Usage;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class WebApplication implements SoftwareComponentInternal {
+
+    private final Usage webArchiveUsage = new WebArchiveUsage();
+    private final PublishArtifact warArtifact;
+
+    public WebApplication(PublishArtifact warArtifact) {
+        this.warArtifact = warArtifact;
+    }
+
+    public String getName() {
+        return "web";
+    }
+
+    public Set<Usage> getUsages() {
+        return Collections.singleton(webArchiveUsage);
+    }
+
+    private class WebArchiveUsage implements Usage {
+        public String getName() {
+            return "master"; // TODO:DAZ Maybe come up with a better name
+        }
+
+        public Set<PublishArtifact> getArtifacts() {
+            return Collections.singleton(warArtifact);
+        }
+
+        public Set<ModuleDependency> getDependencies() {
+            return Collections.emptySet();
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultBinariesContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultBinariesContainer.java
new file mode 100644
index 0000000..45f690a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultBinariesContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.Named;
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.tasks.BinariesContainer;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultBinariesContainer extends DefaultNamedDomainObjectSet<Named> implements BinariesContainer {
+    public DefaultBinariesContainer(Instantiator instantiator) {
+        super(Named.class, instantiator);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClassDirectoryBinary.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClassDirectoryBinary.java
new file mode 100644
index 0000000..6090b1c
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClassDirectoryBinary.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.Nullable;
+import org.gradle.api.Task;
+import org.gradle.api.internal.DefaultDomainObjectSet;
+import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+
+public class DefaultClassDirectoryBinary implements ClassDirectoryBinary {
+    private final String name;
+    private File classesDir;
+    private File resourcesDir;
+    private final DomainObjectCollection<LanguageSourceSet> source = new DefaultDomainObjectSet<LanguageSourceSet>(LanguageSourceSet.class);
+    private Task classesTask;
+    private Copy resourcesTask;
+    private AbstractCompile compileTask;
+
+    public DefaultClassDirectoryBinary(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public File getClassesDir() {
+        return classesDir;
+    }
+
+    public void setClassesDir(File classesDir) {
+        this.classesDir = classesDir;
+    }
+
+    public File getResourcesDir() {
+        return resourcesDir;
+    }
+
+    public void setResourcesDir(File resourcesDir) {
+        this.resourcesDir = resourcesDir;
+    }
+
+    public DomainObjectCollection<LanguageSourceSet> getSource() {
+        return source;
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return null;  //TODO
+    }
+
+    public Task getClassesTask() {
+        return classesTask;
+    }
+
+    public void setClassesTask(Task classesTask) {
+        this.classesTask = classesTask;
+    }
+
+    @Nullable
+    public Copy getResourcesTask() {
+        return resourcesTask;
+    }
+
+    public void setResourcesTask(Copy resourcesTask) {
+        this.resourcesTask = resourcesTask;
+    }
+
+    @Nullable
+    public AbstractCompile getCompileTask() {
+        return compileTask;
+    }
+
+    public void setCompileTask(AbstractCompile compileTask) {
+        this.compileTask = compileTask;
+    }
+
+    public String getTaskName(@Nullable String verb, @Nullable String target) {
+        if (verb == null) {
+            return StringUtils.uncapitalize(String.format("%s%s", getTaskBaseName(), StringUtils.capitalize(target)));
+        }
+        if (target == null) {
+            return StringUtils.uncapitalize(String.format("%s%s", verb, GUtil.toCamelCase(name)));
+        }
+        return StringUtils.uncapitalize(String.format("%s%s%s", verb, getTaskBaseName(), StringUtils.capitalize(target)));
+    }
+
+    public String getTaskBaseName() {
+        return name.equals("main") ? "" : GUtil.toCamelCase(name);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClasspath.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClasspath.java
new file mode 100644
index 0000000..44e0fb8
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultClasspath.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection;
+import org.gradle.api.tasks.Classpath;
+import org.gradle.api.tasks.TaskDependency;
+
+public class DefaultClasspath implements Classpath {
+    private final FileCollection files;
+
+    public DefaultClasspath(FileResolver fileResolver, TaskResolver taskResolver) {
+        files = new DefaultConfigurableFileCollection(fileResolver, taskResolver);
+    }
+
+    public FileCollection getFiles() {
+        return files;
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return files.getBuildDependencies();
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultFunctionalSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultFunctionalSourceSet.java
new file mode 100644
index 0000000..08fcbbe
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultFunctionalSourceSet.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.tasks.FunctionalSourceSet;
+import org.gradle.api.tasks.LanguageSourceSet;
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultFunctionalSourceSet extends DefaultPolymorphicDomainObjectContainer<LanguageSourceSet> implements FunctionalSourceSet {
+    private final String name;
+
+    public DefaultFunctionalSourceSet(String name, Instantiator instantiator) {
+        super(LanguageSourceSet.class, instantiator);
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJavaSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJavaSourceSet.java
new file mode 100644
index 0000000..baf13a9
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJavaSourceSet.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.Task;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.tasks.Classpath;
+import org.gradle.api.tasks.FunctionalSourceSet;
+import org.gradle.api.tasks.JavaSourceSet;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class DefaultJavaSourceSet implements JavaSourceSet {
+    private final String name;
+    private final SourceDirectorySet source;
+    private final Classpath compileClasspath;
+    private final FunctionalSourceSet parent;
+
+    public DefaultJavaSourceSet(String name, SourceDirectorySet source, Classpath compileClasspath, FunctionalSourceSet parent) {
+        this.name = name;
+        this.source = source;
+        this.compileClasspath = compileClasspath;
+        this.parent = parent;
+    }
+
+    public Classpath getCompileClasspath() {
+        return compileClasspath;
+    }
+
+    public SourceDirectorySet getSource() {
+        return source;
+    }
+
+    public FunctionalSourceSet getParent() {
+        return parent;
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return new TaskDependency() {
+            public Set<? extends Task> getDependencies(Task task) {
+                Set<Task> dependencies = new HashSet<Task>();
+                dependencies.addAll(compileClasspath.getBuildDependencies().getDependencies(task));
+                dependencies.addAll(source.getBuildDependencies().getDependencies(task));
+                return dependencies;
+            }
+        };
+    }
+
+    public String getName() {
+        return name;
+    }
+
+
+    public String toString() {
+        return String.format("%s/%s source set", parent.getName(), name);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJvmBinaryContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJvmBinaryContainer.java
new file mode 100644
index 0000000..bc51285
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultJvmBinaryContainer.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.internal.DefaultPolymorphicDomainObjectContainer;
+import org.gradle.api.tasks.ClassDirectoryBinary;
+import org.gradle.api.tasks.JvmBinaryContainer;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultJvmBinaryContainer extends DefaultPolymorphicDomainObjectContainer<ClassDirectoryBinary> implements JvmBinaryContainer {
+    public DefaultJvmBinaryContainer(Instantiator instantiator) {
+        super(ClassDirectoryBinary.class, instantiator);
+    }
+
+    public String getName() {
+        return "jvm";
+    }
+
+    @Override
+    protected ClassDirectoryBinary doCreate(String name) {
+        return getInstantiator().newInstance(DefaultClassDirectoryBinary.class, name);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultProjectSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultProjectSourceSet.java
new file mode 100644
index 0000000..a340827
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultProjectSourceSet.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.internal.AbstractNamedDomainObjectContainer;
+import org.gradle.api.tasks.FunctionalSourceSet;
+import org.gradle.api.tasks.ProjectSourceSet;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultProjectSourceSet extends AbstractNamedDomainObjectContainer<FunctionalSourceSet> implements ProjectSourceSet {
+    public DefaultProjectSourceSet(Instantiator instantiator) {
+        super(FunctionalSourceSet.class, instantiator);
+    }
+
+    @Override
+    protected FunctionalSourceSet doCreate(String name) {
+        return getInstantiator().newInstance(DefaultFunctionalSourceSet.class, name, getInstantiator());
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultResourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultResourceSet.java
new file mode 100644
index 0000000..912f623
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultResourceSet.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.tasks.FunctionalSourceSet;
+import org.gradle.api.tasks.ResourceSet;
+import org.gradle.api.tasks.TaskDependency;
+
+public class DefaultResourceSet implements ResourceSet {
+    private final String name;
+    private final SourceDirectorySet source;
+    private final FunctionalSourceSet parent;
+
+    public DefaultResourceSet(String name, SourceDirectorySet source, FunctionalSourceSet parent) {
+        this.name = name;
+        this.source = source;
+        this.parent = parent;
+    }
+
+    public SourceDirectorySet getSource() {
+        return source;
+    }
+
+    public FunctionalSourceSet getParent() {
+        return parent;
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return source.getBuildDependencies();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String toString() {
+        return String.format("%s/%s source set", parent.getName(), name);
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/SourceSetCompileClasspath.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/SourceSetCompileClasspath.java
new file mode 100644
index 0000000..323f9c6
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/SourceSetCompileClasspath.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.*;
+
+public class SourceSetCompileClasspath implements Classpath {
+    private final org.gradle.api.tasks.SourceSet sourceSet;
+
+    public SourceSetCompileClasspath(org.gradle.api.tasks.SourceSet sourceSet) {
+        this.sourceSet = sourceSet;
+    }
+
+    public FileCollection getFiles() {
+        return sourceSet.getCompileClasspath();
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return sourceSet.getCompileClasspath().getBuildDependencies();
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java
index 99c2af6..b779a83 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/daemon/CompilerDaemonManager.java
@@ -22,6 +22,7 @@ import org.gradle.BuildResult;
 import org.gradle.api.internal.project.ProjectInternal;
 import org.gradle.api.logging.Logger;
 import org.gradle.api.logging.Logging;
+import org.gradle.internal.CompositeStoppable;
 import org.gradle.internal.jvm.Jvm;
 import org.gradle.process.internal.JavaExecHandleBuilder;
 import org.gradle.process.internal.WorkerProcess;
@@ -63,9 +64,7 @@ public class CompilerDaemonManager implements CompilerDaemonFactory {
 
     public synchronized void stop() {
         LOGGER.info("Stopping {} Gradle compiler daemon(s).", clients.size());
-        for (CompilerDaemonClient client : clients) {
-            client.stop();
-        }
+        CompositeStoppable.stoppable(clients).stop();
         LOGGER.info("Stopped {} Gradle compiler daemon(s).", clients.size());
         clients.clear();
     }
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 b580d47..e06bf88 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
@@ -41,7 +41,7 @@ public class DecoratingTestDescriptor implements TestDescriptorInternal {
     }
 
     public String getClassName() {
-        return findClassName();
+        return descriptor.getClassName();
     }
 
     public String getName() {
@@ -51,14 +51,4 @@ public class DecoratingTestDescriptor implements TestDescriptorInternal {
     public boolean isComposite() {
         return descriptor.isComposite();
     }
-
-    private String findClassName() {
-        String className;
-        if (parent != null && parent.getName().equals(parent.getClassName())) {
-            className = parent.getName();
-        } else {
-            className = descriptor.getClassName();
-        }
-        return className;
-    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/IgnoredTestDescriptorProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/IgnoredTestDescriptorProvider.java
new file mode 100644
index 0000000..5cb6259
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/IgnoredTestDescriptorProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit;
+
+import org.gradle.api.internal.tasks.testing.TestSuiteExecutionException;
+import org.junit.runner.Description;
+import org.junit.runner.Request;
+import org.junit.runner.Runner;
+
+import java.util.List;
+
+public class IgnoredTestDescriptorProvider {
+    List<Description> getAllDescriptions(Description description, String className) {
+        final AllExceptIgnoredTestRunnerBuilder allExceptIgnoredTestRunnerBuilder = new AllExceptIgnoredTestRunnerBuilder();
+        try {
+            final Class<?> testClass = description.getClass().getClassLoader().loadClass(className);
+            Runner runner = allExceptIgnoredTestRunnerBuilder.runnerForClass(testClass);
+            if (runner == null) {
+                //fall back to default runner
+                runner = Request.aClass(testClass).getRunner();
+            }
+            final Description runnerDescription = runner.getDescription();
+            return runnerDescription.getChildren();
+        } catch (Throwable throwable) {
+            throw new TestSuiteExecutionException(String.format("Unable to process Ignored class %s.", className), throwable);
+        }
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
index 7b1535c..878ac76 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestEventAdapter.java
@@ -22,14 +22,13 @@ import org.gradle.internal.TimeProvider;
 import org.gradle.internal.concurrent.ThreadSafe;
 import org.gradle.internal.id.IdGenerator;
 import org.junit.runner.Description;
-import org.junit.runner.Request;
-import org.junit.runner.Runner;
 import org.junit.runner.notification.Failure;
 import org.junit.runner.notification.RunListener;
-import org.slf4j.LoggerFactory;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -39,6 +38,7 @@ public class JUnitTestEventAdapter extends RunListener {
     private final IdGenerator<?> idGenerator;
     private final Object lock = new Object();
     private final Map<Description, TestDescriptorInternal> executing = new HashMap<Description, TestDescriptorInternal>();
+    private final Set<Description> assumptionFailed = new HashSet<Description>();
 
     public JUnitTestEventAdapter(TestResultProcessor resultProcessor, TimeProvider timeProvider,
                                  IdGenerator<?> idGenerator) {
@@ -78,6 +78,13 @@ public class JUnitTestEventAdapter extends RunListener {
     }
 
     @Override
+    public void testAssumptionFailure(Failure failure) {
+        synchronized (lock) {
+            assumptionFailed.add(failure.getDescription());
+        }
+    }
+
+    @Override
     public void testIgnored(Description description) throws Exception {
         if (methodName(description) == null) {
             // An @Ignored class, ignore the event. We don't get testIgnored events for each method, so we have
@@ -92,21 +99,10 @@ public class JUnitTestEventAdapter extends RunListener {
     }
 
     private void processIgnoredClass(Description description) throws Exception {
+        IgnoredTestDescriptorProvider provider = new IgnoredTestDescriptorProvider();
         String className = className(description);
-        final AllExceptIgnoredTestRunnerBuilder allExceptIgnoredTestRunnerBuilder = new AllExceptIgnoredTestRunnerBuilder();
-        try {
-            final Class<?> testClass = description.getClass().getClassLoader().loadClass(className);
-            Runner runner = allExceptIgnoredTestRunnerBuilder.runnerForClass(testClass);
-            if (runner == null) {
-                //fall back to default runner
-                runner = Request.aClass(testClass).getRunner();
-            }
-            final Description runnerDescription = runner.getDescription();
-            for (Description childrenDescription : runnerDescription.getChildren()) {
-                testIgnored(childrenDescription);
-            }
-        } catch (Throwable throwable) {
-            LoggerFactory.getLogger(getClass()).warn("Unable to process IgnoredClass", throwable);
+        for (Description childDescription : provider.getAllDescriptions(description, className)) {
+            testIgnored(childDescription);
         }
     }
 
@@ -114,6 +110,7 @@ public class JUnitTestEventAdapter extends RunListener {
     public void testFinished(Description description) throws Exception {
         long endTime = timeProvider.getCurrentTime();
         TestDescriptorInternal testInternal;
+        TestResult.ResultType resultType;
         synchronized (lock) {
             testInternal = executing.remove(description);
             if (testInternal == null && executing.size() == 1) {
@@ -122,8 +119,9 @@ public class JUnitTestEventAdapter extends RunListener {
                 executing.clear();
             }
             assert testInternal != null : String.format("Unexpected end event for %s", description);
+            resultType = assumptionFailed.remove(description) ? TestResult.ResultType.SKIPPED : null;
         }
-        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(endTime));
+        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(endTime, resultType));
     }
 
     private TestStartEvent startEvent() {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
index 83d8ec2..13320db 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/ClassPageRenderer.java
@@ -15,14 +15,13 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
-import org.gradle.api.Action;
+import org.gradle.api.internal.ErroringAction;
 import org.gradle.api.internal.tasks.testing.junit.result.TestResultsProvider;
 import org.gradle.api.tasks.testing.TestOutputEvent;
 import org.gradle.reporting.CodePanelRenderer;
-import org.w3c.dom.Element;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
+import java.io.IOException;
 
 class ClassPageRenderer extends PageRenderer<ClassTestResults> {
     private final CodePanelRenderer codePanelRenderer = new CodePanelRenderer();
@@ -35,81 +34,81 @@ class ClassPageRenderer extends PageRenderer<ClassTestResults> {
     }
 
     @Override
-    protected void renderBreadcrumbs(Element parent) {
-        Element div = append(parent, "div");
-        div.setAttribute("class", "breadcrumbs");
-        appendLink(div, "index.html", "all");
-        appendText(div, " > ");
-        appendLink(div, String.format("%s.html", getResults().getPackageResults().getName()), getResults().getPackageResults().getName());
-        appendText(div, String.format(" > %s", getResults().getSimpleName()));
+    protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException {
+        htmlWriter.startElement("div").attribute("class", "breadcrumbs")
+            .startElement("a").attribute("href", "index.html").characters("all").endElement()
+            .characters(" > ")
+            .startElement("a").attribute("href", String.format("%s.html", getResults().getPackageResults().getName())).characters(getResults().getPackageResults().getName()).endElement()
+            .characters(String.format(" > %s", getResults().getSimpleName()))
+        .endElement();
     }
 
-    private void renderTests(Element parent) {
-        Element table = append(parent, "table");
-        Element thead = append(table, "thead");
-        Element tr = append(thead, "tr");
-        appendWithText(tr, "th", "Test");
-        appendWithText(tr, "th", "Duration");
-        appendWithText(tr, "th", "Result");
+    private void renderTests(SimpleHtmlWriter htmlWriter) throws IOException {
+        htmlWriter.startElement("table")
+            .startElement("thead")
+                .startElement("tr")
+                    .startElement("th").characters("Test").endElement()
+                    .startElement("th").characters("Duration").endElement()
+                    .startElement("th").characters("Result").endElement()
+                .endElement()
+        .endElement();
+
         for (TestResult test : getResults().getTestResults()) {
-            tr = append(table, "tr");
-            Element td = appendWithText(tr, "td", test.getName());
-            td.setAttribute("class", test.getStatusClass());
-            appendWithText(tr, "td", test.getFormattedDuration());
-            td = appendWithText(tr, "td", test.getFormattedResultType());
-            td.setAttribute("class", test.getStatusClass());
+            htmlWriter.startElement("tr")
+                .startElement("td").attribute("class", test.getStatusClass()).characters(test.getName()).endElement()
+                .startElement("td").characters(test.getFormattedDuration()).endElement()
+                .startElement("td").attribute("class", test.getStatusClass()).characters(test.getFormattedResultType()).endElement()
+            .endElement();
         }
+        htmlWriter.endElement();
     }
 
-    @Override protected void renderFailures(Element parent) {
+    @Override
+    protected void renderFailures(SimpleHtmlWriter htmlWriter) throws IOException {
         for (TestResult test : getResults().getFailures()) {
-            Element div = append(parent, "div");
-            div.setAttribute("class", "test");
-            append(div, "a").setAttribute("name", test.getId().toString());
-            appendWithText(div, "h3", test.getName()).setAttribute("class", test.getStatusClass());
+            htmlWriter.startElement("div").attribute("class", "test")
+                .startElement("a").attribute("name", test.getId().toString()).characters("").endElement() //browsers dont understand <a name="..."/>
+                .startElement("h3").attribute("class", test.getStatusClass()).characters(test.getName()).endElement();
             for (TestFailure failure : test.getFailures()) {
-                codePanelRenderer.render(failure.getStackTrace(), div);
+                codePanelRenderer.render(failure.getStackTrace(), htmlWriter);
             }
+            htmlWriter.endElement();
         }
     }
 
-    private void renderStd(Element parent, String stdString) {
-        codePanelRenderer.render(stdString, parent);
-    }
-
-    @Override protected void registerTabs() {
+    @Override
+    protected void registerTabs() {
         addFailuresTab();
-        addTab("Tests", new Action<Element>() {
-            public void execute(Element element) {
-                renderTests(element);
+        addTab("Tests", new ErroringAction<SimpleHtmlWriter>() {
+            public void doExecute(SimpleHtmlWriter writer) throws IOException {
+                renderTests(writer);
             }
         });
-        final String stdOut = getOutputString(TestOutputEvent.Destination.StdOut);
-        if (stdOut.length() > 0) {
-            addTab("Standard output", new Action<Element>() {
-                public void execute(Element element) {
-                    renderStd(element, stdOut);
+        if (resultsProvider.hasOutput(className, TestOutputEvent.Destination.StdOut)) {
+            addTab("Standard output", new ErroringAction<SimpleHtmlWriter>() {
+                @Override
+                protected void doExecute(SimpleHtmlWriter htmlWriter) throws IOException {
+                    htmlWriter.startElement("span").attribute("class", "code")
+                        .startElement("pre")
+                        .characters("");
+                    resultsProvider.writeOutputs(className, TestOutputEvent.Destination.StdOut, htmlWriter);
+                        htmlWriter.endElement()
+                    .endElement();
                 }
             });
         }
-        final String stdErr = getOutputString(TestOutputEvent.Destination.StdErr);
-        if (stdErr.length() > 0) {
-            addTab("Standard error", new Action<Element>() {
-                public void execute(Element element) {
-                    renderStd(element, stdErr);
+        if (resultsProvider.hasOutput(className, TestOutputEvent.Destination.StdErr)) {
+            addTab("Standard error", new ErroringAction<SimpleHtmlWriter>() {
+                @Override
+                protected void doExecute(SimpleHtmlWriter element) throws Exception {
+                    element.startElement("span").attribute("class", "code")
+                    .startElement("pre")
+                        .characters("");
+                    resultsProvider.writeOutputs(className, TestOutputEvent.Destination.StdErr, element);
+                    element.endElement()
+                    .endElement();
                 }
             });
         }
     }
-
-    /**
-     * @TODO RG: This method can consume a lot of memory depending on the amount of output We'll when moving away from dom based report generation
-     */
-    private String getOutputString(TestOutputEvent.Destination destination) {
-        final StringWriter stringWriter = new StringWriter();
-        PrintWriter writer = new PrintWriter(stringWriter);
-        resultsProvider.writeOutputs(className, destination, writer);
-        writer.close();
-        return stringWriter.toString();
-    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
index 918cb86..46aeb74 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReport.java
@@ -54,6 +54,7 @@ public class DefaultTestReport implements TestReporter {
         final AllTestResults model = new AllTestResults();
         resultsProvider.visitClasses(new Action<TestClassResult>() {
             public void execute(TestClassResult classResult) {
+                model.addTestClass(classResult.getClassName());
                 List<TestMethodResult> collectedResults = classResult.getResults();
                 for (TestMethodResult collectedResult : collectedResults) {
                     final TestResult testResult = model.addTest(classResult.getClassName(), collectedResult.getName(), collectedResult.getDuration());
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/OverviewPageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/OverviewPageRenderer.java
index 368f195..69b9355 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/OverviewPageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/OverviewPageRenderer.java
@@ -15,73 +15,88 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
-import org.gradle.api.Action;
-import org.w3c.dom.Element;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+
+import java.io.IOException;
 
 class OverviewPageRenderer extends PageRenderer<AllTestResults> {
 
-    @Override protected void registerTabs() {
+    @Override
+    protected void registerTabs() {
         addFailuresTab();
         if (!getResults().getPackages().isEmpty()) {
-            addTab("Packages", new Action<Element>() {
-                public void execute(Element element) {
-                    renderPackages(element);
+            addTab("Packages", new ErroringAction<SimpleHtmlWriter>() {
+                @Override
+                protected void doExecute(SimpleHtmlWriter writer) throws IOException {
+                    renderPackages(writer);
                 }
             });
         }
-        addTab("Classes", new Action<Element>() {
-            public void execute(Element element) {
-                renderClasses(element);
+        addTab("Classes", new ErroringAction<SimpleHtmlWriter>() {
+            public void doExecute(SimpleHtmlWriter htmlWriter) throws IOException {
+                renderClasses(htmlWriter);
             }
         });
     }
 
-    @Override protected void renderBreadcrumbs(Element element) {
+    @Override
+    protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) {
     }
 
-    private void renderPackages(Element parent) {
-        Element table = append(parent, "table");
-        Element thead = append(table, "thead");
-        Element tr = append(thead, "tr");
-        appendWithText(tr, "th", "Package");
-        appendWithText(tr, "th", "Tests");
-        appendWithText(tr, "th", "Failures");
-        appendWithText(tr, "th", "Duration");
-        appendWithText(tr, "th", "Success rate");
+    private void renderPackages(SimpleHtmlWriter htmlWriter) throws IOException {
+        htmlWriter.startElement("table");
+        htmlWriter.startElement("thead");
+        htmlWriter.startElement("tr");
+        htmlWriter.startElement("th").characters("Package").endElement();
+        htmlWriter.startElement("th").characters("Tests").endElement();
+        htmlWriter.startElement("th").characters("Failures").endElement();
+        htmlWriter.startElement("th").characters("Duration").endElement();
+        htmlWriter.startElement("th").characters("Success rate").endElement();
+        htmlWriter.endElement();
+        htmlWriter.endElement();
+        htmlWriter.startElement("tbody");
         for (PackageTestResults testPackage : getResults().getPackages()) {
-            tr = append(table, "tr");
-            Element td = append(tr, "td");
-            td.setAttribute("class", testPackage.getStatusClass());
-            appendLink(td, String.format("%s.html", testPackage.getName()), testPackage.getName());
-            appendWithText(tr, "td", testPackage.getTestCount());
-            appendWithText(tr, "td", testPackage.getFailureCount());
-            appendWithText(tr, "td", testPackage.getFormattedDuration());
-            td = appendWithText(tr, "td", testPackage.getFormattedSuccessRate());
-            td.setAttribute("class", testPackage.getStatusClass());
+            htmlWriter.startElement("tr");
+            htmlWriter.startElement("td").attribute("class", testPackage.getStatusClass());
+            htmlWriter.startElement("a").attribute("href", String.format("%s.html", testPackage.getName())).characters(testPackage.getName()).endElement();
+            htmlWriter.endElement();
+            htmlWriter.startElement("td").characters(Integer.toString(testPackage.getTestCount())).endElement();
+            htmlWriter.startElement("td").characters(Integer.toString(testPackage.getFailureCount())).endElement();
+            htmlWriter.startElement("td").characters(testPackage.getFormattedDuration()).endElement();
+            htmlWriter.startElement("td").attribute("class", testPackage.getStatusClass()).characters(testPackage.getFormattedDuration()).endElement();
+            htmlWriter.endElement();
         }
+        htmlWriter.endElement();
+        htmlWriter.endElement();
     }
 
-    private void renderClasses(Element parent) {
-        Element table = append(parent, "table");
-        Element thead = append(table, "thead");
-        Element tr = append(thead, "tr");
-        appendWithText(tr, "th", "Class");
-        appendWithText(tr, "th", "Tests");
-        appendWithText(tr, "th", "Failures");
-        appendWithText(tr, "th", "Duration");
-        appendWithText(tr, "th", "Success rate");
+    private void renderClasses(SimpleHtmlWriter htmlWriter) throws IOException {
+        htmlWriter.startElement("table");
+        htmlWriter.startElement("thead");
+        htmlWriter.startElement("tr");
+        htmlWriter.startElement("th").characters("Class").endElement();
+        htmlWriter.startElement("th").characters("Tests").endElement();
+        htmlWriter.startElement("th").characters("Failures").endElement();
+        htmlWriter.startElement("th").characters("Duration").endElement();
+        htmlWriter.startElement("th").characters("Success rate").endElement();
+        htmlWriter.endElement();
+        htmlWriter.endElement();
+        htmlWriter.startElement("tbody");
+
         for (PackageTestResults testPackage : getResults().getPackages()) {
             for (ClassTestResults testClass : testPackage.getClasses()) {
-                tr = append(table, "tr");
-                Element td = append(tr, "td");
-                td.setAttribute("class", testClass.getStatusClass());
-                appendLink(td, String.format("%s.html", testClass.getName()), testClass.getName());
-                appendWithText(tr, "td", testClass.getTestCount());
-                appendWithText(tr, "td", testClass.getFailureCount());
-                appendWithText(tr, "td", testClass.getFormattedDuration());
-                td = appendWithText(tr, "td", testClass.getFormattedSuccessRate());
-                td.setAttribute("class", testClass.getStatusClass());
+                htmlWriter.startElement("tr");
+                htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).endElement();
+                htmlWriter.startElement("a").attribute("href", String.format("%s.html", testClass.getName())).characters(testClass.getName()).endElement();
+                htmlWriter.startElement("td").characters(Integer.toString(testClass.getTestCount())).endElement();
+                htmlWriter.startElement("td").characters(Integer.toString(testClass.getFailureCount())).endElement();
+                htmlWriter.startElement("td").characters(testClass.getFormattedDuration()).endElement();
+                htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).characters(testClass.getFormattedSuccessRate()).endElement();
+                htmlWriter.endElement();
             }
         }
+        htmlWriter.endElement();
+        htmlWriter.endElement();
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackagePageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackagePageRenderer.java
index d527633..707b1dd 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackagePageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PackagePageRenderer.java
@@ -15,45 +15,55 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report;
 
-import org.gradle.api.Action;
-import org.w3c.dom.Element;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+
+import java.io.IOException;
 
 class PackagePageRenderer extends PageRenderer<PackageTestResults> {
 
-    @Override protected void renderBreadcrumbs(Element parent) {
-        Element div = append(parent, "div");
-        div.setAttribute("class", "breadcrumbs");
-        appendLink(div, "index.html", "all");
-        appendText(div, String.format(" > %s", getResults().getName()));
+    @Override
+    protected void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException {
+        htmlWriter.startElement("div").attribute("class", "breadcrumbs");
+        htmlWriter.startElement("a").attribute("href", "index.html").characters("all").endElement();
+        htmlWriter.characters(String.format(" > %s", getResults().getName()));
+        htmlWriter.endElement();
     }
 
-    private void renderClasses(Element parent) {
-        Element table = append(parent, "table");
-        Element thead = append(table, "thead");
-        Element tr = append(thead, "tr");
-        appendWithText(tr, "th", "Class");
-        appendWithText(tr, "th", "Tests");
-        appendWithText(tr, "th", "Failures");
-        appendWithText(tr, "th", "Duration");
-        appendWithText(tr, "th", "Success rate");
+    private void renderClasses(SimpleHtmlWriter htmlWriter) throws IOException {
+        htmlWriter.startElement("table");
+        htmlWriter.startElement("thread");
+        htmlWriter.startElement("tr");
+
+        htmlWriter.startElement("th").characters("Class").endElement();
+        htmlWriter.startElement("th").characters("Tests").endElement();
+        htmlWriter.startElement("th").characters("Failures").endElement();
+        htmlWriter.startElement("th").characters("Duration").endElement();
+        htmlWriter.startElement("th").characters("Success rate").endElement();
+
+        htmlWriter.endElement();
+        htmlWriter.endElement();
+
         for (ClassTestResults testClass : getResults().getClasses()) {
-            tr = append(table, "tr");
-            Element td = append(tr, "td");
-            td.setAttribute("class", testClass.getStatusClass());
-            appendLink(td, String.format("%s.html", testClass.getName()), testClass.getSimpleName());
-            appendWithText(tr, "td", testClass.getTestCount());
-            appendWithText(tr, "td", testClass.getFailureCount());
-            appendWithText(tr, "td", testClass.getFormattedDuration());
-            td = appendWithText(tr, "td", testClass.getFormattedSuccessRate());
-            td.setAttribute("class", testClass.getStatusClass());
+            htmlWriter.startElement("tr");
+            htmlWriter.startElement("td").attribute("class", testClass.getStatusClass());
+                htmlWriter.startElement("a").attribute("href", String.format("%s.html", testClass.getName())).characters(testClass.getSimpleName()).endElement();
+            htmlWriter.endElement();
+            htmlWriter.startElement("td").characters(Integer.toString(testClass.getTestCount())).endElement();
+            htmlWriter.startElement("td").characters(Integer.toString(testClass.getFailureCount())).endElement();
+            htmlWriter.startElement("td").characters(testClass.getFormattedDuration()).endElement();
+            htmlWriter.startElement("td").attribute("class", testClass.getStatusClass()).characters(testClass.getFormattedSuccessRate()).endElement();
+            htmlWriter.endElement();
         }
+        htmlWriter.endElement();
     }
 
-    @Override protected void registerTabs() {
+    @Override
+    protected void registerTabs() {
         addFailuresTab();
-        addTab("Classes", new Action<Element>() {
-            public void execute(Element element) {
-                renderClasses(element);
+        addTab("Classes", new ErroringAction<SimpleHtmlWriter>() {
+            public void doExecute(SimpleHtmlWriter htmlWriter) throws IOException {
+                renderClasses(htmlWriter);
             }
         });
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
index 6620edc..909c4a5 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/report/PageRenderer.java
@@ -16,10 +16,13 @@
 package org.gradle.api.internal.tasks.testing.junit.report;
 
 import org.gradle.api.Action;
-import org.gradle.reporting.DomReportRenderer;
+import org.gradle.api.internal.ErroringAction;
+import org.gradle.api.internal.html.SimpleHtmlWriter;
+import org.gradle.reporting.ReportRenderer;
 import org.gradle.reporting.TabbedPageRenderer;
 import org.gradle.reporting.TabsRenderer;
-import org.w3c.dom.Element;
+
+import java.io.IOException;
 
 abstract class PageRenderer<T extends CompositeTestResults> extends TabbedPageRenderer<T> {
     private T results;
@@ -29,60 +32,43 @@ abstract class PageRenderer<T extends CompositeTestResults> extends TabbedPageRe
         return results;
     }
 
-    protected abstract void renderBreadcrumbs(Element parent);
+    protected abstract void renderBreadcrumbs(SimpleHtmlWriter htmlWriter) throws IOException;
 
     protected abstract void registerTabs();
 
-    protected void addTab(String title, final Action<Element> contentRenderer) {
-        tabsRenderer.add(title, new DomReportRenderer<T>() {
+    protected void addTab(String title, final Action<SimpleHtmlWriter> contentRenderer) {
+        tabsRenderer.add(title, new ReportRenderer<T, SimpleHtmlWriter>() {
             @Override
-            public void render(T model, Element parent) {
-                contentRenderer.execute(parent);
+            public void render(T model, SimpleHtmlWriter writer) {
+                contentRenderer.execute(writer);
             }
         });
     }
 
-    protected void renderTabs(Element element) {
-        tabsRenderer.render(getModel(), element);
+    protected void renderTabs(SimpleHtmlWriter htmlWriter) throws IOException {
+        tabsRenderer.render(getModel(), htmlWriter);
     }
 
     protected void addFailuresTab() {
         if (!results.getFailures().isEmpty()) {
-            addTab("Failed tests", new Action<Element>() {
-                public void execute(Element element) {
+            addTab("Failed tests", new ErroringAction<SimpleHtmlWriter>() {
+                public void doExecute(SimpleHtmlWriter element) throws IOException {
                     renderFailures(element);
                 }
             });
         }
     }
 
-    protected void renderFailures(Element parent) {
-        Element ul = append(parent, "ul");
-        ul.setAttribute("class", "linkList");
+    protected void renderFailures(SimpleHtmlWriter htmlWriter) throws IOException {
+        htmlWriter.startElement("ul").attribute("class", "linkList");
         for (TestResult test : results.getFailures()) {
-            Element li = append(ul, "li");
-            appendLink(li, String.format("%s.html", test.getClassResults().getName()), test.getClassResults().getSimpleName());
-            appendText(li, ".");
-            appendLink(li, String.format("%s.html#%s", test.getClassResults().getName(), test.getName()), test.getName());
+            htmlWriter.startElement("li");
+            htmlWriter.startElement("a").attribute("href", String.format("%s.html", test.getClassResults().getName())).characters(test.getClassResults().getSimpleName()).endElement();
+            htmlWriter.characters(".");
+            htmlWriter.startElement("a").attribute("href", String.format("%s.html#%s", test.getClassResults().getName(), test.getName())).characters(test.getName()).endElement();
+            htmlWriter.endElement();
         }
-    }
-
-    protected Element appendTableAndRow(Element parent) {
-        return append(append(parent, "table"), "tr");
-    }
-
-    protected Element appendCell(Element parent) {
-        return append(append(parent, "td"), "div");
-    }
-
-    protected <T extends TestResultModel> DomReportRenderer<T> withStatus(final DomReportRenderer<T> renderer) {
-        return new DomReportRenderer<T>() {
-            @Override
-            public void render(T model, Element parent) {
-                parent.setAttribute("class", model.getStatusClass());
-                renderer.render(model, parent);
-            }
-        };
+        htmlWriter.endElement();
     }
 
     @Override
@@ -96,60 +82,65 @@ abstract class PageRenderer<T extends CompositeTestResults> extends TabbedPageRe
     }
 
     @Override
-    protected DomReportRenderer<T> getHeaderRenderer() {
-        return new DomReportRenderer<T>() {
+    protected ReportRenderer<T, SimpleHtmlWriter> getHeaderRenderer() {
+        return new ReportRenderer<T, SimpleHtmlWriter>() {
             @Override
-            public void render(T model, Element content) {
+            public void render(T model, SimpleHtmlWriter htmlWriter) throws IOException {
                 PageRenderer.this.results = model;
-                renderBreadcrumbs(content);
+                renderBreadcrumbs(htmlWriter);
 
                 // summary
-                Element summary = appendWithId(content, "div", "summary");
-                Element row = appendTableAndRow(summary);
-                Element group = appendCell(row);
-                group.setAttribute("class", "summaryGroup");
-                Element summaryRow = appendTableAndRow(group);
-
-                Element tests = appendCell(summaryRow);
-                tests.setAttribute("id", "tests");
-                tests.setAttribute("class", "infoBox");
-                Element div = appendWithText(tests, "div", results.getTestCount());
-                div.setAttribute("class", "counter");
-                appendWithText(tests, "p", "tests");
-
-                Element failures = appendCell(summaryRow);
-                failures.setAttribute("id", "failures");
-                failures.setAttribute("class", "infoBox");
-                div = appendWithText(failures, "div", results.getFailureCount());
-                div.setAttribute("class", "counter");
-                appendWithText(failures, "p", "failures");
-
-                Element duration = appendCell(summaryRow);
-                duration.setAttribute("id", "duration");
-                duration.setAttribute("class", "infoBox");
-                div = appendWithText(duration, "div", results.getFormattedDuration());
-                div.setAttribute("class", "counter");
-                appendWithText(duration, "p", "duration");
-
-                Element successRate = appendCell(row);
-                successRate.setAttribute("id", "successRate");
-                successRate.setAttribute("class", String.format("infoBox %s", results.getStatusClass()));
-                div = appendWithText(successRate, "div", results.getFormattedSuccessRate());
-                div.setAttribute("class", "percent");
-                appendWithText(successRate, "p", "successful");
+                htmlWriter.startElement("div").attribute("id", "summary");
+                htmlWriter.startElement("table");
+                htmlWriter.startElement("tr");
+                htmlWriter.startElement("td");
+                htmlWriter.startElement("div").attribute("class", "summaryGroup");
+                htmlWriter.startElement("table");
+                htmlWriter.startElement("tr");
+                htmlWriter.startElement("td");
+                htmlWriter.startElement("div").attribute("class", "infoBox").attribute("id", "tests");
+                htmlWriter.startElement("div").attribute("class", "counter").characters(Integer.toString(results.getTestCount())).endElement();
+                htmlWriter.startElement("p").characters("tests").endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.startElement("td");
+                htmlWriter.startElement("div").attribute("class", "infoBox").attribute("id", "failures");
+                htmlWriter.startElement("div").attribute("class", "counter").characters(Integer.toString(results.getFailureCount())).endElement();
+                htmlWriter.startElement("p").characters("failures").endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.startElement("td");
+                htmlWriter.startElement("div").attribute("class", "infoBox").attribute("id", "duration");
+                htmlWriter.startElement("div").attribute("class", "counter").characters(results.getFormattedDuration()).endElement();
+                htmlWriter.startElement("p").characters("duration").endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.startElement("td");
+                htmlWriter.startElement("div").attribute("class", String.format("infoBox %s", results.getStatusClass())).attribute("id", "successRate");
+                htmlWriter.startElement("div").attribute("class", "percent").characters(results.getFormattedSuccessRate()).endElement();
+                htmlWriter.startElement("p").characters("successful").endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
+                htmlWriter.endElement();
             }
         };
     }
 
     @Override
-    protected DomReportRenderer<T> getContentRenderer() {
-        return new DomReportRenderer<T>() {
+    protected ReportRenderer<T, SimpleHtmlWriter> getContentRenderer() {
+        return new ReportRenderer<T, SimpleHtmlWriter>() {
             @Override
-            public void render(T model, Element content) {
+            public void render(T model, SimpleHtmlWriter htmlWriter) throws IOException {
                 PageRenderer.this.results = model;
                 tabsRenderer.clear();
                 registerTabs();
-                renderTabs(content);
+                renderTabs(htmlWriter);
             }
         };
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AbstractTestResultProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AbstractTestResultProvider.java
deleted file mode 100644
index 9a4f38b..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AbstractTestResultProvider.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.internal.tasks.testing.junit.result;
-
-import org.gradle.api.UncheckedIOException;
-import org.gradle.api.tasks.testing.TestOutputEvent;
-
-import java.io.*;
-
-public abstract class AbstractTestResultProvider implements TestResultsProvider {
-    private final File resultsDir;
-
-    protected AbstractTestResultProvider(File resultsDir) {
-        this.resultsDir = resultsDir;
-    }
-
-    public File getResultsDir() {
-        return resultsDir;
-    }
-
-    protected File outputsFile(String className, TestOutputEvent.Destination destination) {
-        return destination == TestOutputEvent.Destination.StdOut ? standardOutputFile(className) : standardErrorFile(className);
-    }
-
-    private File standardErrorFile(String className) {
-        return new File(resultsDir, className + ".stderr");
-    }
-
-    private File standardOutputFile(String className) {
-        return new File(resultsDir, className + ".stdout");
-    }
-
-    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
-        final File file = outputsFile(className, destination);
-        if (!file.exists()) {
-            return;
-        }
-        try {
-            Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file)), "UTF-8");
-            try {
-                char[] buffer = new char[2048];
-                while (true) {
-                    int read = reader.read(buffer);
-                    if (read < 0) {
-                        return;
-                    }
-                    writer.write(buffer, 0, read);
-                }
-            } finally {
-                reader.close();
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java
index 93b1fa2..f1debb0 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/AggregateTestResultsProvider.java
@@ -52,6 +52,10 @@ public class AggregateTestResultsProvider implements TestResultsProvider {
         }
     }
 
+    public boolean hasOutput(String className, TestOutputEvent.Destination destination) {
+        return classOutputProviders.get(className).hasOutput(className, destination);
+    }
+
     public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
         classOutputProviders.get(className).writeOutputs(className, destination, writer);
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java
index 3c1ccb0..217a4db 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/BinaryResultBackedTestResultsProvider.java
@@ -17,15 +17,30 @@
 package org.gradle.api.internal.tasks.testing.junit.result;
 
 import org.gradle.api.Action;
+import org.gradle.api.tasks.testing.TestOutputEvent;
 
 import java.io.File;
+import java.io.Writer;
+
+public class BinaryResultBackedTestResultsProvider implements TestResultsProvider {
+    private final File resultsDir;
+    private final TestOutputSerializer outputSerializer;
+    private final TestResultSerializer resultSerializer = new TestResultSerializer();
 
-public class BinaryResultBackedTestResultsProvider extends AbstractTestResultProvider {
     public BinaryResultBackedTestResultsProvider(File resultsDir) {
-        super(resultsDir);
+        this.resultsDir = resultsDir;
+        outputSerializer = new TestOutputSerializer(resultsDir);
+    }
+
+    public boolean hasOutput(String className, TestOutputEvent.Destination destination) {
+        return outputSerializer.hasOutput(className, destination);
+    }
+
+    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
+        outputSerializer.writeOutputs(className, destination, writer);
     }
 
     public void visitClasses(final Action<? super TestClassResult> visitor) {
-        new TestResultSerializer().read(getResultsDir(), visitor);
+        resultSerializer.read(resultsDir, visitor);
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java
index b38601b..2d8841a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/CachingFileWriter.java
@@ -25,7 +25,7 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import static org.gradle.internal.CompositeStoppable.closeable;
+import static org.gradle.internal.CompositeStoppable.stoppable;
 
 /**
  * by Szczepan Faber, created at: 11/19/12
@@ -35,10 +35,10 @@ public class CachingFileWriter {
     private final static Logger LOG = Logging.getLogger(CachingFileWriter.class);
 
     final LinkedHashMap<File, Writer> openFiles = new LinkedHashMap<File, Writer>();
-    private final int openFilesCount;
+    private final int maxOpenFiles;
 
-    public CachingFileWriter(int openFilesCount) {
-        this.openFilesCount = openFilesCount;
+    public CachingFileWriter(int maxOpenFiles) {
+        this.maxOpenFiles = maxOpenFiles;
     }
 
     public void write(File file, String text) {
@@ -50,7 +50,7 @@ public class CachingFileWriter {
                 } else {
                     out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(file, true)), "UTF-8");
                     openFiles.put(file, out);
-                    if (openFiles.size() > openFilesCount) {
+                    if (openFiles.size() > maxOpenFiles) {
                         //remove first
                         Iterator<Map.Entry<File, Writer>> iterator = openFiles.entrySet().iterator();
                         close(iterator.next().getValue(), file.toString());
@@ -82,7 +82,7 @@ public class CachingFileWriter {
 
     private void cleanUpQuietly() {
         try {
-            closeable(openFiles.values()).stop();
+            stoppable(openFiles.values()).stop();
         } catch (Exception e) {
             LOG.debug("Problems closing files", e);
         } finally {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializer.java
new file mode 100644
index 0000000..929394f
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializer.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.junit.result;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.tasks.testing.TestOutputEvent;
+
+import java.io.*;
+
+/**
+ * Assembles test results. Keeps a copy of the results in memory to provide them later and spools test output to file.
+ *
+ * by Szczepan Faber, created at: 11/13/12
+ */
+public class TestOutputSerializer {
+    private final File resultsDir;
+    private final CachingFileWriter cachingFileWriter;
+
+    public TestOutputSerializer(File resultsDir) {
+        //TODO SF calculate number of open files based on parallel forks
+        this(resultsDir, new CachingFileWriter(10));
+    }
+
+    private TestOutputSerializer(File resultsDir, CachingFileWriter cachingFileWriter) {
+        this.resultsDir = resultsDir;
+        this.cachingFileWriter = cachingFileWriter;
+    }
+
+    private File outputsFile(String className, TestOutputEvent.Destination destination) {
+        return destination == TestOutputEvent.Destination.StdOut ? standardOutputFile(className) : standardErrorFile(className);
+    }
+
+    private File standardErrorFile(String className) {
+        return new File(resultsDir, className + ".stderr");
+    }
+
+    private File standardOutputFile(String className) {
+        return new File(resultsDir, className + ".stdout");
+    }
+
+    public boolean hasOutput(String className, TestOutputEvent.Destination destination){
+        return outputsFile(className, destination).exists();
+    }
+
+    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
+        final File file = outputsFile(className, destination);
+        if (!file.exists()) {
+            return;
+        }
+        try {
+            Reader reader = new InputStreamReader(new BufferedInputStream(new FileInputStream(file)), "UTF-8");
+            try {
+                char[] buffer = new char[2048];
+                while (true) {
+                    int read = reader.read(buffer);
+                    if (read < 0) {
+                        return;
+                    }
+                    writer.write(buffer, 0, read);
+                }
+            } finally {
+                reader.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void finishOutputs() {
+        cachingFileWriter.closeAll();
+    }
+
+    public void onOutput(String className, TestOutputEvent.Destination destination, String message) {
+        cachingFileWriter.write(outputsFile(className, destination), message);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java
index 82d861a..8b0ab17 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollector.java
@@ -20,6 +20,7 @@ import org.gradle.api.Action;
 import org.gradle.api.tasks.testing.*;
 
 import java.io.File;
+import java.io.Writer;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -28,20 +29,20 @@ import java.util.Map;
  *
  * by Szczepan Faber, created at: 11/13/12
  */
-public class TestReportDataCollector extends AbstractTestResultProvider implements TestListener, TestOutputListener {
+public class TestReportDataCollector implements TestListener, TestOutputListener, TestResultsProvider {
     private final Map<String, TestClassResult> results = new HashMap<String, TestClassResult>();
-    private final TestResultSerializer serializer;
-    private final CachingFileWriter cachingFileWriter;
+    private final TestResultSerializer resultSerializer;
+    private final File resultsDir;
+    private final TestOutputSerializer outputSerializer;
 
     public TestReportDataCollector(File resultsDir) {
-        //TODO SF calculate number of open files based on parallel forks
-        this(resultsDir, new CachingFileWriter(10), new TestResultSerializer());
+        this(resultsDir, new TestOutputSerializer(resultsDir), new TestResultSerializer());
     }
 
-    TestReportDataCollector(File resultsDir, CachingFileWriter cachingFileWriter, TestResultSerializer serializer) {
-        super(resultsDir);
-        this.cachingFileWriter = cachingFileWriter;
-        this.serializer = serializer;
+    TestReportDataCollector(File resultsDir, TestOutputSerializer outputSerializer, TestResultSerializer resultSerializer) {
+        this.resultsDir = resultsDir;
+        this.outputSerializer = outputSerializer;
+        this.resultSerializer = resultSerializer;
     }
 
     public void beforeSuite(TestDescriptor suite) {
@@ -49,13 +50,13 @@ public class TestReportDataCollector extends AbstractTestResultProvider implemen
 
     public void afterSuite(TestDescriptor suite, TestResult result) {
         if (suite.getParent() == null) {
-            cachingFileWriter.closeAll();
+            outputSerializer.finishOutputs();
             writeResults();
         }
     }
 
     private void writeResults() {
-        serializer.write(results.values(), getResultsDir());
+        resultSerializer.write(results.values(), resultsDir);
     }
 
     public void beforeTest(TestDescriptor testDescriptor) {
@@ -81,7 +82,12 @@ public class TestReportDataCollector extends AbstractTestResultProvider implemen
             //we don't have a place for such output in any of the reports so skipping.
             return;
         }
-        cachingFileWriter.write(outputsFile(className, outputEvent.getDestination()), outputEvent.getMessage());
+        TestClassResult classResult = results.get(className);
+        if (classResult == null) {
+            classResult = new TestClassResult(className, 0);
+            results.put(className, classResult);
+        }
+        outputSerializer.onOutput(className, outputEvent.getDestination(), outputEvent.getMessage());
     }
 
     public void visitClasses(Action<? super TestClassResult> visitor) {
@@ -89,4 +95,12 @@ public class TestReportDataCollector extends AbstractTestResultProvider implemen
             visitor.execute(classResult);
         }
     }
+
+    public boolean hasOutput(String className, TestOutputEvent.Destination destination) {
+        return outputSerializer.hasOutput(className, destination);
+    }
+
+    public void writeOutputs(String className, TestOutputEvent.Destination destination, Writer writer) {
+        outputSerializer.writeOutputs(className, destination, writer);
+    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java
index e98d04f..9fc3550 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestResultsProvider.java
@@ -34,4 +34,6 @@ public interface TestResultsProvider {
      * Visits the results of each test class, in no specific order. Each class is visited exactly once.
      */
     void visitClasses(Action<? super TestClassResult> visitor);
+
+    boolean hasOutput(String className, TestOutputEvent.Destination destination);
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGSpec.java
new file mode 100644
index 0000000..d424dfd
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGSpec.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.internal.tasks.testing.testng;
+
+import org.gradle.api.tasks.testing.testng.TestNGOptions;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+public class TestNGSpec implements Serializable {
+    private static final long serialVersionUID = 1;
+
+    private final String defaultSuiteName;
+    private final String defaultTestName;
+    private final String parallel;
+    private final int threadCount;
+    private final String annotations;
+    private final boolean javadocAnnotations;
+    private final List testResources;
+    private final boolean useDefaultListener;
+    private final Set<String> includeGroups;
+    private final Set<String> excludeGroups;
+    private final Set<String> listeners;
+
+    public TestNGSpec(TestNGOptions options) {
+        this.defaultSuiteName = options.getSuiteName();
+        this.defaultTestName = options.getTestName();
+        this.parallel = options.getParallel();
+        this.threadCount = options.getThreadCount();
+        this.annotations = options.getAnnotations();
+        this.javadocAnnotations = options.getJavadocAnnotations();
+        this.testResources = options.getTestResources();
+        this.useDefaultListener = options.getUseDefaultListeners();
+        this.includeGroups = options.getIncludeGroups();
+        this.excludeGroups = options.getExcludeGroups();
+        this.listeners = options.getListeners();
+    }
+
+    public Set<String> getListeners() {
+        return listeners;
+    }
+
+    public Set<String> getExcludeGroups() {
+        return excludeGroups;
+    }
+
+    public Set<String> getIncludeGroups() {
+        return includeGroups;
+    }
+
+    public boolean getUseDefaultListeners() {
+        return useDefaultListener;
+    }
+
+    public List getTestResources() {
+        return testResources;
+    }
+
+    public boolean getJavadocAnnotations() {
+        return javadocAnnotations;
+    }
+
+    public String getAnnotations() {
+        return annotations;
+    }
+
+    public int getThreadCount() {
+        return threadCount;
+    }
+
+    public String getParallel() {
+        return parallel;
+    }
+
+    public String getDefaultTestName() {
+        return defaultTestName;
+    }
+
+    public String getDefaultSuiteName() {
+        return defaultSuiteName;
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
index bc90fb9..ad884ec 100755
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
@@ -16,13 +16,11 @@
 
 package org.gradle.api.internal.tasks.testing.testng;
 
-import groovy.lang.MissingMethodException;
 import org.gradle.api.GradleException;
 import org.gradle.api.internal.tasks.testing.TestClassProcessor;
 import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
 import org.gradle.api.internal.tasks.testing.TestResultProcessor;
 import org.gradle.api.internal.tasks.testing.processors.CaptureTestOutputTestResultProcessor;
-import org.gradle.api.tasks.testing.testng.TestNGOptions;
 import org.gradle.internal.id.IdGenerator;
 import org.gradle.logging.StandardOutputRedirector;
 import org.gradle.util.CollectionUtils;
@@ -32,28 +30,27 @@ import org.testng.ITestListener;
 import org.testng.TestNG;
 
 import java.io.File;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
 public class TestNGTestClassProcessor implements TestClassProcessor {
     private final List<Class<?>> testClasses = new ArrayList<Class<?>>();
     private final File testReportDir;
-    private final TestNGOptions options;
+    private final TestNGSpec options;
     private final List<File> suiteFiles;
     private final IdGenerator<?> idGenerator;
     private final StandardOutputRedirector outputRedirector;
-    private final boolean testReportOn;
     private TestNGTestResultProcessorAdapter testResultProcessor;
     private ClassLoader applicationClassLoader;
 
-    public TestNGTestClassProcessor(File testReportDir, TestNGOptions options, List<File> suiteFiles, IdGenerator<?> idGenerator,
-                                    StandardOutputRedirector outputRedirector, boolean testReportOn) {
+    public TestNGTestClassProcessor(File testReportDir, TestNGSpec options, List<File> suiteFiles, IdGenerator<?> idGenerator,
+                                    StandardOutputRedirector outputRedirector) {
         this.testReportDir = testReportDir;
         this.options = options;
         this.suiteFiles = suiteFiles;
         this.idGenerator = idGenerator;
         this.outputRedirector = outputRedirector;
-        this.testReportOn = testReportOn;
     }
 
     public void startProcessing(TestResultProcessor resultProcessor) {
@@ -75,14 +72,18 @@ public class TestNGTestClassProcessor implements TestClassProcessor {
     public void stop() {
         TestNG testNg = new TestNG();
         testNg.setOutputDirectory(testReportDir.getAbsolutePath());
-        testNg.setDefaultSuiteName(options.getSuiteName());
-        testNg.setDefaultTestName(options.getTestName());
+        testNg.setDefaultSuiteName(options.getDefaultSuiteName());
+        testNg.setDefaultTestName(options.getDefaultTestName());
         testNg.setParallel(options.getParallel());
         testNg.setThreadCount(options.getThreadCount());
         try {
+            final Method setAnnotations = TestNG.class.getMethod("setAnnotations");
+            setAnnotations.invoke(testNg, options.getAnnotations());
             ReflectionUtil.invoke(testNg, "setAnnotations", options.getAnnotations());
-        } catch (MissingMethodException e) {
+        } catch (NoSuchMethodException e) {
             /* do nothing; method has been removed in TestNG 6.3 */
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not configure TestNG annotations with value '%s'.", options.getAnnotations()), e);
         }
         if (options.getJavadocAnnotations()) {
             testNg.setSourcePath(CollectionUtils.join(File.pathSeparator, options.getTestResources()));
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
index 6a8f279..af43966 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
@@ -51,8 +51,7 @@ public class TestNGTestFramework implements TestFramework {
     public WorkerTestClassProcessorFactory getProcessorFactory() {
         options.setTestResources(testTask.getTestSrcDirs());
         List<File> suiteFiles = options.getSuites(testTask.getTemporaryDir());
-        return new TestClassProcessorFactoryImpl(testTask.getTestReportDir(), options, suiteFiles,
-                testTask.isTestReport());
+        return new TestClassProcessorFactoryImpl(testTask.getTestReportDir(), new TestNGSpec(options), suiteFiles);
     }
 
     public Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
@@ -77,21 +76,18 @@ public class TestNGTestFramework implements TestFramework {
 
     private static class TestClassProcessorFactoryImpl implements WorkerTestClassProcessorFactory, Serializable {
         private final File testReportDir;
-        private final TestNGOptions options;
+        private final TestNGSpec options;
         private final List<File> suiteFiles;
-        private final boolean testReportOn;
 
-        public TestClassProcessorFactoryImpl(File testReportDir, TestNGOptions options, List<File> suiteFiles,
-                                             boolean testReportOn) {
+        public TestClassProcessorFactoryImpl(File testReportDir, TestNGSpec options, List<File> suiteFiles) {
             this.testReportDir = testReportDir;
             this.options = options;
             this.suiteFiles = suiteFiles;
-            this.testReportOn = testReportOn;
         }
 
         public TestClassProcessor create(ServiceRegistry serviceRegistry) {
             return new TestNGTestClassProcessor(testReportDir, options, suiteFiles,
-                    serviceRegistry.get(IdGenerator.class), new JULRedirector(), testReportOn);
+                    serviceRegistry.get(IdGenerator.class), new JULRedirector());
         }
     }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java
index 37580c8..381a484 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java
@@ -39,6 +39,7 @@ class TestNGTestMethodDetecter extends MethodVisitor {
         testMethodAnnotations.add("Lorg/testng/annotations/AfterTest;");
         testMethodAnnotations.add("Lorg/testng/annotations/BeforeGroups;");
         testMethodAnnotations.add("Lorg/testng/annotations/AfterGroups;");
+        testMethodAnnotations.add("Lorg/testng/annotations/Factory;");
     }
 
     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/Manifest.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/Manifest.java
index d2d3e12..09002fb 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/Manifest.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/Manifest.java
@@ -62,7 +62,7 @@ public interface Manifest {
     Manifest attributes(Map<String, ?> attributes, String sectionName) throws ManifestException;
 
     /**
-     * Returns a new manifest instance where all the attribute values are expanded (e.g. there toString method is called).
+     * Returns a new manifest instance where all the attribute values are expanded (e.g. their toString method is called).
      * The returned manifest also contains all the attributes of the to be merged manifests specified in {@link #from(Object...)}.
      */
     Manifest getEffectiveManifest();
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeSpec.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeSpec.java
index 4251477..8f04131 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeSpec.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeSpec.java
@@ -24,7 +24,7 @@ import org.gradle.api.Action;
 public interface ManifestMergeSpec {
     /**
      * Adds a merge path to a manifest that should be merged into the base manifest. A merge path can be either another
-     * {@link org.gradle.api.java.archives.Manifest} or a path that is evaluated as for
+     * {@link org.gradle.api.java.archives.Manifest} or a path that is evaluated as per
      * {@link org.gradle.api.Project#files(Object...)} . If multiple merge paths are specified, the manifest are merged
      * in the order in which they are added.
      * 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java
index 4801603..7463325 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java
@@ -160,7 +160,7 @@ public class DefaultManifest implements org.gradle.api.java.archives.Manifest {
     }
 
     public org.gradle.api.java.archives.Manifest writeTo(Object path) {
-        IoActions.writeFile(fileResolver.resolve(path), new ErroringAction<Writer>() {
+        IoActions.writeTextFile(fileResolver.resolve(path), new ErroringAction<Writer>() {
             @Override
             protected void doExecute(Writer writer) throws Exception {
                 writeTo(writer);
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/DistributionExtension.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/DistributionExtension.groovy
deleted file mode 100755
index 6ab3eec..0000000
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/DistributionExtension.groovy
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.plugins
-
-import org.gradle.api.Incubating
-
-/**
- * Extension for {@link JavaLibraryDistributionPlugin }
- * 
- *  
- * <p>The extension for the java-library-distribution plugin.</p>
- * <p>
- * Use this class to configure the name of the distribution.
- * <pre autoTested=''>
- * apply plugin: 'java-library-distribution'
- *
- * distribution {
- *   name = 'my-name'
- * }
- * </pre>
- * <p>
- * @author scogneau
- *
- */
- at Incubating
-class DistributionExtension {
-
-	/**
-	 * The name of the distribution
-	 */
-	String name;
-}
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 874ca4e..3a8b519 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,11 +21,13 @@ import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.internal.ConventionMapping;
 import org.gradle.api.internal.IConventionAware;
-import org.gradle.api.internal.plugins.ProcessResources;
+import org.gradle.api.internal.plugins.DslObject;
 import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.DefaultJavaSourceSet;
+import org.gradle.api.internal.tasks.DefaultResourceSet;
+import org.gradle.api.internal.tasks.SourceSetCompileClasspath;
 import org.gradle.api.reporting.ReportingExtension;
-import org.gradle.api.tasks.Copy;
-import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.*;
 import org.gradle.api.tasks.compile.AbstractCompile;
 import org.gradle.api.tasks.compile.JavaCompile;
 import org.gradle.api.tasks.javadoc.Javadoc;
@@ -63,6 +65,7 @@ public class JavaBasePlugin implements Plugin<Project> {
     public void apply(Project project) {
         project.getPlugins().apply(BasePlugin.class);
         project.getPlugins().apply(ReportingBasePlugin.class);
+        project.getPlugins().apply(JavaLanguagePlugin.class);
 
         JavaPluginConvention javaConvention = new JavaPluginConvention((ProjectInternal) project, instantiator);
         project.getConvention().getPlugins().put("java", javaConvention);
@@ -79,10 +82,11 @@ public class JavaBasePlugin implements Plugin<Project> {
     }
 
     private void configureSourceSetDefaults(final JavaPluginConvention pluginConvention) {
+        final Project project = pluginConvention.getProject();
+        final ProjectSourceSet projectSourceSet = project.getExtensions().getByType(ProjectSourceSet.class);
+
         pluginConvention.getSourceSets().all(new Action<SourceSet>() {
             public void execute(final SourceSet sourceSet) {
-                final Project project = pluginConvention.getProject();
-
                 ConventionMapping outputConventionMapping = ((IConventionAware) sourceSet.getOutput()).getConventionMapping();
 
                 ConfigurationContainer configurations = project.getConfigurations();
@@ -120,28 +124,34 @@ public class JavaBasePlugin implements Plugin<Project> {
 
                 sourceSet.getJava().srcDir(String.format("src/%s/java", sourceSet.getName()));
                 sourceSet.getResources().srcDir(String.format("src/%s/resources", sourceSet.getName()));
+                sourceSet.compiledBy(sourceSet.getClassesTaskName());
 
-                Copy processResources = project.getTasks().add(sourceSet.getProcessResourcesTaskName(), ProcessResources.class);
-                processResources.setDescription(String.format("Processes the %s.", sourceSet.getResources()));
-                ConventionMapping conventionMapping = processResources.getConventionMapping();
-                processResources.from(sourceSet.getResources());
-                conventionMapping.map("destinationDir", new Callable<Object>() {
-                    public Object call() throws Exception {
+                FunctionalSourceSet functionalSourceSet = projectSourceSet.create(sourceSet.getName());
+                Classpath compileClasspath = new SourceSetCompileClasspath(sourceSet);
+                DefaultJavaSourceSet javaSourceSet = instantiator.newInstance(DefaultJavaSourceSet.class, "java", sourceSet.getJava(), compileClasspath, functionalSourceSet);
+                functionalSourceSet.add(javaSourceSet);
+                ResourceSet resourceSet = instantiator.newInstance(DefaultResourceSet.class, "resources", sourceSet.getResources(), functionalSourceSet);
+                functionalSourceSet.add(resourceSet);
+
+                JvmBinaryContainer jvmBinaryContainer = project.getPlugins().getPlugin(JvmLanguagePlugin.class).getJvmBinaryContainer();
+                ClassDirectoryBinary binary = jvmBinaryContainer.create(sourceSet.getName(), ClassDirectoryBinary.class);
+                ConventionMapping conventionMapping = new DslObject(binary).getConventionMapping();
+                conventionMapping.map("classesDir", new Callable<File>() {
+                    public File call() throws Exception {
+                        return sourceSet.getOutput().getClassesDir();
+                    }
+                });
+                conventionMapping.map("resourcesDir", new Callable<File>() {
+                    public File call() throws Exception {
                         return sourceSet.getOutput().getResourcesDir();
                     }
                 });
 
-                String compileTaskName = sourceSet.getCompileJavaTaskName();
-                JavaCompile compileJava = project.getTasks().add(compileTaskName, JavaCompile.class);
-                configureForSourceSet(sourceSet, compileJava);
+                binary.getSource().add(javaSourceSet);
+                binary.getSource().add(resourceSet);
 
-                Task classes = project.getTasks().add(sourceSet.getClassesTaskName());
-                classes.dependsOn(sourceSet.getProcessResourcesTaskName(), compileTaskName);
-                classes.setDescription(String.format("Assembles the %s classes.", sourceSet.getName()));
-                classes.setGroup(BasePlugin.BUILD_GROUP);
-                classes.dependsOn(sourceSet.getOutput().getDirs());
-
-                sourceSet.compiledBy(sourceSet.getClassesTaskName());
+                // TODO:DAZ review this
+                binary.getClassesTask().dependsOn(sourceSet.getOutput().getDirs());
             }
         });
     }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLanguagePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLanguagePlugin.java
new file mode 100644
index 0000000..8717fb7
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLanguagePlugin.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins;
+
+import org.gradle.api.*;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.tasks.DefaultClasspath;
+import org.gradle.api.internal.tasks.DefaultJavaSourceSet;
+import org.gradle.api.internal.tasks.DefaultProjectSourceSet;
+import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.compile.JavaCompile;
+import org.gradle.internal.reflect.Instantiator;
+
+import javax.inject.Inject;
+
+/**
+ * Plugin for compiling Java code. Applies the {@link JvmLanguagePlugin}.
+ * Adds a {@link JavaCompile} task for each {@link JavaSourceSet} added to a {@link ClassDirectoryBinary}.
+ * Registers the {@link JavaSourceSet} element type for each {@link FunctionalSourceSet} added to {@link ProjectSourceSet}.
+ */
+ at Incubating
+public class JavaLanguagePlugin implements Plugin<Project> {
+    private final Instantiator instantiator;
+
+    @Inject
+    public JavaLanguagePlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    public void apply(final Project target) {
+        final JvmLanguagePlugin jvmLanguagePlugin = target.getPlugins().apply(JvmLanguagePlugin.class);
+
+        JvmBinaryContainer jvmBinaryContainer = jvmLanguagePlugin.getJvmBinaryContainer();
+        jvmBinaryContainer.all(new Action<ClassDirectoryBinary>() {
+            public void execute(final ClassDirectoryBinary binary) {
+                binary.getSource().withType(JavaSourceSet.class).all(new Action<JavaSourceSet>() {
+                    public void execute(JavaSourceSet javaSourceSet) {
+                        JavaCompile compileTask = (JavaCompile) binary.getCompileTask(); // TODO: can't simply cast
+                        if (compileTask == null) {
+                            compileTask = target.getTasks().add(binary.getTaskName("compile", "java"), JavaCompile.class);
+                            jvmLanguagePlugin.configureCompileTask(compileTask, javaSourceSet, binary);
+                            binary.setCompileTask(compileTask);
+                            binary.getClassesTask().dependsOn(compileTask);
+                        }
+                    }
+                });
+            }
+        });
+
+        ProjectSourceSet projectSourceSet = target.getExtensions().getByType(DefaultProjectSourceSet.class);
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(JavaSourceSet.class, new NamedDomainObjectFactory<JavaSourceSet>() {
+                    public JavaSourceSet create(String name) {
+                        return instantiator.newInstance(DefaultJavaSourceSet.class, name,
+                                instantiator.newInstance(DefaultSourceDirectorySet.class),
+                                instantiator.newInstance(DefaultClasspath.class), functionalSourceSet);
+                    }
+                });
+            }
+        });
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy
old mode 100755
new mode 100644
index a36e9ef..090b856
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaLibraryDistributionPlugin.groovy
@@ -16,51 +16,27 @@
 
 package org.gradle.api.plugins
 
-import org.gradle.api.GradleException
 import org.gradle.api.Incubating
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.tasks.bundling.Zip
+import org.gradle.api.distribution.plugins.DistributionPlugin
 
 /**
- * A {@link Plugin} which package project as a distribution including
- *  JAR, API documentation and source JAR for the project.
- * @author scogneau
+ * A {@link Plugin} which package a Java project as a distribution including the JAR and runtime dependencies.
  *
+ * @author scogneau
  */
 @Incubating
 class JavaLibraryDistributionPlugin implements Plugin<Project> {
-    static final String JAVA_LIBRARY_PLUGIN_NAME = "java-library-distribution"
-    static final String JAVA_LIBRARY_GROUP = JAVA_LIBRARY_PLUGIN_NAME
-    static final String TASK_DIST_ZIP_NAME = "distZip"
-
-    private DistributionExtension extension
     private Project project
 
     public void apply(Project project) {
         this.project = project
         project.plugins.apply(JavaPlugin)
-        addPluginExtension()
-        addDistZipTask()
-    }
-
-    private void addPluginExtension() {
-        extension = project.extensions.create("distribution", DistributionExtension)
-        extension.name = project.name
-    }
-
-    private void addDistZipTask() {
-        def distZipTask = project.tasks.add(TASK_DIST_ZIP_NAME, Zip)
-        distZipTask.description = "Bundles the project as a java library distribution."
-        distZipTask.group = JAVA_LIBRARY_GROUP
-        distZipTask.conventionMapping.baseName = {
-            if (project.distribution.name == null) {
-                throw new GradleException("Distribution name must not be null! Check your configuration of the java-library-distribution plugin.")
-            }
-            extension.name
-        }
+        project.plugins.apply(DistributionPlugin)
+        def contents = project.distributions[DistributionPlugin.MAIN_DISTRIBUTION_NAME].contents
         def jar = project.tasks[JavaPlugin.JAR_TASK_NAME]
-        distZipTask.with {
+        contents.with {
             from(jar)
             from(project.file("src/dist"))
             into("lib") {
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
index f16e15b..e306224 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
@@ -23,9 +23,9 @@ import org.gradle.api.Task;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
 import org.gradle.api.artifacts.Dependency;
-import org.gradle.api.internal.java.JavaLibrary;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.internal.java.JavaLibrary;
 import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
 import org.gradle.api.internal.plugins.EmbeddableJavaProject;
 import org.gradle.api.internal.project.ProjectInternal;
@@ -69,11 +69,10 @@ public class JavaPlugin implements Plugin<Project> {
 
         configureSourceSets(javaConvention);
         configureConfigurations(project);
-        configureComponent(project);
 
         configureJavaDoc(javaConvention);
         configureTest(project, javaConvention);
-        configureArchives(project, javaConvention);
+        configureArchivesAndComponent(project, javaConvention);
         configureBuild(project);
     }
 
@@ -99,7 +98,7 @@ public class JavaPlugin implements Plugin<Project> {
         addDependsOnTaskInOtherProjects(javadoc, true, JAVADOC_TASK_NAME, COMPILE_CONFIGURATION_NAME);
     }
 
-    private void configureArchives(final Project project, final JavaPluginConvention pluginConvention) {
+    private void configureArchivesAndComponent(final Project project, final JavaPluginConvention pluginConvention) {
         Jar jar = project.getTasks().add(JAR_TASK_NAME, Jar.class);
         jar.getManifest().from(pluginConvention.getManifest());
         jar.setDescription("Assembles a jar archive containing the main classes.");
@@ -111,8 +110,12 @@ public class JavaPlugin implements Plugin<Project> {
             }
         });
 
-        project.getExtensions().getByType(DefaultArtifactPublicationSet.class).addCandidate(new ArchivePublishArtifact(jar));
-        project.getConfigurations().getByName(RUNTIME_CONFIGURATION_NAME).getArtifacts().add(new ArchivePublishArtifact(jar));
+        ArchivePublishArtifact jarArtifact = new ArchivePublishArtifact(jar);
+        Configuration runtimeConfiguration = project.getConfigurations().getByName(RUNTIME_CONFIGURATION_NAME);
+
+        runtimeConfiguration.getArtifacts().add(jarArtifact);
+        project.getExtensions().getByType(DefaultArtifactPublicationSet.class).addCandidate(jarArtifact);
+        project.getComponents().add(new JavaLibrary(jarArtifact, runtimeConfiguration.getAllDependencies()));
     }
 
     private void configureBuild(Project project) {
@@ -162,11 +165,6 @@ public class JavaPlugin implements Plugin<Project> {
         configurations.getByName(Dependency.DEFAULT_CONFIGURATION).extendsFrom(runtimeConfiguration);
     }
 
-
-    private void configureComponent(Project project) {
-        project.getComponents().add(new JavaLibrary(project.getConfigurations().getByName(RUNTIME_CONFIGURATION_NAME)));
-    }
-
     /**
      * Adds a dependency on tasks with the specified name in other projects.  The other projects are determined from
      * project lib dependencies using the specified configuration name. These may be projects this project depends on or
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JvmLanguagePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JvmLanguagePlugin.java
new file mode 100644
index 0000000..6618927
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/JvmLanguagePlugin.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins;
+
+import org.gradle.api.*;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.plugins.DslObject;
+import org.gradle.api.internal.plugins.ProcessResources;
+import org.gradle.api.internal.tasks.DefaultBinariesContainer;
+import org.gradle.api.internal.tasks.DefaultClassDirectoryBinary;
+import org.gradle.api.internal.tasks.DefaultJvmBinaryContainer;
+import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.internal.reflect.Instantiator;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.concurrent.Callable;
+
+/**
+ * Base plugin for JVM language support. Applies the {@link LanguageBasePlugin}.
+ * Adds a {@link JvmBinaryContainer} named {@code jvm} to the project's {@link BinariesContainer}.
+ * Registers the {@link ClassDirectoryBinary} element type for that container.
+ * Adds a lifecycle task named {@code classes} for each {@link ClassDirectoryBinary}.
+ * Adds a {@link Copy} task named {@code processXYZResources} for each {@link ResourceSet} added to a {@link ClassDirectoryBinary}.
+ */
+ at Incubating
+public class JvmLanguagePlugin implements Plugin<Project> {
+    private final Instantiator instantiator;
+
+    private JvmBinaryContainer jvmBinaryContainer;
+
+    @Inject
+    public JvmLanguagePlugin(Instantiator instantiator) {
+        this.instantiator = instantiator;
+    }
+
+    public void apply(final Project target) {
+        target.getPlugins().apply(LanguageBasePlugin.class);
+
+        BinariesContainer binariesContainer = target.getExtensions().getByType(DefaultBinariesContainer.class);
+        jvmBinaryContainer = instantiator.newInstance(DefaultJvmBinaryContainer.class, instantiator);
+        binariesContainer.add(jvmBinaryContainer);
+
+        jvmBinaryContainer.registerFactory(ClassDirectoryBinary.class, new NamedDomainObjectFactory<ClassDirectoryBinary>() {
+            public ClassDirectoryBinary create(String name) {
+                return instantiator.newInstance(DefaultClassDirectoryBinary.class, name);
+            };
+        });
+
+        jvmBinaryContainer.all(new Action<ClassDirectoryBinary>() {
+            public void execute(final ClassDirectoryBinary binary) {
+                ConventionMapping conventionMapping = new DslObject(binary).getConventionMapping();
+                conventionMapping.map("classesDir", new Callable<File>() {
+                    public File call() throws Exception {
+                        return new File(new File(target.getBuildDir(), "classes"), binary.getName());
+                    }
+                });
+                final Task classesTask = target.getTasks().add(binary.getTaskName(null, "classes"));
+                classesTask.setDescription(String.format("Assembles the %s classes.", binary.getName()));
+                binary.setClassesTask(classesTask);
+                binary.getSource().withType(ResourceSet.class).all(new Action<ResourceSet>() {
+                    public void execute(ResourceSet resourceSet) {
+                        Copy resourcesTask = binary.getResourcesTask();
+                        if (resourcesTask == null) {
+                            resourcesTask = target.getTasks().add(binary.getTaskName("process", "resources"), ProcessResources.class);
+                            resourcesTask.setDescription(String.format("Processes the %s resources.", binary.getName()));
+                            new DslObject(resourcesTask).getConventionMapping().map("destinationDir", new Callable<File>() {
+                                public File call() throws Exception {
+                                    return binary.getResourcesDir();
+                                }
+                            });
+                            binary.setResourcesTask(resourcesTask);
+                            classesTask.dependsOn(resourcesTask);
+                        }
+                        resourcesTask.from(resourceSet.getSource());
+                    }
+                });
+            }
+        });
+    }
+
+    /**
+     * Returns the {@code binaries.jvm} container that was added by this plugin to the project.
+     *
+     * @return the {@code binaries.jvm} container that was added by this plugin to the project
+     */
+    public JvmBinaryContainer getJvmBinaryContainer() {
+        return jvmBinaryContainer;
+    }
+
+    /**
+     * Preconfigures the specified compile task based on the specified source set and class directory binary.
+     *
+     * @param compile the compile task to be preconfigured
+     * @param sourceSet the source set for the compile task
+     * @param binary the binary for the compile task
+     */
+    public void configureCompileTask(AbstractCompile compile, final JvmLanguageSourceSet sourceSet, final ClassDirectoryBinary binary) {
+        compile.setDescription(String.format("Compiles the %s.", sourceSet));
+        compile.setSource(sourceSet.getSource());
+        compile.dependsOn(sourceSet);
+        ConventionMapping conventionMapping = compile.getConventionMapping();
+        conventionMapping.map("classpath", new Callable<Object>() {
+            public Object call() throws Exception {
+                return sourceSet.getCompileClasspath().getFiles();
+            }
+        });
+        conventionMapping.map("destinationDir", new Callable<Object>() {
+            public Object call() throws Exception {
+                return binary.getClassesDir();
+            }
+        });
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/LanguageBasePlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/LanguageBasePlugin.java
new file mode 100644
index 0000000..a62a71a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/LanguageBasePlugin.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins;
+
+import org.gradle.api.*;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.*;
+import org.gradle.api.tasks.*;
+import org.gradle.internal.reflect.Instantiator;
+
+import javax.inject.Inject;
+
+/**
+ * Base plugin for language support.
+ * Adds a {@link BinariesContainer} named {@code binaries} to the project.
+ * Adds a {@link ProjectSourceSet} named {@code sources} to the project.
+ * Registers the {@link ResourceSet} element type for each {@link FunctionalSourceSet} added to {@link ProjectSourceSet}.
+ */
+ at Incubating
+public class LanguageBasePlugin implements Plugin<Project> {
+    private final Instantiator instantiator;
+    private final FileResolver fileResolver;
+
+    @Inject
+    public LanguageBasePlugin(Instantiator instantiator, FileResolver fileResolver) {
+        this.instantiator = instantiator;
+        this.fileResolver = fileResolver;
+    }
+
+    public void apply(Project target) {
+        target.getExtensions().create("binaries", DefaultBinariesContainer.class, instantiator);
+        ProjectSourceSet projectSourceSet = target.getExtensions().create("sources", DefaultProjectSourceSet.class, instantiator);
+
+        // TODO: move to JvmLanguagePlugin?
+        projectSourceSet.all(new Action<FunctionalSourceSet>() {
+            public void execute(final FunctionalSourceSet functionalSourceSet) {
+                functionalSourceSet.registerFactory(ResourceSet.class, new NamedDomainObjectFactory<ResourceSet>() {
+                    public ResourceSet create(String name) {
+                        return instantiator.newInstance(DefaultResourceSet.class, name,
+                                instantiator.newInstance(DefaultSourceDirectorySet.class, name, fileResolver), functionalSourceSet);
+                    }
+                });
+            }
+        });
+    }
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
index 7244571..040f7b0 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
@@ -21,9 +21,11 @@ import org.gradle.api.Plugin;
 import org.gradle.api.Project;
 import org.gradle.api.artifacts.Configuration;
 import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.PublishArtifact;
 import org.gradle.api.file.FileCollection;
 import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
 import org.gradle.api.internal.plugins.DefaultArtifactPublicationSet;
+import org.gradle.api.internal.java.WebApplication;
 import org.gradle.api.tasks.SourceSet;
 import org.gradle.api.tasks.bundling.War;
 
@@ -74,8 +76,10 @@ public class WarPlugin implements Plugin<Project> {
         War war = project.getTasks().add(WAR_TASK_NAME, War.class);
         war.setDescription("Generates a war archive with all the compiled classes, the web-app content and the libraries.");
         war.setGroup(BasePlugin.BUILD_GROUP);
-        project.getExtensions().getByType(DefaultArtifactPublicationSet.class).addCandidate(new ArchivePublishArtifact(war));
+        ArchivePublishArtifact warArtifact = new ArchivePublishArtifact(war);
+        project.getExtensions().getByType(DefaultArtifactPublicationSet.class).addCandidate(warArtifact);
         configureConfigurations(project.getConfigurations());
+        configureComponent(project, warArtifact);
     }
 
     public void configureConfigurations(ConfigurationContainer configurationContainer) {
@@ -87,4 +91,8 @@ public class WarPlugin implements Plugin<Project> {
         configurationContainer.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME).extendsFrom(provideCompileConfiguration);
         configurationContainer.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME).extendsFrom(provideRuntimeConfiguration);
     }
+
+    private void configureComponent(Project project, PublishArtifact warArtifact) {
+        project.getComponents().add(new WebApplication(warArtifact));
+    }
 }
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/BinariesContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/BinariesContainer.java
new file mode 100644
index 0000000..4951358
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/BinariesContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.*;
+
+/**
+ * A container for binaries that in turn contains more specialized containers.
+ * Added to a project by the {@link org.gradle.api.plugins.LanguageBasePlugin}.
+ */
+// TODO: ideally this would be a container where each element type is only allowed once and elements can be looked up by type
+// for now I solved the lookup (usability) problem with a JvmLanguagePlugin.getJvmBinariesContainer() method; maybe that's good enough
+ at Incubating
+public interface BinariesContainer extends NamedDomainObjectSet<Named> {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ClassDirectoryBinary.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ClassDirectoryBinary.java
new file mode 100644
index 0000000..ae2a94a
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ClassDirectoryBinary.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.*;
+import org.gradle.api.tasks.compile.AbstractCompile;
+
+import java.io.File;
+
+/**
+ * An exploded binary containing resources and compiled class files.
+ */
+// TODO: maybe we need to allow additional dirs like SourceSetOutput does
+// (esp. for backwards compatibility). Wonder if it's still necessary to distinguish
+// between classes and resources dirs, instead of just maintaining a collection of dirs.
+// As far as generated resources are concerned, it might be better to model
+// them as an additional (Buildable) ResourceSet.
+ at Incubating
+public interface ClassDirectoryBinary extends Named, Buildable {
+    File getClassesDir();
+    void setClassesDir(File dir);
+    File getResourcesDir();
+    void setResourcesDir(File dir);
+    DomainObjectCollection<LanguageSourceSet> getSource();
+    Task getClassesTask();
+    void setClassesTask(Task task);
+    @Nullable
+    Copy getResourcesTask();
+    void setResourcesTask(Copy task);
+    // TODO: having a single compile task won't work if multiple separately compiled langs are used
+    @Nullable
+    AbstractCompile getCompileTask();
+    void setCompileTask(AbstractCompile task);
+    String getTaskName(String verb, String target);
+    String getTaskBaseName();
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Classpath.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Classpath.java
new file mode 100644
index 0000000..6c6a4e4
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/Classpath.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Incubating;
+import org.gradle.api.file.FileCollection;
+
+/**
+ * A collection of files to be used as a class path.
+ */
+ at Incubating
+public interface Classpath extends Buildable {
+    FileCollection getFiles();
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/FunctionalSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/FunctionalSourceSet.java
new file mode 100644
index 0000000..c12c4d0
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/FunctionalSourceSet.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+/**
+ * A container holding {@link LanguageSourceSet}s with a similar function
+ * (production code, test code, etc.).
+ */
+ at Incubating
+public interface FunctionalSourceSet extends ExtensiblePolymorphicDomainObjectContainer<LanguageSourceSet>, Named {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JavaSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JavaSourceSet.java
new file mode 100644
index 0000000..163c7e7
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JavaSourceSet.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A set of sources passed to the Java compiler.
+ */
+ at Incubating
+public interface JavaSourceSet extends JvmLanguageSourceSet {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmBinaryContainer.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmBinaryContainer.java
new file mode 100644
index 0000000..0a59bb6
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmBinaryContainer.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer;
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+
+/**
+ * A container for JVM binaries.
+ */
+ at Incubating
+public interface JvmBinaryContainer extends ExtensiblePolymorphicDomainObjectContainer<ClassDirectoryBinary>, Named {
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmLanguageSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmLanguageSourceSet.java
new file mode 100644
index 0000000..4a9c85e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/JvmLanguageSourceSet.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A set of sources for a JVM language.
+ */
+ at Incubating
+public interface JvmLanguageSourceSet extends LanguageSourceSet {
+    Classpath getCompileClasspath();
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/LanguageSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/LanguageSourceSet.java
new file mode 100644
index 0000000..7fd9b13
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/LanguageSourceSet.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.Incubating;
+import org.gradle.api.Named;
+import org.gradle.api.file.SourceDirectorySet;
+
+/**
+ * A set of sources for a programming language.
+ */
+ at Incubating
+public interface LanguageSourceSet extends Named, Buildable {
+    // TODO: do we want to keep using SourceDirectorySet in the new API?
+    // would feel more natural if dirs could be added directly to LanguageSourceSet
+    // could also think about extending SourceDirectorySet
+    SourceDirectorySet getSource();
+    FunctionalSourceSet getParent();
+}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ProjectSourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ProjectSourceSet.java
new file mode 100644
index 0000000..6831762
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ProjectSourceSet.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectContainer;
+
+/**
+ * A container of {@link FunctionalSourceSet}s. Added to a project by the
+ * {@link org.gradle.api.plugins.LanguageBasePlugin}.
+ */
+ at Incubating
+public interface ProjectSourceSet extends NamedDomainObjectContainer<FunctionalSourceSet> {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ResourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ResourceSet.java
new file mode 100644
index 0000000..b40e94e
--- /dev/null
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/ResourceSet.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.tasks;
+
+import org.gradle.api.Incubating;
+
+/**
+ * A set of resource files.
+ */
+ at Incubating
+public interface ResourceSet extends LanguageSourceSet {}
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java
index be80099..3e6287a 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java
@@ -85,7 +85,7 @@ public interface SourceSet {
 
    /**
      * {@link SourceSetOutput} is a {@link FileCollection} of all output directories (compiled classes, processed resources, etc.)
-     *  and it provides means configure the default output dirs and register additional output dirs. See examples in {@link SourceSetOutput}
+     *  and it provides means to configure the default output dirs and register additional output dirs. See examples in {@link SourceSetOutput}
      *
      * @return The output dirs, as a {@link SourceSetOutput}.
      */
@@ -93,7 +93,7 @@ public interface SourceSet {
 
     /**
      * Registers a set of tasks which are responsible for compiling this source set into the classes directory. The
-     * paths are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * paths are evaluated as per {@link org.gradle.api.Task#dependsOn(Object...)}.
      *
      * @param taskPaths The tasks which compile this source set.
      * @return this
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy
index 8e61d0d..edbec72 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy
@@ -104,7 +104,7 @@ class War extends Jar {
     /**
      * Adds files to the classpath to include in the WAR archive.
      *
-     * @param classpath The files to add. These are evaluated as for {@link org.gradle.api.Project#files(Object [])}
+     * @param classpath The files to add. These are evaluated as per {@link org.gradle.api.Project#files(Object [])}
      */
     void classpath(Object... classpath) {
         FileCollection oldClasspath = getClasspath()
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
index 7f59d9c..1067436 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
+++ b/subprojects/plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
@@ -22,9 +22,7 @@ import org.gradle.api.tasks.testing.TestFrameworkOptions
 /**
  * @author Tom Eyckmans
  */
-public class TestNGOptions extends TestFrameworkOptions implements Serializable {
-    private static final long serialVersionUID = 1
-
+public class TestNGOptions extends TestFrameworkOptions{
     static final String JDK_ANNOTATIONS = 'JDK'
     static final String JAVADOC_ANNOTATIONS = 'Javadoc'
 
diff --git a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriter.java b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriter.java
index 167af9f..02d4761 100644
--- a/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriter.java
+++ b/subprojects/plugins/src/main/groovy/org/gradle/external/javadoc/internal/JavadocOptionFileWriter.java
@@ -40,7 +40,7 @@ public class JavadocOptionFileWriter {
     }
 
     void write(File outputFile) throws IOException {
-        IoActions.writeFile(outputFile, new ErroringAction<BufferedWriter>() {
+        IoActions.writeTextFile(outputFile, new ErroringAction<BufferedWriter>() {
             @Override
             protected void doExecute(BufferedWriter writer) throws Exception {
                 final Map<String, JavadocOptionFileOption> options = new TreeMap<String, JavadocOptionFileOption>(optionFile.getOptions());
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/distribution.properties b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/distribution.properties
new file mode 100644
index 0000000..5b1a76b
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/distribution.properties
@@ -0,0 +1,17 @@
+#
+# Copyright 2012 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+implementation-class=org.gradle.api.distribution.plugins.DistributionPlugin
\ No newline at end of file
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/java-lang.properties b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/java-lang.properties
new file mode 100644
index 0000000..6365cfa
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/java-lang.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.JavaLanguagePlugin
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/jvm-lang.properties b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/jvm-lang.properties
new file mode 100644
index 0000000..ca08f4c
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/jvm-lang.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.JvmLanguagePlugin
diff --git a/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/lang-base.properties b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/lang-base.properties
new file mode 100644
index 0000000..7507f57
--- /dev/null
+++ b/subprojects/plugins/src/main/resources/META-INF/gradle-plugins/lang-base.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.plugins.LanguageBasePlugin
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
new file mode 100644
index 0000000..aed4ace
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/distribution/plugins/DistributionPluginTest.groovy
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.distribution.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.distribution.DistributionContainer
+import org.gradle.api.tasks.Sync
+import org.gradle.api.tasks.bundling.Zip
+import org.gradle.api.tasks.bundling.Tar
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+class DistributionPluginTest extends Specification {
+    private final Project project = HelperUtil.builder().withName("test-project").build()
+
+    def "adds convention object and a main distribution"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+
+        then:
+        def distributions = project.extensions.getByType(DistributionContainer.class)
+        def dist = distributions.main
+        dist.name == 'main'
+        dist.baseName == 'test-project'
+    }
+
+    def "provides default values for additional distributions"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+
+        then:
+        def distributions = project.extensions.getByType(DistributionContainer.class)
+        def dist = distributions.create('custom')
+        dist.name == 'custom'
+        dist.baseName == 'test-project-custom'
+    }
+
+    def "adds distZip task for main distribution"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+
+        then:
+        def task = project.tasks.distZip
+        task instanceof Zip
+        task.archivePath == project.file("build/distributions/test-project.zip")
+    }
+
+    def "adds distZip task for custom distribution"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+        project.distributions.create('custom')
+
+        then:
+        def task = project.tasks.customDistZip
+        task instanceof Zip
+        task.archivePath == project.file("build/distributions/test-project-custom.zip")
+    }
+
+    def "adds distTar task for main distribution"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+
+        then:
+        def task = project.tasks.distTar
+        task instanceof Tar
+        task.archivePath == project.file("build/distributions/test-project.tar")
+    }
+
+    def "adds distTar task for custom distribution"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+        project.distributions.create('custom')
+
+        then:
+        def task = project.tasks.customDistTar
+        task instanceof Tar
+        task.archivePath == project.file("build/distributions/test-project-custom.tar")
+    }
+
+    def "distribution names include project version when specified"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+        project.version = '1.2'
+
+        then:
+        def zip = project.tasks.distZip
+        zip.archivePath == project.file("build/distributions/test-project-1.2.zip")
+        def tar = project.tasks.distTar
+        tar.archivePath == project.file("build/distributions/test-project-1.2.tar")
+    }
+
+    def "adds installDist task for main distribution"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+
+        then:
+        def task = project.installDist
+        task instanceof Sync
+        task.destinationDir == project.file("build/install/test-project")
+    }
+
+    def "adds installDist task for custom distribution"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+        project.distributions.create('custom')
+
+        then:
+        def task = project.installCustomDist
+        task instanceof Sync
+        task.destinationDir == project.file("build/install/test-project-custom")
+    }
+
+    public void "distribution name is configurable"() {
+        when:
+        project.apply(plugin: DistributionPlugin)
+        project.distributions.main.baseName = "SuperApp";
+
+        then:
+        def distZipTask = project.tasks.distZip
+        distZipTask.archiveName == "SuperApp.zip"
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
index db1e3e0..d4c638d 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
@@ -36,11 +36,13 @@ import org.junit.runner.notification.RunNotifier
 
 import static org.hamcrest.Matchers.*
 import static org.junit.Assert.assertThat
+import static org.junit.Assume.assumeTrue
 
 @RunWith(JMock.class)
 class JUnitTestClassProcessorTest {
     private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
-    @Rule public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
+    @Rule
+    public final TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider();
     private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
     private final ActorFactory actorFactory = new TestActorFactory()
     private final JUnitTestClassProcessor processor = new JUnitTestClassProcessor(tmpDir.testDirectory, new LongIdGenerator(), actorFactory, {} as StandardOutputRedirector);
@@ -143,6 +145,38 @@ class JUnitTestClassProcessorTest {
     }
 
     @Test
+    public void executesAJUnit4TestClassWithFailedTestAssumption() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite, TestStartEvent event ->
+                assertThat(suite.id, equalTo(1L))
+                assertThat(suite.name, equalTo(ATestClassWithFailedTestAssumption.class.name))
+                assertThat(suite.className, equalTo(ATestClassWithFailedTestAssumption.class.name))
+                assertThat(event.parentId, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test, TestStartEvent event ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('assumed'))
+                assertThat(test.className, equalTo(ATestClassWithFailedTestAssumption.class.name))
+                assertThat(event.parentId, equalTo(1L))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, equalTo(TestResult.ResultType.SKIPPED))
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithFailedTestAssumption.class));
+        processor.stop();
+    }
+
+    @Test
     public void executesAnIgnoredJUnit4TestClass() {
         context.checking {
             one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
@@ -155,11 +189,20 @@ class JUnitTestClassProcessorTest {
             one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
             will { TestDescriptorInternal test, TestStartEvent event ->
                 assertThat(test.id, equalTo(2L))
-                assertThat(test.name, equalTo('ignored'))
+                assertThat(test.name, equalTo('ignored2'))
                 assertThat(test.className, equalTo(AnIgnoredTestClass.class.name))
                 assertThat(event.parentId, equalTo(1L))
             }
             one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test, TestStartEvent event ->
+                assertThat(test.id, equalTo(3L))
+                assertThat(test.name, equalTo('ignored'))
+                assertThat(test.className, equalTo(AnIgnoredTestClass.class.name))
+                assertThat(event.parentId, equalTo(1L))
+            }
+            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
             will { id, TestCompleteEvent event ->
                 assertThat(event.resultType, equalTo(TestResult.ResultType.SKIPPED))
             }
@@ -724,16 +767,28 @@ public class ATestClass {
 }
 
 public class ATestClassWithIgnoredMethod {
-    @Test @Ignore
+    @Test
+    @Ignore
     public void ignored() {
     }
 }
 
+public class ATestClassWithFailedTestAssumption {
+    @Test
+    public void assumed() {
+        assumeTrue(false)
+    }
+}
+
 @Ignore
 public class AnIgnoredTestClass {
     @Test
     public void ignored() {
     }
+
+    @Test
+    public void ignored2() {
+    }
 }
 
 public class ABrokenTestClass {
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
index 0249361..bb8ba78 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/report/DefaultTestReportTest.groovy
@@ -15,7 +15,6 @@
  */
 package org.gradle.api.internal.tasks.testing.junit.report
 
-import org.cyberneko.html.parsers.SAXParser
 import org.gradle.api.Action
 import org.gradle.api.internal.tasks.testing.junit.result.TestClassResult
 import org.gradle.api.internal.tasks.testing.junit.result.TestMethodResult
@@ -26,6 +25,8 @@ import org.gradle.api.tasks.testing.TestResult
 import org.gradle.test.fixtures.file.TestFile
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.gradle.util.ConfigureUtil
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
 import org.junit.Rule
 import spock.lang.Specification
 
@@ -300,6 +301,14 @@ class TestResultsBuilder implements TestResultsProvider {
         }
     }
 
+    boolean hasOutput(String className, TestOutputEvent.Destination destination) {
+        if (destination == TestOutputEvent.Destination.StdOut) {
+            return testClasses[className].stdout != null && testClasses[className].stdout.length() != 0
+        } else if (destination == TestOutputEvent.Destination.StdErr) {
+            return testClasses[className].stderr != null && testClasses[className].stderr.length() != 0
+        }
+    }
+
     private static class BuildableTestClassResult extends TestClassResult {
         String stderr;
         String stdout;
@@ -352,83 +361,78 @@ class TestResultsBuilder implements TestResultsProvider {
         public void printStackTrace(PrintWriter s) {
             s.print(text);
         }
-
-
     }
 }
 
 class TestResultsFixture {
-    final TestFile file
-    Node content
+    Document content
 
     TestResultsFixture(TestFile file) {
-        this.file = file
         file.assertIsFile()
-        def text = file.getText('utf-8').readLines()
-        def withoutDocType = text.subList(1, text.size()).join('\n')
-        content = new XmlParser(new SAXParser()).parseText(withoutDocType)
+        content = Jsoup.parse(file, null)
     }
 
     void assertHasTests(int tests) {
-        Node testDiv = content.depthFirst().find { it.'@id' == 'tests' }
+        def testDiv = content.select("div#tests")
         assert testDiv != null
-        Node counter = testDiv.DIV.find { it.'@class' == 'counter' }
+        def counter = testDiv.select("div.counter")
         assert counter != null
         assert counter.text() == tests as String
     }
 
     void assertHasFailures(int tests) {
-        Node testDiv = content.depthFirst().find { it.'@id' == 'failures' }
+        def testDiv = content.select("div#failures")
         assert testDiv != null
-        Node counter = testDiv.DIV.find { it.'@class' == 'counter' }
+        def counter = testDiv.select("div.counter")
         assert counter != null
         assert counter.text() == tests as String
     }
 
     void assertHasDuration(String duration) {
-        Node testDiv = content.depthFirst().find { it.'@id' == 'duration' }
+        def testDiv = content.select("div#duration")
         assert testDiv != null
-        Node counter = testDiv.DIV.find { it.'@class' == 'counter' }
+        def counter = testDiv.select("div.counter")
         assert counter != null
-        assert counter.text() == duration
+        assert counter.text() == duration as String
+
     }
 
     void assertHasNoDuration() {
-        Node testDiv = content.depthFirst().find { it.'@id' == 'duration' }
+        def testDiv = content.select("div#duration")
         assert testDiv != null
-        Node counter = testDiv.DIV.find { it.'@class' == 'counter' }
+        def counter = testDiv.select("div.counter")
         assert counter != null
-        assert counter.text() == '-'
+        assert counter.text() == "-"
     }
 
     void assertHasSuccessRate(int rate) {
-        Node testDiv = content.depthFirst().find { it.'@id' == 'successRate' }
+        def testDiv = content.select("div#successRate")
         assert testDiv != null
-        Node counter = testDiv.DIV.find { it.'@class' == 'percent' }
+        def counter = testDiv.select("div.percent")
         assert counter != null
         assert counter.text() == "${rate}%"
     }
 
     void assertHasNoSuccessRate() {
-        Node testDiv = content.depthFirst().find { it.'@id' == 'successRate' }
+        def testDiv = content.select("div#successRate")
         assert testDiv != null
-        Node counter = testDiv.DIV.find { it.'@class' == 'percent' }
+        def counter = testDiv.select("div.percent")
         assert counter != null
-        assert counter.text() == '-'
+        assert counter.text() == "-"
     }
 
     void assertHasNoNavLinks() {
-        assert findTab('Packages') == null
+        assert findTab('Packages').isEmpty()
     }
 
     void assertHasLinkTo(String target, String display = target) {
-        assert content.depthFirst().find { it.name() == 'A' && it.'@href' == "${target}.html" && it.text() == display }
+        assert content.select("a[href=${target}.html]").find{it.text() == display}
     }
 
     void assertHasFailedTest(String className, String testName) {
         def tab = findTab('Failed tests')
         assert tab != null
-        assert tab.depthFirst().find { it.name() == 'A' && it.'@href' == "${className}.html#${testName}" && it.text() == testName }
+        assert tab.select("a[href=${className}.html#$testName]").find{it.text() == testName}
     }
 
     void assertHasTest(String testName) {
@@ -437,39 +441,37 @@ class TestResultsFixture {
 
     void assertTestIgnored(String testName) {
         def row = findTestDetails(testName)
-        assert row.TD[2].text() == 'ignored'
+        assert row.select("tr > td:eq(2)").text() == 'ignored'
     }
 
     void assertHasFailure(String testName, String stackTrace) {
         def detailsRow = findTestDetails(testName)
-        assert detailsRow.TD[2].text() == 'failed'
-
+        assert detailsRow.select("tr > td:eq(2)").text() == 'failed'
         def tab = findTab('Failed tests')
-        assert tab != null
-        def pre = tab.depthFirst().findAll { it.name() == 'PRE' }
-        assert pre.find { it.text() == stackTrace.trim() }
+        assert tab != null && !tab.isEmpty()
+        assert tab.select("pre").find {it.text() == stackTrace.trim() }
     }
 
     private def findTestDetails(String testName) {
         def tab = findTab('Tests')
-        def anchor = tab.depthFirst().find { it.name() == 'TD' && it.text() == testName }
+        def anchor = tab.select("TD").find {it.text() == testName}
         return anchor?.parent()
     }
 
     void assertHasStandardOutput(String stdout) {
         def tab = findTab('Standard output')
         assert tab != null
-        assert tab.SPAN[0].PRE[0].text() == stdout.trim()
+        assert tab.select("SPAN > PRE").find{it.text() == stdout.trim() }
     }
 
     void assertHasStandardError(String stderr) {
         def tab = findTab('Standard error')
         assert tab != null
-        assert tab.SPAN[0].PRE[0].text() == stderr.trim()
+        assert tab.select("SPAN > PRE").find{it.text() == stderr.trim() }
     }
 
     private def findTab(String title) {
-        def tab = content.depthFirst().find { it.name() == 'DIV' && it.'@class' == 'tab' && it.H2[0].text() == title }
+        def tab = content.select("div.tab:has(h2:contains($title))")
         return tab
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializerTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializerTest.groovy
new file mode 100644
index 0000000..8dd7729
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestOutputSerializerTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.internal.tasks.testing.junit.result
+
+import org.gradle.api.tasks.testing.TestOutputEvent
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.junit.Rule
+import spock.lang.Specification
+
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
+import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
+
+/**
+ * by Szczepan Faber, created at: 11/19/12
+ */
+class TestOutputSerializerTest extends Specification {
+    @Rule
+    private TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
+    private serializer = new TestOutputSerializer(temp.testDirectory)
+
+    def "flushes all output when output finishes"() {
+        when:
+        serializer.onOutput("Class1", StdOut, "[out]")
+        serializer.onOutput("Class2", StdErr, "[err]")
+        serializer.onOutput("Class1", StdErr, "[err]")
+        serializer.onOutput("Class1", StdOut, "[out2]")
+        serializer.finishOutputs()
+
+        then:
+        collectOutput("Class1", StdOut) == "[out][out2]"
+        collectOutput("Class1", StdErr) == "[err]"
+        collectOutput("Class2", StdErr) == "[err]"
+    }
+
+    def "writes nothing for unknown test class"() {
+        when:
+        serializer.finishOutputs()
+
+        then:
+        collectOutput("Unknown", StdErr) == ""
+    }
+
+    def "can query whether output is available for a test class"() {
+        when:
+        serializer.onOutput("Class1", StdOut, "[out]")
+        serializer.finishOutputs()
+
+        then:
+        serializer.hasOutput("Class1", StdOut)
+        !serializer.hasOutput("Class1", StdErr)
+        !serializer.hasOutput("Unknown", StdErr)
+    }
+
+    String collectOutput(String className, TestOutputEvent.Destination destination) {
+        def writer = new StringWriter()
+        serializer.writeOutputs(className, destination, writer)
+        return writer.toString()
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy
index 858226c..dcee91b 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/result/TestReportDataCollectorSpec.groovy
@@ -16,32 +16,30 @@
 
 package org.gradle.api.internal.tasks.testing.junit.result
 
-import org.gradle.api.internal.tasks.testing.*
+import org.gradle.api.Action
 import org.gradle.api.internal.tasks.testing.results.DefaultTestResult
-import org.gradle.api.tasks.testing.TestOutputEvent
 import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
 import org.junit.Rule
 import spock.lang.Specification
+import org.gradle.api.internal.tasks.testing.*
 
 import static java.util.Arrays.asList
 import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdErr
 import static org.gradle.api.tasks.testing.TestOutputEvent.Destination.StdOut
 import static org.gradle.api.tasks.testing.TestResult.ResultType.FAILURE
 import static org.gradle.api.tasks.testing.TestResult.ResultType.SUCCESS
-import org.gradle.api.Action
 
 /**
  * by Szczepan Faber, created at: 11/19/12
  */
 class TestReportDataCollectorSpec extends Specification {
-
     @Rule
     private TestNameTestDirectoryProvider temp = new TestNameTestDirectoryProvider()
-    private CachingFileWriter fileWriter = Mock()
-    private TestResultSerializer serializer = Mock()
-    private collector = new TestReportDataCollector(temp.testDirectory, fileWriter, serializer)
+    private TestOutputSerializer outputSerializer = Mock()
+    private TestResultSerializer resultSerializer = Mock()
+    private collector = new TestReportDataCollector(temp.testDirectory, outputSerializer, resultSerializer)
 
-    def "closes all files when root finishes"() {
+    def "closes output when root finishes"() {
         def root = new DefaultTestSuiteDescriptor("1", "Suite")
         def clazz = new DecoratingTestDescriptor(new DefaultTestClassDescriptor("1.1", "Class"), root)
 
@@ -51,13 +49,13 @@ class TestReportDataCollectorSpec extends Specification {
         collector.afterSuite(clazz, dummyResult)
 
         then:
-        0 * fileWriter.closeAll()
+        0 * outputSerializer.finishOutputs()
 
         when:
         collector.afterSuite(root, dummyResult)
 
         then:
-        1 * fileWriter.closeAll()
+        1 * outputSerializer.finishOutputs()
     }
 
     def "writes results when root finishes"() {
@@ -70,14 +68,14 @@ class TestReportDataCollectorSpec extends Specification {
         collector.afterSuite(clazz, dummyResult)
 
         then:
-        0 * serializer._
+        0 * resultSerializer._
 
         when:
         collector.afterSuite(root, dummyResult)
 
         then:
-        1 * serializer.write(_, temp.testDirectory)
-        0 * serializer._
+        1 * resultSerializer.write(_, temp.testDirectory)
+        0 * resultSerializer._
     }
 
     def "keeps track of test results"() {
@@ -117,7 +115,7 @@ class TestReportDataCollectorSpec extends Specification {
         fooTest.results.find { it.name == 'testMethod2' && it.endTime == 300 && it.duration == 50 }
     }
 
-    def "keeps track of outputs"() {
+    def "writes test outputs"() {
         def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
         def test2 = new DefaultTestDescriptor("1.1.2", "FooTest", "testMethod2")
         def suite = new DefaultTestSuiteDescriptor("1", "Suite")
@@ -128,36 +126,18 @@ class TestReportDataCollectorSpec extends Specification {
         collector.onOutput(test2, new DefaultTestOutputEvent(StdOut, "out"))
 
         then:
-        1 * fileWriter.write(new File(temp.testDirectory, "FooTest.stderr"), "err")
-        1 * fileWriter.write(new File(temp.testDirectory, "FooTest.stdout"), "out")
-        0 * fileWriter._
+        1 * outputSerializer.onOutput("FooTest", StdErr, "err")
+        1 * outputSerializer.onOutput("FooTest", StdOut, "out")
+        0 * outputSerializer._
     }
 
     def "provides outputs"() {
-        def collector = new TestReportDataCollector(temp.testDirectory)
-        def test = new DefaultTestDescriptor("1.1.1", "FooTest", "testMethod")
-        def test2 = new DefaultTestDescriptor("1.1.2", "FooTest", "testMethod2")
-        def test3 = new DefaultTestDescriptor("1.1.3", "BarTest", "testMethod")
+        def writer = new StringWriter()
 
         when:
-        collector.onOutput(test, new DefaultTestOutputEvent(StdErr, "err"))
-        collector.onOutput(test, new DefaultTestOutputEvent(StdErr, "err2"))
-        collector.onOutput(test2, new DefaultTestOutputEvent(StdOut, "out"))
-        collector.onOutput(test3, new DefaultTestOutputEvent(StdOut, "out, don't show"))
-
-        collector.afterSuite(new DefaultTestSuiteDescriptor("1", "suite"), null) //force closing of files
+        collector.writeOutputs("TestClass", StdErr, writer)
 
         then:
-        collectOutput("FooTest", StdErr) == 'errerr2'
-        collectOutput("FooTest", StdOut) == 'out'
-
-        collectOutput("TestWithoutOutputs", StdErr) == ''
-        collectOutput("TestWithoutOutputs", StdOut) == ''
-    }
-
-    String collectOutput(String className, TestOutputEvent.Destination destination) {
-        def writer = new StringWriter()
-        collector.writeOutputs(className, destination, writer)
-        return writer.toString()
+        1 * outputSerializer.writeOutputs("TestClass", StdErr, writer)
     }
 }
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
index cc6745d..5e71868 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
@@ -36,8 +36,13 @@ class TestNGTestClassProcessorTest extends Specification {
 
     private resultProcessor = Mock(TestResultProcessor)
 
-    private TestNGOptions options = new TestNGOptions(reportDir.testDirectory)
-    private TestNGTestClassProcessor processor  = new TestNGTestClassProcessor(reportDir.testDirectory, options, [], new LongIdGenerator(), {} as StandardOutputRedirector, true);
+    private TestNGSpec options
+    private TestNGTestClassProcessor processor
+
+    void setup(){
+        options = Spy(TestNGSpec, constructorArgs:[new TestNGOptions(reportDir.testDirectory)]);
+        processor = new TestNGTestClassProcessor(reportDir.testDirectory, options, [], new LongIdGenerator(), {} as StandardOutputRedirector);
+    }
 
     void "executes the test class"() {
         when:
@@ -144,8 +149,8 @@ class TestNGTestClassProcessorTest extends Specification {
 
     void "includes and excludes groups"() {
         given:
-        options.includeGroups('group1', 'group2')
-        options.excludeGroups('group3')
+        _ * options.getIncludeGroups() >> ['group1', 'group2']
+        _ * options.getExcludeGroups() >> ['group3']
 
         when:
         processor.startProcessing(resultProcessor);
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 98fa678..b7c10a6 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
@@ -18,6 +18,7 @@ package org.gradle.api.plugins
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.reporting.ReportingExtension
+import org.gradle.api.tasks.ClassDirectoryBinary
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.bundling.Jar
 import org.gradle.api.tasks.compile.JavaCompile
@@ -28,6 +29,7 @@ import org.gradle.util.HelperUtil
 import org.gradle.util.Matchers
 import org.gradle.util.SetSystemProperties
 import org.junit.Rule
+
 import spock.lang.Specification
 
 import static org.gradle.util.Matchers.sameCollection
@@ -48,8 +50,9 @@ class JavaBasePluginTest extends Specification {
         javaBasePlugin.apply(project)
 
         then:
-        project.getPlugins().hasPlugin(ReportingBasePlugin)
-        project.getPlugins().hasPlugin(BasePlugin)
+        project.plugins.hasPlugin(ReportingBasePlugin)
+        project.plugins.hasPlugin(BasePlugin)
+        project.plugins.hasPlugin(JavaLanguagePlugin)
         project.convention.plugins.java instanceof JavaPluginConvention
     }
 
@@ -74,7 +77,7 @@ class JavaBasePluginTest extends Specification {
         resources sameCollection(project.sourceSets.custom.resources)
 
         def compileJava = project.tasks['compileCustomJava']
-        compileJava.description == 'Compiles the custom Java source.'
+        compileJava.description == 'Compiles the custom/java source set.'
         compileJava instanceof JavaCompile
         Matchers.dependsOn().matches(compileJava)
         compileJava.classpath.is(project.sourceSets.custom.compileClasspath)
@@ -220,4 +223,54 @@ class JavaBasePluginTest extends Specification {
         task.includes == ['**/pattern*.class'] as Set
     }
 
+    def "adds functional and language source sets for each source set added to the 'sourceSets' container"() {
+        javaBasePlugin.apply(project)
+
+        when:
+        project.sourceSets {
+            custom {
+                java {
+                    srcDirs = [project.file("src1"), project.file("src2")]
+                }
+                resources {
+                    srcDirs = [project.file("resrc1"), project.file("resrc2")]
+                }
+                compileClasspath = project.files("jar1.jar", "jar2.jar")
+            }
+        }
+
+        then:
+        def functional = project.sources.findByName("custom")
+        functional != null
+
+        and:
+        def java = functional.findByName("java")
+        java != null
+        java.source.srcDirs as Set == [project.file("src1"), project.file("src2")] as Set
+        java.compileClasspath.files as Set == project.files("jar1.jar", "jar2.jar") as Set
+
+        and:
+        def resources = functional.findByName("resources")
+        resources != null
+        resources.source.srcDirs as Set == [project.file("resrc1"), project.file("resrc2")] as Set
+    }
+
+    def "adds a class directory binary for each source set added to the 'sourceSets' container"() {
+        javaBasePlugin.apply(project)
+
+        when:
+        project.sourceSets {
+            custom {
+                output.classesDir = project.file("classes")
+                output.resourcesDir = project.file("resources")
+            }
+        }
+
+        then:
+        def binary = project.binaries.jvm.findByName("custom")
+        binary instanceof ClassDirectoryBinary
+        binary.classesDir == project.file("classes")
+        binary.resourcesDir == project.file("resources")
+        binary.source as Set == [project.sources.custom.java, project.sources.custom.resources] as Set
+    }
 }
\ No newline at end of file
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLanguagePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLanguagePluginTest.groovy
new file mode 100644
index 0000000..24c0c45
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLanguagePluginTest.groovy
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins
+
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+
+import spock.lang.Specification
+
+class JavaLanguagePluginTest extends Specification {
+    Project project = HelperUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(JavaLanguagePlugin)
+    }
+
+    def "applies jvm-lang plugin"() {
+        expect:
+        project.plugins.hasPlugin(JvmLanguagePlugin)
+    }
+
+    // TODO once we have a DSL for adding source sets/binaries
+    def "adds a JavaCompile task for every JavaSourceSet added to a ClassDirectoryBinary"() {
+
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy
old mode 100755
new mode 100644
index eb76389..2babac1
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaLibraryDistributionPluginTest.groovy
@@ -16,42 +16,33 @@
 package org.gradle.api.plugins
 
 import org.gradle.api.Project
+import org.gradle.api.distribution.DistributionContainer
+import org.gradle.api.distribution.plugins.DistributionPlugin
 import org.gradle.api.tasks.bundling.Zip
 import org.gradle.util.HelperUtil
 import spock.lang.Specification
 
 class JavaLibraryDistributionPluginTest extends Specification {
-    private final Project project = HelperUtil.createRootProject();
-    private final JavaLibraryDistributionPlugin plugin = new JavaLibraryDistributionPlugin();
+    private final Project project = HelperUtil.builder().withName("test-project").build()
 
     def "applies JavaPlugin and adds convention object with default values"() {
         when:
-        plugin.apply(project)
+        project.apply(plugin: JavaLibraryDistributionPlugin)
 
         then:
         project.plugins.hasPlugin(JavaPlugin.class)
-        project.extensions.getByType(DistributionExtension.class) != null
-        project.distribution.name == project.name
-
+        project.extensions.getByType(DistributionContainer.class) != null
+        project.plugins.hasPlugin(DistributionPlugin.class)
+        project.distributions.main.baseName == project.name
     }
 
     def "adds distZip task to project"() {
         when:
-        plugin.apply(project)
+        project.apply(plugin: JavaLibraryDistributionPlugin)
 
         then:
-        def task = project.tasks[JavaLibraryDistributionPlugin.TASK_DIST_ZIP_NAME]
+        def task = project.tasks.distZip
         task instanceof Zip
-        task.archiveName == "${project.distribution.name}.zip"
-    }
-
-    public void "distribution name is configurable"() {
-        when:
-        plugin.apply(project)
-        project.distribution.name = "SuperApp";
-
-        then:
-        def distZipTask = project.tasks[JavaLibraryDistributionPlugin.TASK_DIST_ZIP_NAME]
-        distZipTask.archiveName == "SuperApp.zip"
+        task.archiveName == "test-project.zip"
     }
 }
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 8d7b3a8..5bcbb76 100644
--- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
@@ -20,6 +20,7 @@ import org.gradle.api.DefaultTask
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.artifacts.Dependency
+import org.gradle.api.internal.java.JavaLibrary
 import org.gradle.api.internal.plugins.EmbeddableJavaProject
 import org.gradle.api.internal.project.DefaultProject
 import org.gradle.api.reporting.ReportingExtension
@@ -93,7 +94,17 @@ class JavaPluginTest {
         def archivesConfiguration = project.configurations.getByName(Dependency.ARCHIVES_CONFIGURATION)
         assertThat(archivesConfiguration.artifacts.collect { it.archiveTask }, equalTo([project.tasks.getByName(JavaPlugin.JAR_TASK_NAME)]))
     }
-    
+
+    @Test public void addsJavaLibraryComponent() {
+        javaPlugin.apply(project)
+
+        def jarTask = project.tasks.getByName(JavaPlugin.JAR_TASK_NAME)
+
+        JavaLibrary javaLibrary = project.components.getByName("java")
+        assertThat(javaLibrary.artifacts.collect {it.archiveTask}, equalTo([jarTask]))
+        assertThat(javaLibrary.runtimeDependencies, equalTo(project.configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME).allDependencies))
+    }
+
     @Test public void createsStandardSourceSetsAndAppliesMappings() {
         javaPlugin.apply(project)
 
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JvmLanguagePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JvmLanguagePluginTest.groovy
new file mode 100644
index 0000000..3ced8ca
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JvmLanguagePluginTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins
+
+import org.gradle.api.internal.plugins.ProcessResources
+import org.gradle.api.tasks.ClassDirectoryBinary
+import org.gradle.api.tasks.JvmBinaryContainer
+import org.gradle.api.tasks.ResourceSet
+import org.gradle.util.HelperUtil
+
+import spock.lang.Specification
+
+class JvmLanguagePluginTest extends Specification {
+    def project = HelperUtil.createRootProject()
+    def jvmLanguagePlugin = project.plugins.apply(JvmLanguagePlugin)
+
+    def "adds a 'binaries.jvm' container to the project"() {
+        def binaries = project.extensions.findByName("binaries")
+
+        expect:
+        binaries != null
+        binaries.jvm instanceof JvmBinaryContainer
+    }
+
+    def "allows the JvmBinaryContainer to be looked up on the plugin"() {
+        expect:
+        jvmLanguagePlugin.jvmBinaryContainer instanceof JvmBinaryContainer
+    }
+
+    def "adds a 'classes' task for every ClassDirectoryBinary added to the container"() {
+        when:
+        def binary = project.binaries.jvm.create("prod", ClassDirectoryBinary)
+
+        then:
+        binary.classesDir == new File("$project.buildDir/classes/prod")
+        project.tasks.findByName("prodClasses") != null
+    }
+
+    def "adds a 'processResources' task for every ResourceSet added to a ClassDirectoryBinary"() {
+        ClassDirectoryBinary binary = project.binaries.jvm.create("prod", ClassDirectoryBinary)
+        ResourceSet resources = project.sources.create("main").create("resources", ResourceSet)
+
+        when:
+        binary.source.add(resources)
+
+        then:
+        project.tasks.size() == old(project.tasks.size()) + 1
+        project.tasks.findByName("processProdResources") instanceof ProcessResources
+    }
+}
diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/LanguageBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/LanguageBasePluginTest.groovy
new file mode 100644
index 0000000..626f2ab
--- /dev/null
+++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/LanguageBasePluginTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.tasks.BinariesContainer
+import org.gradle.api.tasks.ProjectSourceSet
+import org.gradle.api.tasks.ResourceSet
+import org.gradle.util.HelperUtil
+
+import spock.lang.Specification
+
+class LanguageBasePluginTest extends Specification {
+    Project project = HelperUtil.createRootProject()
+
+    def setup() {
+        project.plugins.apply(LanguageBasePlugin)
+    }
+
+    def "adds a 'binaries' container to the project"() {
+        expect:
+        project.extensions.findByName("binaries") instanceof BinariesContainer
+    }
+
+    def "adds a 'sources' container to the project"() {
+        expect:
+        project.extensions.findByName("sources") instanceof ProjectSourceSet
+    }
+
+    def "registers the 'ResourceSet' type for each functional source set added to the 'sources' container"() {
+        when:
+        project.sources.create("custom")
+        project.sources.custom.create("resources", ResourceSet)
+
+        then:
+        project.sources.custom.resources instanceof ResourceSet
+    }
+}
diff --git a/subprojects/publish/publish.gradle b/subprojects/publish/publish.gradle
index 239b19c..6d7a3dc 100644
--- a/subprojects/publish/publish.gradle
+++ b/subprojects/publish/publish.gradle
@@ -17,6 +17,9 @@
 dependencies {
     groovy libraries.groovy
     compile project(':core')
+
+    integTestRuntime project(":ivy")
+    integTestRuntime project(":maven")
 }
 
 useTestFixtures()
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/Publication.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/Publication.java
similarity index 100%
rename from subprojects/publish/src/main/java/org/gradle/api/publish/Publication.java
rename to subprojects/publish/src/main/groovy/org/gradle/api/publish/Publication.java
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublicationContainer.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublicationContainer.java
new file mode 100644
index 0000000..fc1c6ca
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublicationContainer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.NamedDomainObjectSet;
+
+/**
+ * A {@code PublicationContainer} is responsible for creating and managing {@link Publication} instances.
+ *
+ * The set of available publication types is dependent on the application of particular plugins:
+ * <ul>
+ *     <li>The {@link org.gradle.api.publish.maven.plugins.MavenPublishPlugin} makes it possible to create {@link org.gradle.api.publish.maven.MavenPublication} instances.</li>
+ *     <li>The {@link org.gradle.api.publish.ivy.plugins.IvyPublishPlugin} makes it possible to create {@link org.gradle.api.publish.ivy.IvyPublication} instances.</li>
+ * </ul>
+ *
+ * See the documentation for {@link PublishingExtension#publications(org.gradle.api.Action)} for more examples of how to create and configure publications.
+ *
+ * @since 1.3
+ * @see Publication
+ * @see PublishingExtension
+ */
+ at Incubating
+public interface PublicationContainer extends NamedDomainObjectSet<Publication> {
+
+    /**
+     * Creates a publication with the specified name and type, adding it to the container.
+     *
+     * <pre autoTested="true">
+     * apply plugin: 'maven-publish'
+     *
+     * publishing.publications.add('publication-name', MavenPublication)
+     * </pre>
+     *
+     * @param name The publication name.
+     * @param type The publication type.
+     * @return The added publication
+     * @throws InvalidUserDataException If type is not a valid publication type, or if a publication named "name" already exists.
+     */
+    <T extends Publication> T add(String name, Class<T> type) throws InvalidUserDataException;
+
+    /**
+     * Creates a publication with the specified name and type, adding it to the container and configuring it with the supplied action.
+     * A {@link groovy.lang.Closure} can be supplied in place of an action, through type coercion.
+     *
+     * <pre autoTested="true">
+     * apply plugin: 'ivy-publish'
+     *
+     * publishing.publications.add('publication-name', IvyPublication) {
+     *     // Configure the ivy publication here
+     * }
+     * </pre>
+     *
+     * @param name The publication name.
+     * @param type The publication type.
+     * @param configuration The action or closure to configure the publication with.
+     * @return The added publication
+     * @throws InvalidUserDataException If type is not a valid publication type, or if a publication named "name" already exists.
+     */
+    <T extends Publication> T add(String name, Class<T> type, Action<? super T> configuration) throws InvalidUserDataException;
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublishingExtension.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublishingExtension.java
new file mode 100644
index 0000000..ccc34aa
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/PublishingExtension.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish;
+
+import org.gradle.api.Action;
+import org.gradle.api.Incubating;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+
+/**
+ * The configuration of how to “publish” the different components of a project.
+ * <p>
+ * This new publishing mechanism will eventually replace the current mechanism of upload tasks and configurations. At this time, it is an
+ * incubating feature and under development.
+ *
+ * @since 1.3
+ */
+ at Incubating
+public interface PublishingExtension {
+
+    /**
+     * The name of this extension when installed by the {@link org.gradle.api.publish.plugins.PublishingPlugin} ({@value}).
+     */
+    String NAME = "publishing";
+
+    /**
+     * The container of possible repositories to publish to.
+     * <p>
+     * See {@link #repositories(org.gradle.api.Action)} for more information.
+     *
+     * @return The container of possible repositories to publish to.
+     */
+    RepositoryHandler getRepositories();
+
+    /**
+     * Configures the container of possible repositories to publish to.
+     *
+     * <pre autoTested="true">
+     * apply plugin: 'publishing'
+     *
+     * publishing {
+     *   repositories {
+     *     // Create an ivy publication destination named “releases”
+     *     ivy {
+     *       name "releases"
+     *       url "http://my.org/ivy-repos/releases"
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * The {@code repositories} block is backed by a {@link RepositoryHandler}, which is the same DSL as that that is used for declaring repositories to consume dependencies from. However,
+     * certain types of repositories that can be created by the repository handler are not valid for publishing, such as {@link org.gradle.api.artifacts.dsl.RepositoryHandler#mavenCentral()}.
+     * <p>
+     * At this time, only repositories created by the {@code ivy()} factory method have any effect. Please see {@link org.gradle.api.publish.ivy.IvyPublication}
+     * for information on how this can be used for publishing to Ivy repositories.
+     *
+     * @param configure The action to configure the container of repositories with.
+     */
+    void repositories(Action<? super RepositoryHandler> configure);
+
+    /**
+     * The publications of the project.
+     * <p>
+     * See {@link #publications(org.gradle.api.Action)} for more information.
+     *
+     * @return The publications of this project.
+     */
+    PublicationContainer getPublications();
+
+    /**
+     * Configures the publications of this project.
+     * <p>
+     * The publications container defines the outgoing publications of the project. That is, the consumable representations of things produced
+     * by building the project. An example of a publication would be an Ivy Module (i.e. {@code ivy.xml} and artifacts), or
+     * Maven Project (i.e. {@code pom.xml} and artifacts).
+     * <p>
+     * Actual publication implementations and the ability to create them are provided by different plugins. The “publishing” plugin itself does not provide any publication types.
+     * For example, given that the 'maven-publish' plugin provides a {@link org.gradle.api.publish.maven.MavenPublication} type, you can create a publication like:
+     * <pre autoTested="true">
+     * apply plugin: 'maven-publish'
+     *
+     * publishing {
+     *   publications {
+     *     myPublicationName(MavenPublication) {
+     *       // Configure the publication here
+     *     }
+     *   }
+     * }
+     * </pre>
+     * <p>
+     * Please see {@link org.gradle.api.publish.ivy.IvyPublication} and {@link org.gradle.api.publish.maven.MavenPublication} for more information on publishing in these specific formats.
+     *
+     * @param configure The action or closure to configure the publications with.
+     */
+    void publications(Action<? super PublicationContainer> configure);
+
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/CompositePublicationFactory.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/CompositePublicationFactory.java
new file mode 100644
index 0000000..04fe530
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/CompositePublicationFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.internal;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.publish.Publication;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class CompositePublicationFactory {
+    private final Map<Class<? extends Publication>, PublicationFactory> factories = new LinkedHashMap<Class<? extends Publication>, PublicationFactory>();
+
+    public void register(Class<? extends Publication> type, PublicationFactory factory) {
+        factories.put(type, factory);
+    }
+
+    public <T extends Publication> T create(Class<T> type, String name) {
+        for (Map.Entry<Class<? extends Publication>, PublicationFactory> entry : factories.entrySet()) {
+            if (type.isAssignableFrom(entry.getKey())) {
+                return type.cast(entry.getValue().create(name));
+            }
+        }
+        throw new InvalidUserDataException("Cannot create publications of type: " + type);
+    }
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublicationContainer.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublicationContainer.java
new file mode 100644
index 0000000..bd4442b
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublicationContainer.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.DefaultNamedDomainObjectSet;
+import org.gradle.api.publish.Publication;
+import org.gradle.internal.reflect.Instantiator;
+
+public class DefaultPublicationContainer extends DefaultNamedDomainObjectSet<Publication> implements PublicationContainerInternal {
+
+    private final CompositePublicationFactory publicationFactories = new CompositePublicationFactory();
+
+    public DefaultPublicationContainer(Instantiator instantiator) {
+        super(Publication.class, instantiator);
+    }
+
+    @Override
+    protected void handleAttemptToAddItemWithNonUniqueName(Publication o) {
+        throw new InvalidUserDataException(String.format("Publication with name '%s' added multiple times", o.getName()));
+    }
+
+    public <T extends Publication> T add(String name, Class<T> type) {
+        T publication = publicationFactories.create(type, name);
+        add(publication);
+        return publication;
+    }
+
+    public <T extends Publication> T add(String name, Class<T> type, Action<? super T> action) {
+        T publication = add(name, type);
+        action.execute(publication);
+        return publication;
+    }
+
+    public void registerFactory(Class<? extends Publication> type, PublicationFactory publicationFactory) {
+        publicationFactories.register(type, publicationFactory);
+    }
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublishingExtension.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublishingExtension.java
new file mode 100644
index 0000000..818dfb3
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/DefaultPublishingExtension.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.internal;
+
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.plugins.DeferredConfigurable;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.PublishingExtension;
+
+ at DeferredConfigurable
+public class DefaultPublishingExtension implements PublishingExtension {
+    private final RepositoryHandler repositories;
+    private final PublicationContainer publications;
+
+    public DefaultPublishingExtension(RepositoryHandler repositories, PublicationContainer publications) {
+        this.repositories = repositories;
+        this.publications = publications;
+    }
+
+    public RepositoryHandler getRepositories() {
+        return repositories;
+    }
+
+    public void repositories(Action<? super RepositoryHandler> configure) {
+        configure.execute(repositories);
+    }
+
+    public PublicationContainer getPublications() {
+        return publications;
+    }
+
+    public void publications(Action<? super PublicationContainer> configure) {
+        configure.execute(publications);
+    }
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/GroovyPublicationContainer.groovy b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/GroovyPublicationContainer.groovy
new file mode 100644
index 0000000..90efb2f
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/GroovyPublicationContainer.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.internal
+
+import org.gradle.internal.reflect.Instantiator
+
+class GroovyPublicationContainer extends DefaultPublicationContainer {
+    GroovyPublicationContainer(Instantiator instantiator) {
+        super(instantiator)
+    }
+
+    def methodMissing(String name, args) {
+        if (args.length == 1 && args[0] instanceof Class) {
+            return add(name, args[0]);
+        }
+        if (args.length == 2 && args[0] instanceof Class && args[1] instanceof Closure) {
+            return add(name, args[0], args[1])
+        }
+        throw new MissingMethodException(name, this.class, args)
+    }
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationContainerInternal.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationContainerInternal.java
new file mode 100644
index 0000000..3e45898
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationContainerInternal.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.internal;
+
+import org.gradle.api.publish.Publication;
+import org.gradle.api.publish.PublicationContainer;
+
+public interface PublicationContainerInternal extends PublicationContainer {
+    void registerFactory(Class<? extends Publication> type, PublicationFactory publicationFactory);
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFactory.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFactory.java
new file mode 100644
index 0000000..65ff686
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFactory.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.publish.internal;
+
+import org.gradle.api.publish.Publication;
+
+public interface PublicationFactory {
+    Publication create(String name);
+}
diff --git a/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFieldValidator.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFieldValidator.java
new file mode 100644
index 0000000..b80d949
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublicationFieldValidator.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.internal;
+
+import org.gradle.api.InvalidUserDataException;
+
+public abstract class PublicationFieldValidator<T extends PublicationFieldValidator> {
+    private final Class<T> type;
+    protected final String publicationName;
+    protected final String name;
+    protected final String value;
+
+    public PublicationFieldValidator(Class<T> type, String publicationName, String name, String value) {
+        this.type = type;
+        this.publicationName = publicationName;
+        this.name = name;
+        this.value = value;
+    }
+
+    public T notNull() {
+        if (value == null) {
+            String message = String.format("%s cannot be null.", name);
+            throw failure(message);
+        }
+        return type.cast(this);
+    }
+
+    public T notEmpty() {
+        notNull();
+        if (value.length() == 0) {
+            throw failure(String.format("%s cannot be empty.", name));
+        }
+        return type.cast(this);
+    }
+
+    public T validInFileName() {
+        if (value == null || value.length() == 0) {
+            return type.cast(this);
+        }
+        // Iterate over unicode characters
+        int offset = 0;
+        while (offset < value.length()) {
+            final int unicodeChar = value.codePointAt(offset);
+            if (Character.isISOControl(unicodeChar)) {
+                throw failure(String.format("%s cannot contain ISO control character '\\u%04x'.", name, unicodeChar));
+            }
+            if ('\\' == unicodeChar || '/' == unicodeChar) {
+                throw failure(String.format("%s cannot contain '%c'.", name, (char) unicodeChar));
+            }
+            offset += Character.charCount(unicodeChar);
+        }
+        return type.cast(this);
+    }
+
+    public T optionalNotEmpty() {
+        if (value != null && value.length() == 0) {
+            throw failure(String.format("%s cannot be an empty string. Use null instead.", name));
+        }
+        return type.cast(this);
+    }
+
+    protected abstract InvalidUserDataException failure(String message);
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/PublishOperation.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublishOperation.java
similarity index 100%
rename from subprojects/publish/src/main/java/org/gradle/api/publish/internal/PublishOperation.java
rename to subprojects/publish/src/main/groovy/org/gradle/api/publish/internal/PublishOperation.java
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/package-info.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/package-info.java
similarity index 100%
rename from subprojects/publish/src/main/java/org/gradle/api/publish/package-info.java
rename to subprojects/publish/src/main/groovy/org/gradle/api/publish/package-info.java
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
new file mode 100644
index 0000000..adb19b8
--- /dev/null
+++ b/subprojects/publish/src/main/groovy/org/gradle/api/publish/plugins/PublishingPlugin.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.publish.plugins;
+
+import org.gradle.api.*;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
+import org.gradle.api.publish.PublicationContainer;
+import org.gradle.api.publish.PublishingExtension;
+import org.gradle.api.publish.internal.DefaultPublishingExtension;
+import org.gradle.api.publish.internal.GroovyPublicationContainer;
+import org.gradle.internal.reflect.Instantiator;
+
+import javax.inject.Inject;
+
+/**
+ * Installs a {@link org.gradle.api.publish.PublishingExtension} with name {@value org.gradle.api.publish.PublishingExtension#NAME}.
+ *
+ * @since 1.3
+ */
+ at Incubating
+public class PublishingPlugin implements Plugin<Project> {
+
+    public static final String PUBLISH_LIFECYCLE_TASK_NAME = "publish";
+
+    private final Instantiator instantiator;
+    private final ArtifactPublicationServices publicationServices;
+
+    @Inject
+    public PublishingPlugin(ArtifactPublicationServices publicationServices, Instantiator instantiator) {
+        this.publicationServices = publicationServices;
+        this.instantiator = instantiator;
+    }
+
+    public void apply(Project project) {
+        RepositoryHandler repositories = publicationServices.createRepositoryHandler();
+        PublicationContainer publications = instantiator.newInstance(GroovyPublicationContainer.class, instantiator);
+        project.getExtensions().create(PublishingExtension.NAME, DefaultPublishingExtension.class, repositories, publications);
+
+        // Access the publishing extension to ensure that it is configured and tasks are created
+        project.afterEvaluate(new Action<Project>() {
+            public void execute(Project project) {
+                project.getExtensions().getByType(PublishingExtension.class);
+            }
+        });
+
+        Task publishLifecycleTask = project.getTasks().add(PUBLISH_LIFECYCLE_TASK_NAME);
+        publishLifecycleTask.setDescription("Publishes all publications produced by this project.");
+        publishLifecycleTask.setGroup("publishing");
+    }
+}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/package-info.java b/subprojects/publish/src/main/groovy/org/gradle/api/publish/plugins/package-info.java
similarity index 100%
rename from subprojects/publish/src/main/java/org/gradle/api/publish/plugins/package-info.java
rename to subprojects/publish/src/main/groovy/org/gradle/api/publish/plugins/package-info.java
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/PublicationContainer.java b/subprojects/publish/src/main/java/org/gradle/api/publish/PublicationContainer.java
deleted file mode 100644
index 9512f27..0000000
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/PublicationContainer.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish;
-
-import org.gradle.api.Incubating;
-import org.gradle.api.NamedDomainObjectSet;
-
-/**
- * A {@code PublicationContainer} is responsible for declaring and managing publications.
- *
- * Publications cannot be added to a publication container by users at this time. Publication plugins
- * are responsible for creating {@link Publication} instances in the container.
- *
- * See the documentation for the Ivy Publishing plugin for more information.
- *
- * @since 1.3
- * @see Publication
- */
- at Incubating
-public interface PublicationContainer extends NamedDomainObjectSet<Publication> {
-
-    /**
-     * {@inheritDoc}
-     */
-    Publication getByName(String name) throws UnknownPublicationException;
-
-    /**
-     * {@inheritDoc}
-     */
-    Publication getAt(String name) throws UnknownPublicationException;
-
-}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java b/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java
deleted file mode 100644
index 90854bb..0000000
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/PublishingExtension.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish;
-
-import org.gradle.api.Action;
-import org.gradle.api.Incubating;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-
-/**
- * The configuration of how to “publish” the different components of a project.
- * <p>
- * This new publishing mechanism will eventually replace the current mechanism of upload tasks and configurations. At this time, it is an
- * incubating feature and under development.
- *
- * @since 1.3
- */
- at Incubating
-public interface PublishingExtension {
-
-    /**
-     * The name of this extension when installed by the {@link org.gradle.api.publish.plugins.PublishingPlugin} ({@value}).
-     */
-    String NAME = "publishing";
-
-    /**
-     * The container of possible repositories to publish to.
-     * <p>
-     * See {@link #repositories(org.gradle.api.Action)} for more information.
-     *
-     * @return The container of possible repositories to publish to.
-     */
-    RepositoryHandler getRepositories();
-
-    /**
-     * Configures the container of possible repositories to publish to.
-     *
-     * <pre autoTested="true">
-     * apply plugin: 'publishing'
-     *
-     * publishing {
-     *   repositories {
-     *     // Create an ivy publication destination named “releases”
-     *     ivy {
-     *       name "releases"
-     *       url "http://my.org/ivy-repos/releases"
-     *     }
-     *   }
-     * }
-     * </pre>
-     *
-     * The {@code repositories} block is backed by a {@link RepositoryHandler}, which is the same DSL as that that is used for declaring repositories to consume dependencies from. However,
-     * certain types of repositories that can be created by the repository handler are not valid for publishing, such as {@link org.gradle.api.artifacts.dsl.RepositoryHandler#mavenCentral()}.
-     * <p>
-     * At this time, only repositories created by the {@code ivy()} factory method have any effect. Please see {@link org.gradle.api.publish.ivy.IvyPublication}
-     * for information on how this can be used for publishing to Ivy repositories.
-     *
-     * @param configure The action to configure the container of repositories with.
-     */
-    void repositories(Action<? super RepositoryHandler> configure);
-
-    /**
-     * The publications of the project.
-     * <p>
-     * See {@link #publications(org.gradle.api.Action)} for more information.
-     *
-     * @return The publications of this project.
-     */
-    PublicationContainer getPublications();
-
-    /**
-     * Configures the publications of this project.
-     * <p>
-     * The publications container defines the outgoing publications of the project. That is, the consumable representations of things produced
-     * by building the project. An example of a publication would be an Ivy Module (i.e. {@code ivy.xml} and artifacts), or
-     * Maven Project (i.e. {@code pom.xml} and artifacts).
-     * <p>
-     * The ability to create different kinds of publications is provided by different plugins. The “publishing” plugin itself does not provide a way
-     * to create publications.
-     * <p>
-     * Please see {@link org.gradle.api.publish.ivy.IvyPublication} and {@link org.gradle.api.publish.maven.MavenPublication} for more information on publishing in these specific formats.
-     *
-     * @param configure The action or closure to configure the publications with.
-     */
-    void publications(Action<? super PublicationContainer> configure);
-
-}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/UnknownPublicationException.java b/subprojects/publish/src/main/java/org/gradle/api/publish/UnknownPublicationException.java
deleted file mode 100644
index c54fcdc..0000000
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/UnknownPublicationException.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish;
-
-import org.gradle.api.Incubating;
-import org.gradle.api.UnknownDomainObjectException;
-
-/**
- * An {@code UnknownPublicationException} is thrown when a configuration referenced by name cannot be found.
- *
- * @since 1.3
- */
- at Incubating
-public class UnknownPublicationException extends UnknownDomainObjectException {
-    public UnknownPublicationException(String message) {
-        super(message);
-    }
-}
\ No newline at end of file
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublicationContainer.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublicationContainer.java
deleted file mode 100644
index 2f78c02..0000000
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublicationContainer.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.internal;
-
-import org.gradle.api.UnknownDomainObjectException;
-import org.gradle.api.internal.DefaultNamedDomainObjectSet;
-import org.gradle.api.publish.Publication;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.UnknownPublicationException;
-import org.gradle.internal.reflect.Instantiator;
-
-public class DefaultPublicationContainer extends DefaultNamedDomainObjectSet<Publication> implements PublicationContainer {
-
-    public DefaultPublicationContainer(Instantiator instantiator) {
-        super(Publication.class, instantiator);
-    }
-
-    @Override
-    protected UnknownDomainObjectException createNotFoundException(String name) {
-        return new UnknownPublicationException(String.format("Publication with name '%s' not found", name));
-    }
-}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublishingExtension.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublishingExtension.java
deleted file mode 100644
index 23c7af1..0000000
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/DefaultPublishingExtension.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.internal;
-
-import org.gradle.api.Action;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.PublishingExtension;
-
-public class DefaultPublishingExtension implements PublishingExtension {
-
-    private final RepositoryHandler repositories;
-    private final PublicationContainer publications;
-
-    public DefaultPublishingExtension(RepositoryHandler repositories, PublicationContainer publications) {
-        this.repositories = repositories;
-        this.publications = publications;
-    }
-
-    public RepositoryHandler getRepositories() {
-        return repositories;
-    }
-
-    public void repositories(Action<? super RepositoryHandler> configure) {
-        configure.execute(repositories);
-    }
-
-    public PublicationContainer getPublications() {
-        return publications;
-    }
-
-    public void publications(Action<? super PublicationContainer> configure) {
-        configure.execute(publications);
-    }
-}
diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java b/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java
deleted file mode 100644
index 4e00dda..0000000
--- a/subprojects/publish/src/main/java/org/gradle/api/publish/plugins/PublishingPlugin.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2012 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.api.publish.plugins;
-
-import org.gradle.api.Incubating;
-import org.gradle.api.Plugin;
-import org.gradle.api.Project;
-import org.gradle.api.artifacts.dsl.RepositoryHandler;
-import org.gradle.api.internal.artifacts.ArtifactPublicationServices;
-import org.gradle.api.publish.PublicationContainer;
-import org.gradle.api.publish.PublishingExtension;
-import org.gradle.api.publish.internal.DefaultPublicationContainer;
-import org.gradle.api.publish.internal.DefaultPublishingExtension;
-import org.gradle.internal.reflect.Instantiator;
-
-import javax.inject.Inject;
-
-/**
- * Installs a {@link org.gradle.api.publish.PublishingExtension} with name {@value org.gradle.api.publish.PublishingExtension#NAME}.
- *
- * @since 1.3
- */
- at Incubating
-public class PublishingPlugin implements Plugin<Project> {
-
-    public static final String PUBLISH_LIFECYCLE_TASK_NAME = "publish";
-
-    private final Instantiator instantiator;
-    private final ArtifactPublicationServices publicationServices;
-
-    @Inject
-    public PublishingPlugin(ArtifactPublicationServices publicationServices, Instantiator instantiator) {
-        this.publicationServices = publicationServices;
-        this.instantiator = instantiator;
-    }
-
-    public void apply(Project project) {
-        RepositoryHandler repositories = publicationServices.createRepositoryHandler();
-        PublicationContainer publications = instantiator.newInstance(DefaultPublicationContainer.class, instantiator);
-        project.getExtensions().create(PublishingExtension.NAME, DefaultPublishingExtension.class, repositories, publications);
-
-        project.getTasks().add(PUBLISH_LIFECYCLE_TASK_NAME);
-    }
-}
diff --git a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy
index 566d5ff..5c8f84d 100644
--- a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy
+++ b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/DefaultPublicationContainerTest.groovy
@@ -15,20 +15,19 @@
  */
 
 package org.gradle.api.publish.internal
-
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.UnknownDomainObjectException
 import org.gradle.api.internal.ThreadGlobalInstantiator
 import org.gradle.api.publish.Publication
-import org.gradle.api.publish.PublicationContainer
-import org.gradle.api.publish.UnknownPublicationException
 import org.gradle.internal.reflect.Instantiator
 import spock.lang.Specification
 
 class DefaultPublicationContainerTest extends Specification {
 
     Instantiator instantiator = ThreadGlobalInstantiator.getOrCreate()
-    PublicationContainer container = instantiator.newInstance(DefaultPublicationContainer, instantiator)
+    GroovyPublicationContainer container = instantiator.newInstance(GroovyPublicationContainer, instantiator)
 
-    def "right exception is thrown on unknown access"() {
+    def "exception is thrown on unknown access"() {
         given:
         container.add(publication("foo"))
 
@@ -39,15 +38,96 @@ class DefaultPublicationContainerTest extends Specification {
         container.getByName("notHere")
 
         then:
-        def e = thrown(UnknownPublicationException)
-        e.message == "Publication with name 'notHere' not found"
+        def e = thrown(UnknownDomainObjectException)
+        e.message == "Publication with name 'notHere' not found."
+    }
+
+    def "can add and configure publication with API"() {
+        given:
+        Publication pub = publication("test")
+        PublicationFactory factory = Mock()
+        container.registerFactory(Publication, factory)
+
+        when:
+        container.add("name", Publication) {
+            value = 2
+        }
+
+        then:
+        1 * factory.create("name") >> pub
+
+        and:
+        container.getByName("test") == pub
+        pub.value == 2
+    }
+
+    def "can add publication with DSL"() {
+        given:
+        Publication testPub = publication("test")
+        PublicationFactory factory = Mock()
+        container.registerFactory(Publication, factory)
+
+        when:
+        container.publication_name(Publication)
+
+        then:
+        1 * factory.create("publication_name") >> testPub
+
+        and:
+        container.getByName("test") == testPub
+    }
+
+    def "can add and configure publication with DSL"() {
+        given:
+        TestPublication testPub = publication("test")
+        PublicationFactory factory = Mock()
+        container.registerFactory(TestPublication, factory)
+
+        when:
+        container.publication_name(TestPublication) {
+            value = 2
+        }
+
+        then:
+        1 * factory.create("publication_name") >> testPub
+
+        and:
+        container.getByName("test") == testPub
+        testPub.value == 2
     }
 
-    Publication publication(String name) {
-        new Publication() {
-            String getName() {
-                name
-            }
+    def "cannot add multiple publications with same name"() {
+        given:
+        PublicationFactory factory = Mock()
+        container.registerFactory(TestPublication, factory)
+
+        when:
+        container.publication_name(TestPublication)
+
+        then:
+        1 * factory.create("publication_name") >> publication("test")
+
+        when:
+        container.publication_name(TestPublication)
+
+        then:
+        1 * factory.create("publication_name") >> publication("test")
+
+        and:
+        def t = thrown InvalidUserDataException
+        t.message == "Publication with name 'test' added multiple times"
+    }
+
+    TestPublication publication(String name) {
+        new TestPublication(name)
+    }
+
+    class TestPublication implements Publication {
+        def value = 0
+        String name
+
+        TestPublication(name) {
+            this.name = name
         }
     }
 }
diff --git a/subprojects/reporting/reporting.gradle b/subprojects/reporting/reporting.gradle
index b199a61..e094d99 100644
--- a/subprojects/reporting/reporting.gradle
+++ b/subprojects/reporting/reporting.gradle
@@ -1,6 +1,10 @@
 dependencies {
     groovy libraries.groovy
     compile project(':core')
+    compile 'com.googlecode.jatl:jatl:0.2.2'
+
+    testCompile libraries.jsoup
+    integTestRuntime project(':codeQuality')
 }
 
 useTestFixtures()
\ No newline at end of file
diff --git a/subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/plugins/BuildDashboardPluginIntegrationTest.groovy b/subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/plugins/BuildDashboardPluginIntegrationTest.groovy
new file mode 100644
index 0000000..cc4e082
--- /dev/null
+++ b/subprojects/reporting/src/integTest/groovy/org/gradle/api/reporting/plugins/BuildDashboardPluginIntegrationTest.groovy
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting.plugins
+
+import org.gradle.integtests.fixtures.WellBehavedPluginTest
+import org.gradle.test.fixtures.file.TestFile
+import org.jsoup.Jsoup
+
+import static org.gradle.api.reporting.plugins.BuildDashboardPlugin.BUILD_DASHBOARD_TASK_NAME
+
+class BuildDashboardPluginIntegrationTest extends WellBehavedPluginTest {
+
+    void setup() {
+        writeBuildFile()
+        writeProjectFiles(testDirectory)
+    }
+
+    private void writeProjectFiles(TestFile root) {
+        root.file("src/main/groovy/org/gradle/class1.groovy") << "package org.gradle; class class1 { }"
+        root.file("config/codenarc/rulesets.groovy") << ""
+    }
+
+    private TestFile getBuildDashboardFile() {
+        file("build/reports/buildDashboard/index.html")
+    }
+
+    private int getDashboardLinksCount() {
+        Jsoup.parse(buildDashboardFile, null).select('ul li a').size()
+    }
+
+    private void writeBuildFile() {
+        buildFile << """
+            apply plugin: 'build-dashboard'
+
+            allprojects {
+                apply plugin: 'groovy'
+                apply plugin: 'codenarc'
+
+                codenarc {
+                    configFile = file('config/codenarc/rulesets.groovy')
+                }
+
+                repositories {
+                    mavenCentral()
+                }
+
+                dependencies {
+                    codenarc 'org.codenarc:CodeNarc:0.16.1'
+                    groovy localGroovy()
+                }
+            }
+        """
+    }
+
+    private void setupSubproject() {
+        writeProjectFiles(file('subproject'))
+        file('settings.gradle') << "include 'subproject'"
+    }
+
+    String getPluginId() {
+        'build-dashboard'
+    }
+
+    String getMainTask() {
+        BUILD_DASHBOARD_TASK_NAME
+    }
+
+    void 'running buildDashboard task on its own generates a link to it in the dashboard'() {
+        when:
+        run(BUILD_DASHBOARD_TASK_NAME)
+
+        then:
+        dashboardLinksCount == 1
+    }
+
+    void 'running buildDashboard task after some report generating task generates link to it in the dashboard'() {
+        when:
+        run('check', BUILD_DASHBOARD_TASK_NAME)
+
+        then:
+        dashboardLinksCount == 2
+    }
+
+    void 'no report is generated if it is disabled'() {
+        given:
+        buildFile << """
+            $BUILD_DASHBOARD_TASK_NAME {
+                reports.html.enabled = false
+            }
+        """
+
+        when:
+        run(BUILD_DASHBOARD_TASK_NAME)
+
+        then:
+        !buildDashboardFile.exists()
+    }
+
+    void 'buildDashboard is incremental'() {
+        expect:
+        run(BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in nonSkippedTasks
+        run(BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in skippedTasks
+
+        when:
+        buildDashboardFile.delete()
+
+        then:
+        run(BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in nonSkippedTasks
+    }
+
+    void 'enabling an additional report renders buildDashboard out-of-date'() {
+        expect:
+        run('check', BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in nonSkippedTasks
+
+        when:
+        buildFile << """
+            codenarcMain {
+                reports.text.enabled = true
+            }
+        """
+
+        then:
+        run('check', BUILD_DASHBOARD_TASK_NAME) && ":$BUILD_DASHBOARD_TASK_NAME".toString() in nonSkippedTasks
+        dashboardLinksCount == 3
+    }
+
+    void 'reports from subprojects are aggregated'() {
+        given:
+        setupSubproject()
+
+        when:
+        run('check', BUILD_DASHBOARD_TASK_NAME)
+
+        then:
+        dashboardLinksCount == 3
+    }
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/BuildDashboardReports.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/BuildDashboardReports.java
new file mode 100644
index 0000000..e899f26
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/BuildDashboardReports.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting;
+
+import org.gradle.api.Incubating;
+
+/**
+ * The reporting configuration for the the {@link GenerateBuildDashboard} task.
+ */
+ at Incubating
+public interface BuildDashboardReports extends ReportContainer<SingleFileReport> {
+    /**
+     * The build dashboard html report
+     *
+     * @return The build dashboard html report
+     */
+    SingleFileReport getHtml();
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/GenerateBuildDashboard.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/GenerateBuildDashboard.java
new file mode 100644
index 0000000..0b12c6b
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/GenerateBuildDashboard.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting;
+
+import groovy.lang.Closure;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Incubating;
+import org.gradle.api.NamedDomainObjectSet;
+import org.gradle.api.Transformer;
+import org.gradle.api.reporting.internal.BuildDashboardGenerator;
+import org.gradle.api.reporting.internal.DefaultBuildDashboardReports;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.internal.reflect.Instantiator;
+import org.gradle.util.CollectionUtils;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Generates build dashboard report.
+ */
+ at Incubating
+public class GenerateBuildDashboard extends DefaultTask implements Reporting<BuildDashboardReports> {
+
+    private Set<Reporting> aggregated = new LinkedHashSet<Reporting>();
+
+    @Nested
+    private final DefaultBuildDashboardReports reports;
+
+    @Inject
+    public GenerateBuildDashboard(Instantiator instantiator) {
+        reports = instantiator.newInstance(DefaultBuildDashboardReports.class, this);
+        reports.getHtml().setEnabled(true);
+    }
+
+    /**
+     * A set of report files that will be aggregated by the generated report.
+     * @return A set of input report files.
+     */
+    @Input
+    public Set<File> getInputReportsFiles() {
+        return CollectionUtils.collect(getEnabledInputReports(), new Transformer<File, Report>() {
+            public File transform(Report original) {
+                return original.getDestination();
+            }
+        });
+    }
+
+    private Set<Report> getEnabledInputReports() {
+        Set<NamedDomainObjectSet<Report>> enabledReportSets = CollectionUtils.collect(aggregated, new Transformer<NamedDomainObjectSet<Report>, Reporting>() {
+            public NamedDomainObjectSet<Report> transform(Reporting reporting) {
+                return reporting.getReports().getEnabled();
+            }
+        });
+        return new LinkedHashSet<Report>(CollectionUtils.flattenToList(Report.class, enabledReportSets));
+    }
+
+    /**
+     * Configures which reportings are to be aggregated in the build dashboard report generated by this task.
+     *
+     * <pre>
+     * buildDashboard {
+     *   aggregate codenarcMain, checkstyleMain
+     * }
+     * </pre>
+     *
+     * @param reportings an array of {@link Reporting} instances that are to be aggregated
+     */
+    public void aggregate(Reporting... reportings) {
+        aggregated.addAll(Arrays.asList(reportings));
+    }
+
+    /**
+     * The reports to be generated by this task.
+     *
+     * @return The reports container
+     */
+    public BuildDashboardReports getReports() {
+        return reports;
+    }
+
+    /**
+     * Configures the reports to be generated by this task.
+     *
+     * The contained reports can be configured by name and closures.
+     *
+     * <pre>
+     * buildDashboard {
+     *   reports {
+     *     html {
+     *       destination "build/dashboard.html"
+     *     }
+     *   }
+     * }
+     * </pre>
+     *
+     * @param closure The configuration
+     * @return The reports container
+     */
+    public BuildDashboardReports reports(Closure closure) {
+        return (BuildDashboardReports) reports.configure(closure);
+    }
+
+    @TaskAction
+    void run() {
+        if (getReports().getHtml().isEnabled()) {
+            BuildDashboardGenerator generator = new BuildDashboardGenerator(getEnabledInputReports(), reports.getHtml().getDestination());
+            generator.generate();
+        }else {
+            setDidWork(false);
+        }
+    }
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java
index a2ef25d..b371e8f 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/Report.java
@@ -51,6 +51,13 @@ public interface Report extends Serializable, Configurable<Report> {
     String getName();
 
     /**
+     * A more descriptive name of this report. Used when the report is referenced for end users.
+     *
+     * @return A more descriptive name of this report.
+     */
+    String getDisplayName();
+
+    /**
      * Whether or not this report should be generated by whatever generates it.
      *
      * If {@code true}, the generator of this report will generate it at the appropriate time.
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/BuildDashboardGenerator.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/BuildDashboardGenerator.java
new file mode 100644
index 0000000..e68023e
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/BuildDashboardGenerator.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting.internal;
+
+import com.googlecode.jatl.Html;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.reporting.Report;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.CollectionUtils;
+import org.gradle.util.GFileUtils;
+
+import java.io.*;
+import java.util.Set;
+
+public class BuildDashboardGenerator {
+    private Set<Report> reports;
+    private File outputFile;
+
+    public BuildDashboardGenerator(Set<Report> reports, File outputFile) {
+        this.reports = reports;
+        this.outputFile = outputFile;
+    }
+
+    public void generate() {
+        BufferedWriter writer = null;
+        try {
+            GFileUtils.parentMkdirs(outputFile);
+            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8"));
+            generate(writer);
+            copyCss();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        }
+    }
+
+    private void copyCss() {
+        File cssFile = new File(outputFile.getParent(), "base-style.css");
+        GFileUtils.copyURLToFile(getClass().getResource("/org/gradle/reporting/base-style.css"), cssFile);
+    }
+
+    private Set<Report> getReportsWithExistingDestination() {
+        return CollectionUtils.filter(reports, new Spec<Report>() {
+            public boolean isSatisfiedBy(Report report) {
+                File destination = report.getDestination();
+                return destination != null && destination.exists();
+            }
+        });
+    }
+
+    private void generate(BufferedWriter writer) {
+        new Html(writer) {{
+            html();
+                head();
+                    meta().httpEquiv("Content-Type").content("text/html; charset=utf-8");
+                    link().rel("stylesheet").type("text/css").href("base-style.css");
+                end(2);
+                body();
+                div().id("content");
+                    Set<Report> reports = getReportsWithExistingDestination();
+                    if (reports.size() > 0) {
+                        h1().text("Available build reports:").end();
+                        ul();
+                        for (Report report : reports) {
+                            li();
+                                a().href(GFileUtils.relativePath(outputFile.getParentFile(), report.getDestination())).text(report.getDisplayName());
+                            end(2);
+                        }
+                        end();
+                    } else {
+                        h1().text("There are no build reports available.").end();
+                    }
+            endAll();
+        }};
+    }
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultBuildDashboardReports.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultBuildDashboardReports.java
new file mode 100644
index 0000000..171c2d7
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/DefaultBuildDashboardReports.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting.internal;
+
+import org.gradle.api.Task;
+import org.gradle.api.reporting.BuildDashboardReports;
+import org.gradle.api.reporting.SingleFileReport;
+
+public class DefaultBuildDashboardReports extends TaskReportContainer<SingleFileReport> implements BuildDashboardReports {
+
+    public DefaultBuildDashboardReports(Task task) {
+        super(SingleFileReport.class, task);
+
+        add(TaskGeneratedSingleFileReport.class, "html", task);
+    }
+
+    public SingleFileReport getHtml() {
+        return getByName("html");
+    }
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java
index 30dd192..03e62b7 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/SimpleReport.java
@@ -26,14 +26,16 @@ import java.io.File;
 public class SimpleReport implements Report {
                           
     private String name;
+    private String displayName;
     private FileResolver fileResolver;
 
     private Object destination;
     private boolean enabled;
     private OutputType outputType;
 
-    public SimpleReport(String name, OutputType outputType, FileResolver fileResolver) {
+    public SimpleReport(String name, String displayName, OutputType outputType, FileResolver fileResolver) {
         this.name = name;
+        this.displayName = displayName;
         this.fileResolver = fileResolver;
         this.outputType = outputType;
     }
@@ -42,6 +44,10 @@ public class SimpleReport implements Report {
         return name;
     }
 
+    public String getDisplayName() {
+        return displayName;
+    }
+
     public String toString() {
         return String.format("Report %s", getName());
     }
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java
index e00e60f..f99a53f 100644
--- a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/internal/TaskGeneratedReport.java
@@ -21,8 +21,12 @@ import org.gradle.api.internal.project.ProjectInternal;
 
 public class TaskGeneratedReport extends SimpleReport {
 
+    private static String getDisplayName(String name, Task task) {
+        return String.format("Report generated by task '%s' (%s)", task.getPath(), name);
+    }
+
     public TaskGeneratedReport(String name, OutputType outputType, Task task) {
-        super(name, outputType, ((ProjectInternal)(task.getProject())).getFileResolver());
+        super(name, getDisplayName(name, task), outputType, ((ProjectInternal)(task.getProject())).getFileResolver());
     }
 
 }
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.groovy b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.groovy
new file mode 100644
index 0000000..22ccb22
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/BuildDashboardPlugin.groovy
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.api.reporting.plugins
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.reporting.GenerateBuildDashboard
+import org.gradle.api.reporting.Reporting
+import org.gradle.api.reporting.ReportingExtension
+import org.gradle.api.reporting.SingleFileReport
+
+/**
+ * <p>A {@link Plugin} which allows to generate build dashboard report.</p>
+ */
+ at Incubating
+public class BuildDashboardPlugin implements Plugin<ProjectInternal> {
+    public static final String BUILD_DASHBOARD_TASK_NAME = "buildDashboard"
+
+    public void apply(ProjectInternal project) {
+        project.plugins.apply(ReportingBasePlugin)
+
+        GenerateBuildDashboard buildDashboardTask = project.tasks.add(BUILD_DASHBOARD_TASK_NAME, GenerateBuildDashboard)
+
+        project.allprojects.each { aggregateReportings(it, buildDashboardTask) }
+        addReportDestinationConventionMapping(project, buildDashboardTask.reports.html);
+    }
+
+    private void addReportDestinationConventionMapping(ProjectInternal project, SingleFileReport buildDashboardReport) {
+        buildDashboardReport.conventionMapping.map('destination') {
+            project.extensions.getByType(ReportingExtension).file('buildDashboard/index.html')
+        }
+    }
+
+    private void aggregateReportings(Project project, GenerateBuildDashboard buildDashboardTask) {
+        project.tasks.withType(Reporting).all {
+            buildDashboardTask.aggregate(it)
+        }
+    }
+}
diff --git a/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/package-info.java b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/package-info.java
new file mode 100644
index 0000000..e55cb16
--- /dev/null
+++ b/subprojects/reporting/src/main/groovy/org/gradle/api/reporting/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Plugins for reporting
+ */
+package org.gradle.api.reporting.plugins;
\ No newline at end of file
diff --git a/subprojects/reporting/src/main/resources/META-INF/gradle-plugins/build-dashboard.properties b/subprojects/reporting/src/main/resources/META-INF/gradle-plugins/build-dashboard.properties
new file mode 100755
index 0000000..b867973
--- /dev/null
+++ b/subprojects/reporting/src/main/resources/META-INF/gradle-plugins/build-dashboard.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.reporting.plugins.BuildDashboardPlugin
diff --git a/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/GenerateBuildDashboardSpec.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/GenerateBuildDashboardSpec.groovy
new file mode 100644
index 0000000..debd8c7
--- /dev/null
+++ b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/GenerateBuildDashboardSpec.groovy
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.reporting
+
+import org.gradle.util.HelperUtil
+import org.junit.Test
+import spock.lang.Specification
+
+class GenerateBuildDashboardSpec extends Specification {
+
+    @Test
+    def "does no work if html report is disabled"() {
+        setup:
+        GenerateBuildDashboard task = HelperUtil.createTask(GenerateBuildDashboard)
+        when:
+        task.reports.html.enabled = false
+        and:
+        task.run()
+        then:
+        !task.didWork
+    }
+}
diff --git a/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/BuildDashboardGeneratorSpec.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/BuildDashboardGeneratorSpec.groovy
new file mode 100644
index 0000000..0a8889f
--- /dev/null
+++ b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/BuildDashboardGeneratorSpec.groovy
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.reporting.internal
+
+import org.gradle.api.reporting.Report
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import org.junit.Rule
+import spock.lang.Specification
+
+class BuildDashboardGeneratorSpec extends Specification {
+
+    @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider()
+
+    File outputFile
+    BuildDashboardGenerator generator
+
+    void setup() {
+        outputFile = tmpDir.file('output.html')
+    }
+
+    private void generatorFor(reports) {
+        generator = new BuildDashboardGenerator(reports as Set, outputFile)
+    }
+
+    private Document getOutputHtml() {
+        Jsoup.parse(outputFile, null)
+    }
+
+    Report mockReport(String name, File destination) {
+        Stub(Report) {
+            getDisplayName() >> name
+            getDestination() >> destination
+        }
+    }
+
+    void 'appropriate message is displayed when there are no reports available'() {
+        given:
+        generatorFor([])
+
+        when:
+        generator.generate()
+
+        then:
+        outputHtml.select('h1').text() == 'There are no build reports available.'
+    }
+
+    void 'links to reports are added to the generated markup'() {
+        given:
+        generatorFor([
+                mockReport('a', tmpDir.createFile('report.html')),
+                mockReport('b', tmpDir.createDir('inner').createFile('otherReport.html')),
+                mockReport('c', tmpDir.file('idonotexist.html')),
+                mockReport('d', null)
+        ])
+
+        when:
+        generator.generate()
+
+        then:
+        outputHtml.select('h1').text() == 'Available build reports:'
+        with outputHtml.select('ul li'), {
+            size() == 2
+            select('a[href=report.html]').text() == 'a'
+            select('a[href=inner/otherReport.html]').text() == 'b'
+        }
+    }
+
+    void 'report css is set up'() {
+        given:
+        generatorFor([])
+
+        when:
+        generator.generate()
+
+        then:
+        outputHtml.select('head link[type=text/css]').attr('href') == 'base-style.css'
+        tmpDir.file('base-style.css').text == getClass().getResource('/org/gradle/reporting/base-style.css').text
+    }
+}
diff --git a/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
index bc3ad43..b0ed1e4 100644
--- a/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
+++ b/subprojects/reporting/src/test/groovy/org/gradle/api/reporting/internal/DefaultReportContainerTest.groovy
@@ -39,7 +39,7 @@ class DefaultReportContainerTest extends Specification {
             
             c.delegate = new Object() {
                 Report createReport(String name) {
-                    add(SimpleReport, name, Report.OutputType.FILE, new IdentityFileResolver())
+                    add(SimpleReport, name, name, Report.OutputType.FILE, new IdentityFileResolver())
                 }
             }
             
@@ -75,7 +75,7 @@ class DefaultReportContainerTest extends Specification {
 
     def "container is immutable"() {
         when:
-        container.add(new SimpleReport("d", Report.OutputType.FILE, new IdentityFileResolver()))
+        container.add(new SimpleReport("d", "d", Report.OutputType.FILE, new IdentityFileResolver()))
         
         then:
         thrown(ReportContainer.ImmutableViolationException)
diff --git a/subprojects/scala/scala.gradle b/subprojects/scala/scala.gradle
index ad6e0ce..d06a73a 100644
--- a/subprojects/scala/scala.gradle
+++ b/subprojects/scala/scala.gradle
@@ -23,7 +23,7 @@ dependencies {
     compile project(":plugins")
 
     // keep in sync with ScalaBasePlugin code
-    provided("com.typesafe.zinc:zinc:0.2.0")
+    provided("com.typesafe.zinc:zinc:0.2.1")
 
     testCompile libraries.slf4j_api
 }
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy
index f616c50..faf68a2 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/IncrementalScalaCompileIntegrationTest.groovy
@@ -23,7 +23,7 @@ import spock.lang.Issue
 
 class IncrementalScalaCompileIntegrationTest extends AbstractIntegrationSpec {
 
-    @Rule TestResources resources = new TestResources()
+    @Rule TestResources resources = new TestResources(temporaryFolder)
 
     def recompilesSourceWhenPropertiesChange() {
         expect:
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy
index b501351..6bcbcb0 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntForkingScalaCompilerJdk6IntegrationTest.groovy
@@ -19,7 +19,7 @@ package org.gradle.scala.compile.jdk6
 import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.scala.compile.BasicScalaCompilerIntegrationTest
 
- at TargetVersions(["2.10.0-RC1"])
+ at TargetVersions(["2.10.0"])
 class AntForkingScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
     def setup() {
         executer.requireIsolatedDaemons()
@@ -30,6 +30,7 @@ class AntForkingScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerInteg
 compileScala.scalaCompileOptions.with {
     useAnt = true
     fork = true
+    forkOptions.memoryMaximumSize = "512m"
 }
 '''
     }
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy
index 490e3fa..0158a8d 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/AntInProcessScalaCompilerJdk6IntegrationTest.groovy
@@ -19,7 +19,7 @@ package org.gradle.scala.compile.jdk6
 import org.gradle.integtests.fixtures.TargetVersions
 import org.gradle.scala.compile.BasicScalaCompilerIntegrationTest
 
- at TargetVersions(["2.10.0-RC1"])
+ at TargetVersions(["2.10.0"])
 class AntInProcessScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
     def setup() {
         executer.requireIsolatedDaemons()
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest.groovy
index 7981706..b1939fb 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/compile/jdk6/ZincScalaCompilerJdk6IntegrationTest.groovy
@@ -21,9 +21,9 @@ import org.gradle.integtests.fixtures.TestResources
 import org.gradle.scala.compile.BasicScalaCompilerIntegrationTest
 import org.junit.Rule
 
- at TargetVersions(["2.8.2", "2.9.2", "2.10.0-RC1"])
+ at TargetVersions(["2.8.2", "2.9.2", "2.10.0"])
 class ZincScalaCompilerJdk6IntegrationTest extends BasicScalaCompilerIntegrationTest {
-    @Rule TestResources testResources
+    @Rule TestResources testResources = new TestResources(temporaryFolder)
 
     String compilerConfiguration() {
         """
diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy
index 45550cc..75f38d1 100644
--- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy
+++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/test/ScalaTestIntegrationTest.groovy
@@ -16,12 +16,12 @@
 package org.gradle.scala.test
 
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
-import org.gradle.integtests.fixtures.DefaultTestExecutionResult
+import org.gradle.integtests.fixtures.JUnitXmlTestExecutionResult
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
 
 class ScalaTestIntegrationTest extends AbstractIntegrationSpec {
-    @Rule TestResources resources
+    @Rule TestResources resources = new TestResources(temporaryFolder)
     
     def executesTestsWithMultiLineDescriptions() {
         file("build.gradle") << """
@@ -57,7 +57,7 @@ class MultiLineSuite extends FunSuite {
         then:
         succeeds("test")
 
-        DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory)
+        JUnitXmlTestExecutionResult result = new JUnitXmlTestExecutionResult(testDirectory)
         result.assertTestClassesExecuted("org.gradle.MultiLineSuite")
 	    result.testClass("org.gradle.MultiLineSuite").assertTestPassed("This test method name\nspans many\nlines")
     }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy
index 23efc20..3363d40 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy
@@ -92,7 +92,7 @@ class AntScalaCompiler implements Compiler<ScalaCompileSpec> {
     }
 
     private String chooseBackend(ScalaCompileSpec spec) {
-        // deprecated, but must still honor
+        // deprecated, but must still be honored
         if (spec.scalaCompileOptions.targetCompatibility) {
             return VersionNumber.parse(spec.scalaCompileOptions.targetCompatibility)
         }
@@ -101,9 +101,11 @@ class AntScalaCompiler implements Compiler<ScalaCompileSpec> {
         if (target <= VersionNumber.parse("1.5")) { return "jvm-${target.major}.${target.minor}" }
 
         def scalaVersion = sniffScalaVersion(spec.scalaClasspath)
-        if (scalaVersion >= VersionNumber.parse("2.10.0-AAA")) { return "jvm-${target.major}.${target.minor}" }
+        if (scalaVersion >= VersionNumber.parse("2.10.0-M5") || scalaVersion == VersionNumber.parse("2.10.0")) {
+            return "jvm-${target.major}.${target.minor}"
+        }
 
-        // prior to Scala 2.10, scalac Ant task only supports "jvm-1.5" and "msil" backends
+        // prior to Scala 2.10.0-M5, scalac Ant task only supported "jvm-1.5" and "msil" backends
         return "jvm-1.5"
     }
 }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/jdk6/ZincScalaCompiler.java b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/jdk6/ZincScalaCompiler.java
index 85d47e3..0d1b980 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/jdk6/ZincScalaCompiler.java
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/internal/tasks/scala/jdk6/ZincScalaCompiler.java
@@ -63,7 +63,7 @@ public class ZincScalaCompiler implements Compiler<ScalaJavaJointCompileSpec>, S
             List<String> scalacOptions = new ScalaCompilerArgumentsGenerator().generate(spec);
             List<String> javacOptions = new JavaCompilerArgumentsBuilder(spec).includeClasspath(false).build();
             Inputs inputs = Inputs.create(ImmutableList.copyOf(spec.getClasspath()), ImmutableList.copyOf(spec.getSource()), spec.getDestinationDir(),
-                    scalacOptions, javacOptions, spec.getScalaCompileOptions().getIncrementalOptions().getAnalysisFile(), spec.getAnalysisMap(), "mixed");
+                    scalacOptions, javacOptions, spec.getScalaCompileOptions().getIncrementalOptions().getAnalysisFile(), spec.getAnalysisMap(), "mixed", true);
             if (LOGGER.isDebugEnabled()) {
                 Inputs.debug(inputs, logger);
             }
diff --git a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
index 1dd6118..cf25d89 100644
--- a/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
+++ b/subprojects/scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
@@ -13,8 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.gradle.api.plugins.scala;
+package org.gradle.api.plugins.scala
 
+import org.gradle.api.Incubating
+import org.gradle.api.Nullable;
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.file.FileCollection
@@ -38,8 +40,8 @@ class ScalaBasePlugin implements Plugin<Project> {
     static final String SCALA_TOOLS_CONFIGURATION_NAME = "scalaTools"
     static final String ZINC_CONFIGURATION_NAME = "zinc"
 
-    private static final String DEFAULT_ZINC_VERSION = "0.2.0"
-    private static final Pattern SCALA_LIBRARY_JAR_PATTERN = Pattern.compile("scala-library-(\\d.*).jar")
+    private static final String DEFAULT_ZINC_VERSION = "0.2.1"
+    private static final Pattern SCALA_JAR_PATTERN = Pattern.compile("scala-(\\w.*?)-(\\d.*).jar")
 
     private Project project
     private final FileResolver fileResolver
@@ -49,6 +51,80 @@ class ScalaBasePlugin implements Plugin<Project> {
         this.fileResolver = fileResolver
     }
 
+    /**
+     * Infers a Scala compiler class path (containing a 'scala-compiler' Jar and its dependencies)
+     * based on the 'scala-library' Jar found on the specified class path.
+     *
+     * <p>Falls back to returning the 'scalaTools' configuration if one of the following holds:
+     *
+     * <ol>
+     *     <li>The 'scalaTools' configuration is explicitly configured (ie. has dependencies declared).
+     *         This is important for backwards compatibility.</li>
+     *     <li>No repository is declared for the project.</li>
+     *     <li>A 'scala-library' Jar cannot be found on the specified class path, or its
+     *         version cannot be determined.</li>
+     * </ol>
+     *
+     * Note that the returned class path may be empty, or may fail to resolve when asked for its contents.
+     * If this happens at task execution time, it should usually be treated as a configuration error on part of the user.
+     *
+     * @param classpath a class path (supposedly) containing a 'scala-library' Jar
+     * @return a Scala compiler class path
+     */
+    @Incubating
+    FileCollection inferScalaCompilerClasspath(Iterable<File> classpath) {
+        def scalaTools = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+        if (!scalaTools.dependencies.empty || project.repositories.empty) { return scalaTools }
+
+        def scalaLibraryJar = findScalaJar(classpath, "library")
+        if (scalaLibraryJar == null) { return scalaTools }
+
+        def scalaVersion = getScalaVersion(scalaLibraryJar)
+        if (scalaVersion == null) {
+            throw new AssertionError("Unexpectedly failed to determine version of Scala Jar file: $scalaLibraryJar")
+        }
+
+        return project.configurations.detachedConfiguration(
+                new DefaultExternalModuleDependency("org.scala-lang", "scala-compiler", scalaVersion))
+    }
+
+    /**
+     * Searches the specified class path for a Scala Jar file matching the specified
+     * module (compiler, library, jdbc, etc.).
+     *
+     * @param classpath the class path to search
+     * @param module the module to search for
+     * @return a matching Scala Jar file, or {@code null} if no match was found
+     */
+    @Nullable
+    @Incubating
+    File findScalaJar(Iterable<File> classpath, String module) {
+        for (file in classpath) {
+            def matcher = SCALA_JAR_PATTERN.matcher(file.name)
+            if (matcher.matches() && matcher.group(1) == module) {
+                return file
+            }
+        }
+        return null
+    }
+
+    /**
+     * Determines the version of a Scala Jar file (scala-compiler, scala-library, scala-jdbc, etc.).
+     * If the version cannot be determined, {@code null} is returned.
+     *
+     * <p>Implementation note: The version is determined by parsing the file name, which
+     * is expected to match the pattern 'scala-[component]-[version].jar'.
+     *
+     * @param scalaJar a Scala Jar file
+     * @return the version of the Jar file
+     */
+    @Nullable
+    @Incubating
+    String getScalaVersion(File scalaJar) {
+        def matcher = SCALA_JAR_PATTERN.matcher(scalaJar.name)
+        matcher.matches() ? matcher.group(2) : null
+    }
+
     void apply(Project project) {
         this.project = project
         def javaPlugin = project.plugins.apply(JavaBasePlugin.class)
@@ -108,7 +184,7 @@ class ScalaBasePlugin implements Plugin<Project> {
         scalaConsole.dependsOn(sourceSet.runtimeClasspath)
         scalaConsole.description = "Starts a Scala REPL with the $sourceSet.name runtime class path."
         scalaConsole.main = "scala.tools.nsc.MainGenericRunner"
-        scalaConsole.conventionMapping.classpath = { getScalaClasspath(sourceSet.runtimeClasspath) }
+        scalaConsole.conventionMapping.classpath = { inferScalaCompilerClasspath(sourceSet.runtimeClasspath) }
         scalaConsole.systemProperty("scala.usejavacp", true)
         scalaConsole.standardInput = System.in
         scalaConsole.conventionMapping.jvmArgs = { ["-classpath", sourceSet.runtimeClasspath.asPath] }
@@ -116,7 +192,7 @@ class ScalaBasePlugin implements Plugin<Project> {
 
     private void configureCompileDefaults() {
         project.tasks.withType(ScalaCompile.class) { ScalaCompile compile ->
-            compile.conventionMapping.scalaClasspath = { getScalaClasspath(compile.classpath) }
+            compile.conventionMapping.scalaClasspath = { inferScalaCompilerClasspath(compile.classpath) }
             compile.conventionMapping.zincClasspath = {
                 def config = project.configurations[ZINC_CONFIGURATION_NAME]
                 if (!compile.scalaCompileOptions.useAnt && config.dependencies.empty) {
@@ -133,30 +209,7 @@ class ScalaBasePlugin implements Plugin<Project> {
         project.tasks.withType(ScalaDoc) { ScalaDoc scalaDoc ->
             scalaDoc.conventionMapping.destinationDir = { project.file("$project.docsDir/scaladoc") }
             scalaDoc.conventionMapping.title = { project.extensions.getByType(ReportingExtension).apiDocTitle }
-            scalaDoc.conventionMapping.scalaClasspath = { getScalaClasspath(scalaDoc.classpath) }
-        }
-    }
-
-    private FileCollection getScalaClasspath(Iterable<File> classpath) {
-        def scalaClasspath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
-        if (scalaClasspath.dependencies.empty && !project.repositories.empty) {
-            def scalaVersion = sniffScalaVersion(classpath)
-            if (scalaVersion != null) {
-                scalaClasspath = project.configurations.detachedConfiguration(
-                        new DefaultExternalModuleDependency("org.scala-lang", "scala-compiler", scalaVersion))
-            }
-        }
-        scalaClasspath
-    }
-
-    private String sniffScalaVersion(Iterable<File> classpath) {
-        if (classpath == null) { return null }
-        for (file in classpath) {
-            def matcher = SCALA_LIBRARY_JAR_PATTERN.matcher(file.name)
-            if (matcher.matches()) {
-                return matcher.group(1)
-            }
+            scalaDoc.conventionMapping.scalaClasspath = { inferScalaCompilerClasspath(scalaDoc.classpath) }
         }
-        null
     }
 }
\ No newline at end of file
diff --git a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
index ead9945..0cfb847 100644
--- a/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
+++ b/subprojects/scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
@@ -171,4 +171,71 @@ public class ScalaBasePluginTest {
         assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
         assertThat(task, dependsOn())
     }
+
+    @Test void allowsToInferScalaCompilerClasspath() {
+        def plugin = project.plugins.apply(ScalaBasePlugin)
+
+        project.repositories {
+            mavenCentral()
+        }
+
+        def classpath = plugin.inferScalaCompilerClasspath([new File("other.jar"), new File("scala-library-2.10.0.jar")])
+
+        assert classpath instanceof Configuration
+        assert classpath.state == Configuration.State.UNRESOLVED
+        assert classpath.dependencies.size() == 1
+        classpath.dependencies.iterator().next().with {
+            assert group == "org.scala-lang"
+            assert name == "scala-compiler"
+            assert version == "2.10.0"
+        }
+    }
+
+    @Test void inferenceFallsBackToScalaToolsIfNoRepositoryDeclared() {
+        def plugin = project.plugins.apply(ScalaBasePlugin)
+
+        def classpath = plugin.inferScalaCompilerClasspath([new File("other.jar"), new File("scala-library-2.10.0.jar")])
+
+        assert classpath == project.configurations.scalaTools
+    }
+
+    @Test void inferenceFallsBackToScalaToolsIfScalaLibraryNotFoundOnClassPath() {
+        def plugin = project.plugins.apply(ScalaBasePlugin)
+
+        def classpath = plugin.inferScalaCompilerClasspath([new File("other.jar"), new File("other2.jar")])
+
+        assert classpath == project.configurations.scalaTools
+    }
+
+    @Test void allowsToFindScalaJarInClassPath() {
+        def plugin = project.plugins.apply(ScalaBasePlugin)
+
+        def file = plugin.findScalaJar([new File("other.jar"), new File("scala-jdbc-1.5.jar"), new File("scala-compiler-1.7.jar")], "jdbc")
+
+        assert file.name == "scala-jdbc-1.5.jar"
+    }
+
+    @Test void returnsNullIfScalaJarNotFound() {
+        def plugin = project.plugins.apply(ScalaBasePlugin)
+
+        def file = plugin.findScalaJar([new File("other.jar"), new File("scala-jdbc-1.5.jar"), new File("scala-compiler-1.7.jar")], "library")
+
+        assert file == null
+    }
+
+    @Test void allowsToDetermineVersionOfScalaJar() {
+        def plugin = project.plugins.apply(ScalaBasePlugin)
+
+        assert plugin.getScalaVersion(new File("scala-compiler-2.9.2.jar")) == "2.9.2"
+        assert plugin.getScalaVersion(new File("scala-jdbc-2.9.2.jar")) == "2.9.2"
+        assert plugin.getScalaVersion(new File("scala-library-2.10.0-SNAPSHOT.jar")) == "2.10.0-SNAPSHOT"
+        assert plugin.getScalaVersion(new File("scala-library-2.10.0-rc-3.jar")) == "2.10.0-rc-3"
+    }
+
+    @Test void returnsNullIfScalaVersionCannotBeDetermined() {
+        def plugin = project.plugins.apply(ScalaBasePlugin)
+
+        assert plugin.getScalaVersion(new File("scala-compiler.jar")) == null
+        assert plugin.getScalaVersion(new File("groovy-compiler-2.1.0.jar")) == null
+    }
 }
\ No newline at end of file
diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningIntegrationSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningIntegrationSpec.groovy
index 62584f5..cb07766 100644
--- a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningIntegrationSpec.groovy
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningIntegrationSpec.groovy
@@ -18,11 +18,12 @@ package org.gradle.plugins.signing
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.TestResources
 import org.junit.Rule
+
 import static org.gradle.util.TextUtil.escapeString
 
 abstract class SigningIntegrationSpec extends AbstractIntegrationSpec {
     
-    @Rule public final TestResources resources = new TestResources("keys")
+    @Rule public final TestResources resources = new TestResources(temporaryFolder, "keys")
 
     String jarFileName = "sign-1.0.jar"
 
diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
index 88cabf8..9995fe5 100644
--- a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
+++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy
@@ -19,11 +19,11 @@ package org.gradle.plugins.signing
 import org.gradle.integtests.fixtures.AbstractIntegrationSpec
 import org.gradle.integtests.fixtures.Sample
 import org.gradle.integtests.fixtures.UsesSample
-import org.gradle.test.fixtures.maven.MavenRepository
+import org.gradle.test.fixtures.maven.MavenFileRepository
 import org.junit.Rule
 
 class SigningSamplesSpec extends AbstractIntegrationSpec {
-    @Rule public final Sample mavenSample = new Sample()
+    @Rule public final Sample mavenSample = new Sample(temporaryFolder)
 
     @UsesSample('signing/maven')
     def "upload attaches signatures"() {
@@ -49,10 +49,11 @@ class SigningSamplesSpec extends AbstractIntegrationSpec {
         ":signArchives" in skippedTasks
 
         and:
-        repo.module('gradle', 'conditional', '1.0-SNAPSHOT').assertArtifactsPublished('conditional-1.0-SNAPSHOT.pom', 'conditional-1.0-SNAPSHOT.jar')
+        final module = repo.module('gradle', 'conditional', '1.0-SNAPSHOT')
+        module.assertArtifactsPublished("conditional-${module.publishArtifactVersion}.pom", "conditional-${module.publishArtifactVersion}.jar")
     }
 
-    MavenRepository getRepo() {
+    MavenFileRepository getRepo() {
         return maven(mavenSample.dir.file("build/repo"))
     }
 }
\ No newline at end of file
diff --git a/subprojects/sonar/sonar.gradle b/subprojects/sonar/sonar.gradle
index 8400bb5..0a17a37 100644
--- a/subprojects/sonar/sonar.gradle
+++ b/subprojects/sonar/sonar.gradle
@@ -17,24 +17,29 @@
 apply from: "$rootDir/gradle/providedConfiguration.gradle"
 
 dependencies {
-    groovy libraries.groovy
-
     compile project(":core")
     compile project(":plugins")
+    compile libraries.groovy
+
+    // Sonar Runner plugin
+    compile "org.codehaus.sonar-plugins:sonar-runner:2.0"
+    // version number is part of module name, to allow for multiple versions on same class path
+    integTestRuntime "org.gradle.sonar:sonar-server-3.2:3.2 at war"
+    integTestRuntime "org.gradle.sonar:sonar-test-server-home-dir-3.2:3.2 at zip"
+    integTestRuntime "org.gradle.sonar:sonar-test-server:3.4 at war"
+    integTestRuntime "org.gradle.sonar:sonar-test-server-home-dir:3.4.0.1 at zip"
+
+    // Sonar plugin
     compile libraries.guava
     compile libraries.slf4j_api
     compile libraries.inject
     compile "org.codehaus.sonar:sonar-batch-bootstrapper:2.9 at jar"
-
     // minimal dependencies to make our code compile
     // we don't ship these dependencies because sonar-batch-bootstrapper will download them (and many more) at runtime
     provided "org.codehaus.sonar:sonar-batch:2.9 at jar"
     provided "org.codehaus.sonar:sonar-plugin-api:2.9 at jar"
     provided "commons-configuration:commons-configuration:1.6 at jar"
-
-    //integTestRuntime "com.h2database:h2:1.3.168"
-    integTestRuntime "org.gradle.sonar:sonar-test-server:3.2 at war"
-    integTestRuntime "org.gradle.sonar:sonar-test-server-home-dir:3.2 at zip"
 }
 
 useTestFixtures()
+
diff --git a/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy b/subprojects/sonar/src/integTest/groovy/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest.groovy
index aaf6a06..8076b66 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
@@ -38,15 +38,15 @@ class SonarSmokeIntegrationTest extends AbstractIntegrationSpec {
     TestNameTestDirectoryProvider tempDir = new TestNameTestDirectoryProvider()
 
     @Rule
-    TestResources testResources
+    TestResources testResources = new TestResources(temporaryFolder)
 
     int databasePort
 
     def setup() {
         def classpath = ClasspathUtil.getClasspath(getClass().classLoader).collect() { new File(it.toURI()) }
-        def warFile = classpath.find { it.name == "sonar-test-server-3.2.war" }
+        def warFile = classpath.find { it.name == "sonar-server-3.2-3.2.war" }
         assert warFile
-        def zipFile = classpath.find { it.name == "sonar-test-server-home-dir-3.2.zip" }
+        def zipFile = classpath.find { it.name == "sonar-test-server-home-dir-3.2-3.2.zip" }
         assert zipFile
 
         def sonarHome = tempDir.createDir("sonar-home")
@@ -75,10 +75,10 @@ sonar.embeddedDatabase.port=$databasePort
         // also the Sonar dependencies with "provided" scope. Hence, the Sonar dependencies get loaded by
         // the wrong class loader.
         when:
-        executer.requireGradleHome(true)
+        executer.requireGradleHome()
                 .withArgument("-PserverUrl=http://localhost:${webServer.connectors[0].localPort}")
                 .withArgument("-PdatabaseUrl=jdbc:h2:tcp://localhost:$databasePort/mem:sonartest")
-                .withTasks("sonarAnalyze").run()
+                .withTasks("build", "sonarAnalyze").run()
 
         then:
         noExceptionThrown()
diff --git a/subprojects/sonar/src/integTest/groovy/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy b/subprojects/sonar/src/integTest/groovy/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy
new file mode 100644
index 0000000..9d9b4ed
--- /dev/null
+++ b/subprojects/sonar/src/integTest/groovy/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.api.sonar.runner
+
+import org.gradle.integtests.fixtures.AbstractIntegrationSpec
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider
+import org.gradle.util.AvailablePortFinder
+import org.gradle.util.ClasspathUtil
+import org.junit.Rule
+import org.mortbay.jetty.Server
+import org.mortbay.jetty.webapp.WebAppContext
+import spock.lang.AutoCleanup
+import spock.lang.Shared
+
+class SonarRunnerSmokeIntegrationTest extends AbstractIntegrationSpec {
+    @Shared
+    AvailablePortFinder portFinder = AvailablePortFinder.createPrivate()
+
+    @AutoCleanup("stop")
+    Server webServer = new Server(0)
+
+    @Rule
+    TestNameTestDirectoryProvider tempDir = new TestNameTestDirectoryProvider()
+
+    @Rule
+    TestResources testResources = new TestResources(temporaryFolder)
+
+    int databasePort
+
+    def setup() {
+        def classpath = ClasspathUtil.getClasspath(getClass().classLoader).collect() { new File(it.toURI()) }
+        def warFile = classpath.find { it.name == "sonar-test-server-3.4.war" }
+        assert warFile
+        def zipFile = classpath.find { it.name == "sonar-test-server-home-dir-3.4.0.1.zip" }
+        assert zipFile
+
+        def sonarHome = tempDir.createDir("sonar-home")
+        System.setProperty("SONAR_HOME", sonarHome.path)
+        new AntBuilder().unzip(src: zipFile, dest: sonarHome, overwrite: true)
+
+        databasePort = portFinder.nextAvailable
+        sonarHome.file("conf/sonar.properties") << """
+sonar.jdbc.username=sonar
+sonar.jdbc.password=sonar
+sonar.jdbc.url=jdbc:h2:mem:sonartest
+sonar.embeddedDatabase.port=$databasePort
+        """.trim()
+
+        def context = new WebAppContext()
+        context.war = warFile
+        webServer.addHandler(context)
+        webServer.start()
+    }
+
+    def "execute 'sonarRunner' task"() {
+        when:
+        executer.requireIsolatedDaemons()
+                .requireGradleHome()
+                .withArgument("-i")
+                .withArgument("-PserverUrl=foo") // dummy value for configuring sonarAnalyze task
+                .withArgument("-PdatabaseUrl=bar") // dummy value for configuring sonarAnalyze task
+                .withArgument("-Dsonar.host.url=http://localhost:${webServer.connectors[0].localPort}")
+                .withArgument("-Dsonar.jdbc.url=jdbc:h2:tcp://localhost:$databasePort/mem:sonartest")
+                .withTasks("sonarRunner").run()
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/build.gradle
index ee5346c..8030a19 100644
--- a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/build.gradle
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/build.gradle
@@ -1,12 +1,22 @@
-apply plugin: "java"
 apply plugin:  "sonar"
 
+subprojects {
+    apply plugin: "java"
+
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        testCompile "junit:junit:4.11"
+    }
+}
+
 sonar {
     server {
         url serverUrl
     }
     database {
-        //url = "jdbc:h2:tcp://localhost:9092/sonar"
         url databaseUrl
         driverClassName = "org.h2.Driver"
     }
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/build.gradle
new file mode 100644
index 0000000..cccc116
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/build.gradle
@@ -0,0 +1,14 @@
+description = "Sonar Test Customized Project"
+
+sourceSets {
+    main {
+        java {
+            srcDirs = ["src"]
+        }
+    }
+    test {
+        java {
+            srcDirs = ["test"]
+        }
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/src/org/gradle/test/customizedProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/src/org/gradle/test/customizedProject/Production1.java
new file mode 100644
index 0000000..6a314b0
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/src/org/gradle/test/customizedProject/Production1.java
@@ -0,0 +1,14 @@
+package org.gradle.test.customizedProject;
+
+public class Production1 {
+    private final String property;
+
+    public Production1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/test/org/gradle/test/customizedProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/test/org/gradle/test/customizedProject/Test1.java
new file mode 100644
index 0000000..78b5dcd
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/customizedProject/test/org/gradle/test/customizedProject/Test1.java
@@ -0,0 +1,12 @@
+package org.gradle.test.customizedProject;
+
+import static org.junit.Assert.*;
+
+public class Test1 {
+    private final Production1 production = new Production1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/gradle.properties b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/gradle.properties
new file mode 100644
index 0000000..499051e
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx512m -XX:MaxPermSize=512m
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/build.gradle
new file mode 100644
index 0000000..2d8cfa3
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/build.gradle
@@ -0,0 +1,2 @@
+description = "Sonar Test Java Project"
+
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java
new file mode 100644
index 0000000..5a9d100
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production1 {
+    private final String property;
+
+    public Production1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java
new file mode 100644
index 0000000..3b7b1bd
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production10 {
+    private final String property;
+
+    public Production10(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java
new file mode 100644
index 0000000..c708878
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production2 {
+    private final String property;
+
+    public Production2(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java
new file mode 100644
index 0000000..cdca68e
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production3 {
+    private final String property;
+
+    public Production3(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java
new file mode 100644
index 0000000..43a56c9
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production4 {
+    private final String property;
+
+    public Production4(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java
new file mode 100644
index 0000000..938f15f
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production5 {
+    private final String property;
+
+    public Production5(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java
new file mode 100644
index 0000000..3d490cd
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production6 {
+    private final String property;
+
+    public Production6(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java
new file mode 100644
index 0000000..0c63039
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production7 {
+    private final String property;
+
+    public Production7(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java
new file mode 100644
index 0000000..7e767f4
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production8 {
+    private final String property;
+
+    public Production8(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java
new file mode 100644
index 0000000..6525216
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production9 {
+    private final String property;
+
+    public Production9(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml
new file mode 100644
index 0000000..caf9343
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml
@@ -0,0 +1,6 @@
+<note>
+<to>Fred</to>
+<from>Joe</from>
+<heading>Reminder</heading>
+<body>Don't forget about this weekend!</body>
+</note>
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java
new file mode 100644
index 0000000..db5d11f
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test1 {
+    private final Production1 production = new Production1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java
new file mode 100644
index 0000000..0812b48
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test10 {
+    private final Production10 production = new Production10("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java
new file mode 100644
index 0000000..7eb4590
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test2 {
+    private final Production2 production = new Production2("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java
new file mode 100644
index 0000000..83c47f0
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test3 {
+    private final Production3 production = new Production3("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java
new file mode 100644
index 0000000..d05f600
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test4 {
+    private final Production4 production = new Production4("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java
new file mode 100644
index 0000000..b6c4bd2
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test5 {
+    private final Production5 production = new Production5("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java
new file mode 100644
index 0000000..e7e77d1
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test6 {
+    private final Production6 production = new Production6("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java
new file mode 100644
index 0000000..cb76f30
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test7 {
+    private final Production7 production = new Production7("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java
new file mode 100644
index 0000000..2234e34
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test8 {
+    private final Production8 production = new Production8("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java
new file mode 100644
index 0000000..3826965
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test9 {
+    private final Production9 production = new Production9("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml
new file mode 100644
index 0000000..caf9343
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml
@@ -0,0 +1,6 @@
+<note>
+<to>Fred</to>
+<from>Joe</from>
+<heading>Reminder</heading>
+<body>Don't forget about this weekend!</body>
+</note>
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/build.gradle
new file mode 100644
index 0000000..c82e292
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/build.gradle
@@ -0,0 +1 @@
+description = "Sonar Test Nested Project"
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/main/java/org/gradle/test/nestedProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/main/java/org/gradle/test/nestedProject/Production1.java
new file mode 100644
index 0000000..411660c
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/main/java/org/gradle/test/nestedProject/Production1.java
@@ -0,0 +1,14 @@
+package org.gradle.test.nestedProject;
+
+public class Production1 {
+    private final String property;
+
+    public Production1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/test/java/org/gradle/test/nestedProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/test/java/org/gradle/test/nestedProject/Test1.java
new file mode 100644
index 0000000..8e6f73a
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/test/java/org/gradle/test/nestedProject/Test1.java
@@ -0,0 +1,12 @@
+package org.gradle.test.nestedProject;
+
+import static org.junit.Assert.*;
+
+public class Test1 {
+    private final Production1 production = new Production1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/settings.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/settings.gradle
new file mode 100644
index 0000000..fbb7d87
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/settings.gradle
@@ -0,0 +1,3 @@
+include "javaProject", "skippedProject", "customizedProject", "nested:nested2:nestedProject"
+
+rootProject.name = "Sonar Test Build" // spaces are intentional
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/build.gradle
new file mode 100644
index 0000000..e12da4e
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/build.gradle
@@ -0,0 +1,3 @@
+description = "Sonar Test Skipped Project"
+
+sonar.project.skip = true
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/src/main/java/org/gradle/test/skippedProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/src/main/java/org/gradle/test/skippedProject/Production1.java
new file mode 100644
index 0000000..3c36b25
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/src/main/java/org/gradle/test/skippedProject/Production1.java
@@ -0,0 +1,14 @@
+package org.gradle.test.skippedProject;
+
+public class Production1 {
+    private final String property;
+
+    public Production1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/src/test/java/org/gradle/test/skippedProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/src/test/java/org/gradle/test/skippedProject/Test1.java
new file mode 100644
index 0000000..a32724c
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/plugins/sonar/SonarSmokeIntegrationTest/shared/skippedProject/src/test/java/org/gradle/test/skippedProject/Test1.java
@@ -0,0 +1,12 @@
+package org.gradle.test.skippedProject;
+
+import static org.junit.Assert.*;
+
+public class Test1 {
+    private final Production1 production = new Production1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/build.gradle
new file mode 100644
index 0000000..cdea0fc
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/build.gradle
@@ -0,0 +1,20 @@
+apply plugin: "sonar-runner"
+
+description = "Sonar Test Build"
+
+allprojects {
+    version = "1.42"
+    group = "org.gradle.test.sonar"
+}
+
+subprojects {
+    apply plugin: "java"
+
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        testCompile "junit:junit:4.11"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/build.gradle
new file mode 100644
index 0000000..cccc116
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/build.gradle
@@ -0,0 +1,14 @@
+description = "Sonar Test Customized Project"
+
+sourceSets {
+    main {
+        java {
+            srcDirs = ["src"]
+        }
+    }
+    test {
+        java {
+            srcDirs = ["test"]
+        }
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/src/org/gradle/test/customizedProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/src/org/gradle/test/customizedProject/Production1.java
new file mode 100644
index 0000000..6a314b0
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/src/org/gradle/test/customizedProject/Production1.java
@@ -0,0 +1,14 @@
+package org.gradle.test.customizedProject;
+
+public class Production1 {
+    private final String property;
+
+    public Production1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/test/org/gradle/test/customizedProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/test/org/gradle/test/customizedProject/Test1.java
new file mode 100644
index 0000000..78b5dcd
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/customizedProject/test/org/gradle/test/customizedProject/Test1.java
@@ -0,0 +1,12 @@
+package org.gradle.test.customizedProject;
+
+import static org.junit.Assert.*;
+
+public class Test1 {
+    private final Production1 production = new Production1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/gradle.properties b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/gradle.properties
new file mode 100644
index 0000000..499051e
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/gradle.properties
@@ -0,0 +1 @@
+org.gradle.jvmargs=-Xmx512m -XX:MaxPermSize=512m
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/build.gradle
new file mode 100644
index 0000000..2f1848b
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'groovy'
+
+description = "Sonar Test Groovy Project"
+
+dependencies {
+    compile 'org.codehaus.groovy:groovy-all:2.0.5'
+}
+
+sonarRunner {
+    sonarProperties {
+        // Sonar Groovy plugin can't currently be used because it fails as soon as sonar.project.key contains a colon
+        // property "sonar.language", "grvy"
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy1.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy1.groovy
new file mode 100644
index 0000000..cba2e24
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy1.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy1 {
+    private final String property;
+
+    public ProductionGroovy1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy10.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy10.groovy
new file mode 100644
index 0000000..4d0232d
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy10.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy10 {
+    private final String property;
+
+    public ProductionGroovy10(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy2.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy2.groovy
new file mode 100644
index 0000000..ef8c800
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy2.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy2 {
+    private final String property;
+
+    public ProductionGroovy2(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy3.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy3.groovy
new file mode 100644
index 0000000..27c1a88
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy3.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy3 {
+    private final String property;
+
+    public ProductionGroovy3(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy4.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy4.groovy
new file mode 100644
index 0000000..1745c83
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy4.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy4 {
+    private final String property;
+
+    public ProductionGroovy4(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy5.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy5.groovy
new file mode 100644
index 0000000..33c90ea
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy5.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy5 {
+    private final String property;
+
+    public ProductionGroovy5(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy6.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy6.groovy
new file mode 100644
index 0000000..b0aa82c
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy6.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy6 {
+    private final String property;
+
+    public ProductionGroovy6(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy7.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy7.groovy
new file mode 100644
index 0000000..963bb45
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy7.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy7 {
+    private final String property;
+
+    public ProductionGroovy7(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy8.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy8.groovy
new file mode 100644
index 0000000..e74af19
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy8.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy8 {
+    private final String property;
+
+    public ProductionGroovy8(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy9.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy9.groovy
new file mode 100644
index 0000000..57e9c7d
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/main/groovy/org/gradle/test/groovyProject/ProductionGroovy9.groovy
@@ -0,0 +1,13 @@
+package org.gradle.test.groovyProject
+
+public class ProductionGroovy9 {
+    private final String property;
+
+    public ProductionGroovy9(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy1.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy1.groovy
new file mode 100644
index 0000000..554be1f
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy1.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy1 {
+    private final ProductionGroovy1 production = new ProductionGroovy1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy10.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy10.groovy
new file mode 100644
index 0000000..7478780
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy10.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy10 {
+    private final ProductionGroovy10 production = new ProductionGroovy10("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy2.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy2.groovy
new file mode 100644
index 0000000..2bc9e2b
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy2.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy2 {
+    private final ProductionGroovy2 production = new ProductionGroovy2("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy3.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy3.groovy
new file mode 100644
index 0000000..7cf44dd
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy3.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy3 {
+    private final ProductionGroovy3 production = new ProductionGroovy3("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy4.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy4.groovy
new file mode 100644
index 0000000..17dc1bb
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy4.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy4 {
+    private final ProductionGroovy4 production = new ProductionGroovy4("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy5.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy5.groovy
new file mode 100644
index 0000000..ecf9be3
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy5.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy5 {
+    private final ProductionGroovy5 production = new ProductionGroovy5("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy6.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy6.groovy
new file mode 100644
index 0000000..18edd32
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy6.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy6 {
+    private final ProductionGroovy6 production = new ProductionGroovy6("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy7.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy7.groovy
new file mode 100644
index 0000000..ec2f4c9
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy7.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy7 {
+    private final ProductionGroovy7 production = new ProductionGroovy7("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy8.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy8.groovy
new file mode 100644
index 0000000..6d8bfb0
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy8.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy8 {
+    private final ProductionGroovy8 production = new ProductionGroovy8("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy9.groovy b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy9.groovy
new file mode 100644
index 0000000..86ca712
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/groovyProject/src/test/groovy/org/gradle/test/groovyProject/TestGroovy9.groovy
@@ -0,0 +1,12 @@
+package org.gradle.test.groovyProject
+
+import static org.junit.Assert.assertEquals
+
+public class TestGroovy9 {
+    private final ProductionGroovy9 production = new ProductionGroovy9("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/build.gradle
new file mode 100644
index 0000000..2d8cfa3
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/build.gradle
@@ -0,0 +1,2 @@
+description = "Sonar Test Java Project"
+
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java
new file mode 100644
index 0000000..5a9d100
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production1.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production1 {
+    private final String property;
+
+    public Production1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java
new file mode 100644
index 0000000..3b7b1bd
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production10.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production10 {
+    private final String property;
+
+    public Production10(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java
new file mode 100644
index 0000000..c708878
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production2.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production2 {
+    private final String property;
+
+    public Production2(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java
new file mode 100644
index 0000000..cdca68e
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production3.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production3 {
+    private final String property;
+
+    public Production3(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java
new file mode 100644
index 0000000..43a56c9
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production4.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production4 {
+    private final String property;
+
+    public Production4(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java
new file mode 100644
index 0000000..938f15f
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production5.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production5 {
+    private final String property;
+
+    public Production5(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java
new file mode 100644
index 0000000..3d490cd
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production6.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production6 {
+    private final String property;
+
+    public Production6(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java
new file mode 100644
index 0000000..0c63039
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production7.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production7 {
+    private final String property;
+
+    public Production7(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java
new file mode 100644
index 0000000..7e767f4
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production8.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production8 {
+    private final String property;
+
+    public Production8(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java
new file mode 100644
index 0000000..6525216
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/java/org/gradle/test/javaProject/Production9.java
@@ -0,0 +1,14 @@
+package org.gradle.test.javaProject;
+
+public class Production9 {
+    private final String property;
+
+    public Production9(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml
new file mode 100644
index 0000000..caf9343
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/main/resources/org/gradle/test/javaProject/productionResource.xml
@@ -0,0 +1,6 @@
+<note>
+<to>Fred</to>
+<from>Joe</from>
+<heading>Reminder</heading>
+<body>Don't forget about this weekend!</body>
+</note>
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java
new file mode 100644
index 0000000..db5d11f
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test1.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test1 {
+    private final Production1 production = new Production1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java
new file mode 100644
index 0000000..0812b48
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test10.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test10 {
+    private final Production10 production = new Production10("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java
new file mode 100644
index 0000000..7eb4590
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test2.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test2 {
+    private final Production2 production = new Production2("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java
new file mode 100644
index 0000000..83c47f0
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test3.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test3 {
+    private final Production3 production = new Production3("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java
new file mode 100644
index 0000000..d05f600
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test4.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test4 {
+    private final Production4 production = new Production4("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java
new file mode 100644
index 0000000..b6c4bd2
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test5.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test5 {
+    private final Production5 production = new Production5("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java
new file mode 100644
index 0000000..e7e77d1
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test6.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test6 {
+    private final Production6 production = new Production6("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java
new file mode 100644
index 0000000..cb76f30
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test7.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test7 {
+    private final Production7 production = new Production7("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java
new file mode 100644
index 0000000..2234e34
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test8.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test8 {
+    private final Production8 production = new Production8("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java
new file mode 100644
index 0000000..3826965
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/java/org/gradle/test/javaProject/Test9.java
@@ -0,0 +1,12 @@
+package org.gradle.test.javaProject;
+
+import static org.junit.Assert.*;
+
+public class Test9 {
+    private final Production9 production = new Production9("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml
new file mode 100644
index 0000000..caf9343
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/javaProject/src/test/resources/org/gradle/test/javaProject/testResource.xml
@@ -0,0 +1,6 @@
+<note>
+<to>Fred</to>
+<from>Joe</from>
+<heading>Reminder</heading>
+<body>Don't forget about this weekend!</body>
+</note>
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/build.gradle
new file mode 100644
index 0000000..c82e292
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/build.gradle
@@ -0,0 +1 @@
+description = "Sonar Test Nested Project"
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/main/java/org/gradle/test/nestedProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/main/java/org/gradle/test/nestedProject/Production1.java
new file mode 100644
index 0000000..411660c
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/main/java/org/gradle/test/nestedProject/Production1.java
@@ -0,0 +1,14 @@
+package org.gradle.test.nestedProject;
+
+public class Production1 {
+    private final String property;
+
+    public Production1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/test/java/org/gradle/test/nestedProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/test/java/org/gradle/test/nestedProject/Test1.java
new file mode 100644
index 0000000..8e6f73a
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/nested/nested2/nestedProject/src/test/java/org/gradle/test/nestedProject/Test1.java
@@ -0,0 +1,12 @@
+package org.gradle.test.nestedProject;
+
+import static org.junit.Assert.*;
+
+public class Test1 {
+    private final Production1 production = new Production1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/settings.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/settings.gradle
new file mode 100644
index 0000000..ed2f902
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/settings.gradle
@@ -0,0 +1,3 @@
+include "javaProject", "groovyProject", "skippedProject", "customizedProject", "nested:nested2:nestedProject"
+
+rootProject.name = "SonarTestBuild"
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/build.gradle b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/build.gradle
new file mode 100644
index 0000000..657a3f9
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/build.gradle
@@ -0,0 +1,3 @@
+description = "Sonar Test Skipped Project"
+
+sonarRunner.skipProject = true
\ No newline at end of file
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/src/main/java/org/gradle/test/skippedProject/Production1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/src/main/java/org/gradle/test/skippedProject/Production1.java
new file mode 100644
index 0000000..3c36b25
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/src/main/java/org/gradle/test/skippedProject/Production1.java
@@ -0,0 +1,14 @@
+package org.gradle.test.skippedProject;
+
+public class Production1 {
+    private final String property;
+
+    public Production1(String param) {
+        this.property = param;
+    }
+
+    public String getProperty() {
+        return property;
+    }
+
+}
diff --git a/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/src/test/java/org/gradle/test/skippedProject/Test1.java b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/src/test/java/org/gradle/test/skippedProject/Test1.java
new file mode 100644
index 0000000..a32724c
--- /dev/null
+++ b/subprojects/sonar/src/integTest/resources/org/gradle/api/sonar/runner/SonarRunnerSmokeIntegrationTest/shared/skippedProject/src/test/java/org/gradle/test/skippedProject/Test1.java
@@ -0,0 +1,12 @@
+package org.gradle.test.skippedProject;
+
+import static org.junit.Assert.*;
+
+public class Test1 {
+    private final Production1 production = new Production1("value");
+
+    @org.junit.Test
+    public void test() {
+        assertEquals(production.getProperty(), "value");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarProperties.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarProperties.groovy
new file mode 100644
index 0000000..4d79c65
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarProperties.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.sonar.runner
+
+import org.gradle.api.Incubating
+
+/**
+ * The Sonar properties for the current Gradle project that are to be passed to the Sonar Runner.
+ * The {@code properties} map is already populated with the defaults provided by Gradle, and can be
+ * further manipulated as necessary. Before passing them on to the Sonar Runner, property values
+ * are converted to Strings as follows:
+ *
+ * <ul>
+ *     <li>{@Iterable}s are recursively converted and joined into a comma-separated String.</li>
+ *     <li>All other values are converted to Strings by calling their {@code toString} method.</li>
+ * </ul>
+ */
+ at Incubating
+class SonarProperties {
+    /**
+     * The Sonar properties for the current Gradle project that are to be passed to the Sonar runner.
+     */
+    Map<String, Object> properties = [:]
+
+    /**
+     * Convenience method for setting a single property.
+     *
+     * @param key the key of the property to be added
+     * @param value the value of the property to be added
+     */
+    void property(String key, Object value) {
+        properties[key] = value
+    }
+
+    /**
+     * Convenience method for setting multiple properties.
+     *
+     * @param properties the properties to be added
+     */
+    void properties(Map<String, ?> properties) {
+        this.properties.putAll(properties)
+    }
+}
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunner.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunner.groovy
new file mode 100644
index 0000000..bfaedb0
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunner.groovy
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.sonar.runner
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.Incubating
+import org.gradle.api.logging.Logger
+import org.gradle.api.logging.Logging
+import org.gradle.api.tasks.TaskAction
+import org.sonar.runner.Runner
+
+/**
+ * Analyses one or more projects with the <a href="http://docs.codehaus.org/display/SONAR/Analyzing+with+Sonar+Runner">
+ * Sonar Runner</a>. 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}. 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+Sonar+Runner">Sonar Runner documentation</a>.
+ */
+ at Incubating
+class SonarRunner extends DefaultTask {
+    private static final Logger LOGGER = Logging.getLogger(SonarRunner)
+
+    /**
+     * The String key/value pairs to be passed to the Sonar Runner. {@code null} values are not permitted.
+     */
+    Properties sonarProperties
+
+    @TaskAction
+    void run() {
+        def properties = getSonarProperties()
+        if (LOGGER.infoEnabled) {
+            LOGGER.info("Executing Sonar Runner with properties:\n{}",
+                    properties.sort().collect { key, value -> "$key: $value" }.join("\n"))
+        }
+        def runner = Runner.create(properties)
+        runner.execute()
+    }
+}
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerExtension.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerExtension.groovy
new file mode 100644
index 0000000..875841f
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerExtension.groovy
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.sonar.runner
+
+import groovy.transform.PackageScope
+
+import org.gradle.api.Action
+import org.gradle.api.Incubating
+import org.gradle.listener.ActionBroadcast
+
+/**
+ * An extension for configuring the <a href="http://docs.codehaus.org/display/SONAR/Analyzing+with+Sonar+Runner">
+ * Sonar Runner</a>. The extension is added to all projects that have the {@code sonar-runner}
+ * plugin applied, and all of their subprojects.
+ *
+ * <p>Example usage:
+ *
+ * <pre autoTested=''>
+ * sonarRunner {
+ *     skipProject = false // this is the default
+ *
+ *     sonarProperties {
+ *         property "sonar.host.url", "http://my.sonar.server" // adding a single property
+ *         properties mapOfProperties // adding multiple properties at once
+ *         properties["sonar.sources"] += sourceSets.other.java.srcDirs // manipulating an existing property
+ *     }
+ * }
+ * </pre>
+ */
+ at Incubating
+class SonarRunnerExtension {
+    /**
+     * Tells if the project will be excluded from analysis. Defaults to {@code false}.
+     */
+    boolean skipProject
+
+    /**
+     * Adds an action that configures Sonar properties for the associated Gradle project.
+     * <em>Global</em> Sonar 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}.
+     * Evaluation of the action is deferred until {@code sonarRunner.sonarProperties} is requested.
+     * 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). System properties take
+     * precedence over properties declared in build scripts.
+     *
+     * @param action an action that configures Sonar properties for the associated Gradle project
+     */
+    void sonarProperties(Action<? super SonarProperties> action) {
+        propertiesActions.add(action)
+    }
+
+    private final ActionBroadcast<SonarProperties> propertiesActions = new ActionBroadcast<SonarProperties>()
+
+    @PackageScope
+    void evaluateSonarPropertiesBlocks(Map<String, Object> properties) {
+        def sonarProperties = new SonarProperties(properties: properties)
+        propertiesActions.execute(sonarProperties)
+    }
+}
diff --git a/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerPlugin.groovy b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerPlugin.groovy
new file mode 100644
index 0000000..391e507
--- /dev/null
+++ b/subprojects/sonar/src/main/groovy/org/gradle/api/sonar/runner/SonarRunnerPlugin.groovy
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.sonar.runner
+
+import org.gradle.api.Incubating
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.tasks.SourceSet
+import org.gradle.internal.jvm.Jvm
+
+/**
+ * A plugin for analyzing projects with the
+ * <a href="http://docs.codehaus.org/display/SONAR/Analyzing+with+Sonar+Runner">Sonar Runner</a>.
+ * When applied to a project, both the project itself and its subprojects
+ * will be analyzed (in a single run). Therefore, it's common to apply the
+ * plugin only to the root project. To exclude selected subprojects from
+ * being analyzed, set {@code sonarRunner.skipProject = true}.
+ *
+ * <p>The plugin is configured via {@link SonarRunnerExtension}. Here is a
+ * small example:
+ *
+ * <pre autoTested=''>
+ * sonarRunner {
+ *     skipProject = false // this is the default
+ *
+ *     sonarProperties {
+ *         property "sonar.host.url", "http://my.sonar.server" // adding a single property
+ *         properties mapOfProperties // adding multiple properties at once
+ *         properties["sonar.sources"] += sourceSets.other.java.srcDirs // manipulating an existing property
+ *     }
+ * }
+ * </pre>
+ *
+ * The Sonar Runner already comes with defaults for some of the most important
+ * Sonar properties (server URL, database settings, etc.). For details see
+ * <a href="http://docs.codehaus.org/display/SONAR/Analysis+Parameters">Analysis Parameters</a>
+ * in the Sonar documentation. The {@code sonar-runner} plugin provides the following additional
+ * defaults:
+ *
+ * <dl>
+ *     <dt>sonar.projectKey
+ *     <dd>"$project.group:$project.name"
+ *     <dt>sonar.projectName
+ *     <dd>project.name
+ *     <dt>sonar.projectDescription
+ *     <dd>project.description
+ *     <dt>sonar.projectVersion
+ *     <dd>sonar.version
+ *     <dt>sonar.projectBaseDir
+ *     <dd>project.projectDir
+ *     <dt>sonar.working.directory
+ *     <dd>"$project.buildDir/sonar"
+ *     <dt>sonar.dynamicAnalysis
+ *     <dd>"reuseReports"
+ * </dl>
+ *
+ * For project that have the {@code java-base} plugin applied, additionally the following defaults are provided:
+ *
+ * <dl>
+ *     <dt>sonar.java.source
+ *     <dd>project.sourceCompatibility
+ *     <dt>sonar.java.target
+ *     <dd>project.targetCompatibility
+ * </dl>
+ *
+ * For project that have the {@code java} plugin applied, additionally the following defaults are provided:
+ *
+ * <dl>
+ *     <dt>sonar.sources
+ *     <dd>sourceSets.main.allSource.srcDirs (filtered to only include existing directories)
+ *     <dt>sonar.tests
+ *     <dd>sourceSets.test.allSource.srcDirs (filtered to only include existing directories)
+ *     <dt>sonar.binaries
+ *     <dd>sourceSets.main.runtimeClasspath (filtered to only include directories)
+ *     <dt>sonar.libraries
+ *     <dd>sourceSets.main.runtimeClasspath (filtering to only include files; {@code rt.jar} added if necessary)
+ *     <dt>sonar.surefire.reportsPath
+ *     <dd>test.testResultsDir (if the directory exists)
+ * </dl>
+ */
+ at Incubating
+class SonarRunnerPlugin implements Plugin<Project> {
+    // the project to which the plugin was applied
+    Project targetProject
+
+    void apply(Project project) {
+        targetProject = project
+        def sonarRunnerTask = project.tasks.add("sonarRunner", SonarRunner)
+        sonarRunnerTask.conventionMapping.with {
+            sonarProperties = {
+                def properties = new Properties()
+                computeSonarProperties(project, properties)
+                properties
+            }
+        }
+        project.allprojects {
+            extensions.create("sonarRunner", SonarRunnerExtension)
+        }
+        sonarRunnerTask.dependsOn {
+            project.allprojects.findAll { prj ->
+                prj.plugins.hasPlugin(JavaPlugin) && !prj.sonarRunner.skipProject
+            }.collect { it.tasks.test }
+        }
+    }
+
+    void computeSonarProperties(Project project, Properties properties) {
+        def extension = project.extensions.getByType(SonarRunnerExtension)
+        if (extension.skipProject) { return }
+
+        Map<String, Object> rawProperties = [:]
+        addGradleDefaults(project, rawProperties)
+        extension.evaluateSonarPropertiesBlocks(rawProperties)
+        if (project == targetProject) { addSystemProperties(rawProperties) }
+
+        def projectPrefix = project.path.substring(targetProject.path.size()).replace(":", ".")
+        if (projectPrefix.startsWith(".")) {
+            projectPrefix = projectPrefix.substring(1)
+        }
+        convertProperties(rawProperties, projectPrefix, properties)
+        
+        def enabledChildProjects = project.childProjects.values().findAll { !it.sonarRunner.skipProject }
+        if (enabledChildProjects.empty) { return }
+
+        properties[convertKey("sonar.modules", projectPrefix)] = convertValue(enabledChildProjects.name)
+        for (childProject in enabledChildProjects) {
+            computeSonarProperties(childProject, properties)
+        }
+    }
+
+    private void addGradleDefaults(Project project, Map<String, Object> properties) {
+        properties["sonar.projectName"] = project.name
+        properties["sonar.projectDescription"] = project.description
+        properties["sonar.projectVersion"] = project.version
+        properties["sonar.projectBaseDir"] = project.projectDir
+        properties["sonar.dynamicAnalysis"] = "reuseReports"
+
+        if (project == targetProject) {
+            // We only set project key for root project because Sonar Runner 2.0 will automatically
+            // prefix subproject keys with parent key, even if subproject keys are set explicitly.
+            // Therefore it's better to rely on Sonar's defaults.
+            properties["sonar.projectKey"] = getProjectKey(project)
+            properties["sonar.environment.information.key"] = "Gradle"
+            properties["sonar.environment.information.version"] = project.gradle.gradleVersion
+            properties["sonar.working.directory"] = new File(project.buildDir, "sonar")
+        }
+
+        project.plugins.withType(JavaBasePlugin) {
+            properties["sonar.java.source"] = project.sourceCompatibility
+            properties["sonar.java.target"] = project.targetCompatibility
+        }
+
+        project.plugins.withType(JavaPlugin) {
+            SourceSet main = project.sourceSets.main
+            SourceSet test = project.sourceSets.test
+
+            properties["sonar.sources"] = main.allSource.srcDirs.findAll { it.exists() } ?: null
+            properties["sonar.tests"] = test.allSource.srcDirs.findAll { it.exists() } ?: null
+            properties["sonar.binaries"] = main.runtimeClasspath.findAll { it.directory } ?: null
+            properties["sonar.libraries"] = getLibraries(main)
+            properties["sonar.surefire.reportsPath"] = project.test.testResultsDir.exists() ? project.test.testResultsDir : null
+        }
+
+        if (properties["sonar.sources"] == null) {
+            // Should be able to remove this after upgrading to Sonar Runner 2.1 (issue is already marked as fixed),
+            // if we can live with the fact that leaf projects w/o source dirs will still cause a failure.
+            properties["sonar.sources"] = ""
+        }
+    }
+
+    private String getProjectKey(Project project) {
+        // Sonar 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.
+        // (':' works, ' ' doesn't.) In such a case, sonar.projectKey can be overridden manually.
+        project.group ? "$project.group:$project.name" : project.name
+    }
+
+    private void addSystemProperties(Map<String, Object> properties) {
+        properties.putAll(System.properties.findAll { key, value -> key.startsWith("sonar.") })
+    }
+
+    private Collection<File> getLibraries(SourceSet main) {
+        def libraries = main.runtimeClasspath.findAll { it.file }
+        def runtimeJar = Jvm.current().runtimeJar
+        if (runtimeJar != null) {
+            libraries << runtimeJar
+        }
+        libraries ?: null
+    }
+
+    private void convertProperties(Map<String, Object> rawProperties, String projectPrefix, Properties properties) {
+        rawProperties.each { key, value ->
+            if (value != null) {
+                properties[convertKey(key, projectPrefix)] = convertValue(value)
+            }
+        }
+    }
+    
+    private String convertKey(String key, String projectPrefix) {
+        projectPrefix ? "${projectPrefix}.$key" : key
+    }
+    
+    private String convertValue(Object value) {
+        value instanceof Iterable ? value.collect { convertValue(it) }.join(",") : value.toString()
+    }
+}
diff --git a/subprojects/sonar/src/main/resources/META-INF/gradle-plugins/sonar-runner.properties b/subprojects/sonar/src/main/resources/META-INF/gradle-plugins/sonar-runner.properties
new file mode 100644
index 0000000..f49b1b0
--- /dev/null
+++ b/subprojects/sonar/src/main/resources/META-INF/gradle-plugins/sonar-runner.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.api.sonar.runner.SonarRunnerPlugin
\ No newline at end of file
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarPropertiesTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarPropertiesTest.groovy
new file mode 100644
index 0000000..2676f6f
--- /dev/null
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarPropertiesTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.sonar.runner
+
+import spock.lang.Specification
+
+class SonarPropertiesTest extends Specification {
+    def properties = new SonarProperties()
+
+    def "set a single property"() {
+        when:
+        properties.property "foo", "one"
+
+        then:
+        properties.properties == [foo: "one"]
+    }
+
+    def "set multiple properties at once"() {
+        when:
+        properties.properties foo: "one", bar: "two"
+
+        then:
+        properties.properties == [foo:  "one", bar: "two"]
+    }
+
+    def "read and write the properties map directly"() {
+        when:
+        properties.properties = [foo: "one", bar: "two"]
+        properties.properties.bar *= 2
+        properties.properties.remove("foo")
+
+        then:
+        properties.properties == [bar: "twotwo"]
+    }
+}
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerExtensionTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerExtensionTest.groovy
new file mode 100644
index 0000000..12fdf43
--- /dev/null
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerExtensionTest.groovy
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.sonar.runner
+
+import org.gradle.api.Action
+import spock.lang.Specification
+
+class SonarRunnerExtensionTest extends Specification {
+    def extension = new SonarRunnerExtension()
+
+    def "evaluate properties blocks"() {
+        def props = ["key.1": "value 1"]
+
+        when:
+        extension.sonarProperties({
+            it.property "key.2", ["value 2"]
+            it.properties(["key.3": "value 3", "key.4": "value 4"])
+        } as Action)
+        extension.sonarProperties({
+            it.property "key.5", "value 5"
+            it.properties["key.2"] << "value 6"
+            it.properties.remove("key.3")
+        } as Action)
+
+        extension.evaluateSonarPropertiesBlocks(props)
+
+        then:
+        props == ["key.1": "value 1", "key.2": ["value 2", "value 6"], "key.4": "value 4", "key.5": "value 5"]
+    }
+}
diff --git a/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerPluginTest.groovy b/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerPluginTest.groovy
new file mode 100644
index 0000000..eb84184
--- /dev/null
+++ b/subprojects/sonar/src/test/groovy/org/gradle/api/sonar/runner/SonarRunnerPluginTest.groovy
@@ -0,0 +1,373 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.gradle.api.sonar.runner
+
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.internal.jvm.Jvm
+import org.gradle.test.fixtures.file.TestFile
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+
+import spock.lang.Specification
+
+import static spock.util.matcher.HamcrestSupport.*
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+
+class SonarRunnerPluginTest extends Specification {
+    @Rule SetSystemProperties systemProperties
+
+    def rootProject = ProjectBuilder.builder().withName("root").build()
+    def parentProject = ProjectBuilder.builder().withName("parent").withParent(rootProject).build()
+    def childProject = ProjectBuilder.builder().withName("child").withParent(parentProject).build()
+    def childProject2 = ProjectBuilder.builder().withName("child2").withParent(parentProject).build()
+    def leafProject = ProjectBuilder.builder().withName("leaf").withParent(childProject).build()
+
+    def setup() {
+        parentProject.plugins.apply(SonarRunnerPlugin)
+        rootProject.allprojects {
+            group = "group"
+            version = 1.3
+            description = "description"
+            buildDir = "buildDir"
+        }
+    }
+
+    def "adds a sonarRunner extension to the target project (i.e. the project to which the plugin is applied) and its subprojects"() {
+        expect:
+        rootProject.extensions.findByName("sonarRunner") == null
+        parentProject.extensions.findByName("sonarRunner") instanceof SonarRunnerExtension
+        childProject.extensions.findByName("sonarRunner") instanceof SonarRunnerExtension
+    }
+
+    def "adds a sonarRunner task to the target project"() {
+        expect:
+        parentProject.tasks.findByName("sonarRunner") instanceof SonarRunner
+        childProject.tasks.findByName("sonarRunner") == null
+    }
+
+    def "makes sonarRunner task depend on test tasks of the target project and its subprojects"() {
+        when:
+        rootProject.plugins.apply(JavaPlugin)
+        parentProject.plugins.apply(JavaPlugin)
+        childProject.plugins.apply(JavaPlugin)
+
+        then:
+        expect(parentProject.tasks.sonarRunner, dependsOnPaths(containsInAnyOrder(":parent:test", ":parent:child:test")))
+    }
+
+    def "doesn't make sonarRunner task depend on test task of skipped projects"() {
+        when:
+        rootProject.plugins.apply(JavaPlugin)
+        parentProject.plugins.apply(JavaPlugin)
+        childProject.plugins.apply(JavaPlugin)
+        childProject.sonarRunner.skipProject = true
+
+        then:
+        expect(parentProject.tasks.sonarRunner, dependsOnPaths(contains(":parent:test")))
+    }
+
+    def "adds default properties for target project and its subprojects"() {
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.sources"] == ""
+        properties["sonar.projectName"] == "parent"
+        properties["sonar.projectDescription"] == "description"
+        properties["sonar.projectVersion"] == "1.3"
+        properties["sonar.projectBaseDir"] == parentProject.projectDir as String
+        properties["sonar.working.directory"] == new File(parentProject.buildDir, "sonar") as String
+        properties["sonar.dynamicAnalysis"] == "reuseReports"
+
+        and:
+        properties["child.sonar.sources"] == ""
+        properties["child.sonar.projectName"] == "child"
+        properties["child.sonar.projectDescription"] == "description"
+        properties["child.sonar.projectVersion"] == "1.3"
+        properties["child.sonar.projectBaseDir"] == childProject.projectDir as String
+        properties["child.sonar.dynamicAnalysis"] == "reuseReports"
+    }
+
+    def "adds additional default properties for target project"() {
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.projectKey"] == "group:parent"
+        properties["sonar.environment.information.key"] == "Gradle"
+        properties["sonar.environment.information.version"] == parentProject.gradle.gradleVersion
+        properties["sonar.working.directory"] == new File(parentProject.buildDir, "sonar") as String
+
+        and:
+        !properties.containsKey("child.sonar.projectKey") // default left to Sonar
+        !properties.containsKey("child.sonar.environment.information.key")
+        !properties.containsKey("child.sonar.environment.information.version")
+        !properties.containsKey('child.sonar.working.directory')
+    }
+
+    def "defaults projectKey to project.name if project.group isn't set"() {
+        parentProject.group = "" // or null, but only rootProject.group can effectively be set to null
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.projectKey"] == "parent"
+    }
+
+    def "adds additional default properties for 'java-base' projects"() {
+        parentProject.plugins.apply(JavaBasePlugin)
+        childProject.plugins.apply(JavaBasePlugin)
+        parentProject.sourceCompatibility = 1.5
+        parentProject.targetCompatibility = 1.6
+        childProject.sourceCompatibility = 1.6
+        childProject.targetCompatibility = 1.7
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.java.source"] == "1.5"
+        properties["sonar.java.target"] == "1.6"
+        properties["child.sonar.java.source"] == "1.6"
+        properties["child.sonar.java.target"] == "1.7"
+    }
+
+    def "adds additional default properties for 'java' projects"() {
+        parentProject.plugins.apply(JavaPlugin)
+
+        parentProject.sourceSets.main.java.srcDirs = ["src"]
+        parentProject.sourceSets.test.java.srcDirs = ["test"]
+        parentProject.sourceSets.main.output.classesDir = "$parentProject.buildDir/out"
+        parentProject.sourceSets.main.output.resourcesDir = "$parentProject.buildDir/out"
+        parentProject.sourceSets.main.runtimeClasspath += parentProject.files("lib/SomeLib.jar")
+
+        new TestFile(parentProject.projectDir).create {
+            src {}
+            test {}
+            buildDir {
+                out {}
+                "test-results" {}
+            }
+            lib {
+                file("SomeLib.jar")
+            }
+        }
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.sources"] == new File(parentProject.projectDir, "src") as String
+        properties["sonar.tests"] == new File(parentProject.projectDir, "test") as String
+        properties["sonar.binaries"].contains(new File(parentProject.buildDir, "out") as String)
+        properties["sonar.libraries"].contains(new File(parentProject.projectDir, "lib/SomeLib.jar") as String)
+        properties["sonar.surefire.reportsPath"] == new File(parentProject.buildDir, "test-results") as String
+    }
+
+    def "only adds existing directories"() {
+        parentProject.plugins.apply(JavaPlugin)
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        !properties.containsKey("sonar.tests")
+        !properties.containsKey("sonar.binaries")
+        properties.containsKey("sonar.libraries") == (Jvm.current().getRuntimeJar() != null)
+        !properties.containsKey("sonar.surefire.reportsPath")
+    }
+
+    def "adds empty 'sonar.sources' property if no sources exist (because Sonar Runner 2.0 always expects this property to be set)"() {
+        childProject2.plugins.apply(JavaPlugin)
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.sources"] == ""
+        properties["child.sonar.sources"] == ""
+        properties["child2.sonar.sources"] == ""
+        properties["child.leaf.sonar.sources"] == ""
+    }
+
+    def "allows to configure Sonar properties via 'sonarRunner' extension"() {
+        parentProject.sonarRunner.sonarProperties {
+            property "sonar.some.key", "some value"
+        }
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.some.key"] == "some value"
+    }
+
+    def "prefixes property keys of subprojects"() {
+        childProject.sonarRunner.sonarProperties {
+            property "sonar.some.key", "other value"
+        }
+        leafProject.sonarRunner.sonarProperties {
+            property "sonar.some.key", "other value"
+        }
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["child.sonar.some.key"] == "other value"
+        properties["child.leaf.sonar.some.key"] == "other value"
+    }
+
+    def "adds 'modules' properties declaring (prefixes of) subprojects"() {
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.modules"] in ["child,child2", "child2,child"]
+        properties["child.sonar.modules"] == "leaf"
+        !properties.containsKey("child2.sonar.modules")
+        !properties.containsKey("child.leaf.sonar.modules")
+    }
+
+    def "handles 'modules' properties correctly if plugin is applied to root project"() {
+        def rootProject = ProjectBuilder.builder().withName("root").build()
+        def project = ProjectBuilder.builder().withName("parent").withParent(rootProject).build()
+        def project2 = ProjectBuilder.builder().withName("parent2").withParent(rootProject).build()
+        def childProject = ProjectBuilder.builder().withName("child").withParent(project).build()
+
+        rootProject.plugins.apply(SonarRunnerPlugin)
+
+        when:
+        def properties = rootProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.modules"] in ["parent,parent2", "parent2,parent"]
+        properties["parent.sonar.modules"] == "child"
+        !properties.containsKey("parent2.sonar.modules")
+        !properties.containsKey("parent.child.sonar.modules")
+
+    }
+
+    def "evaluates 'sonarRunner' block lazily"() {
+        parentProject.version = "1.0"
+        parentProject.sonarRunner.sonarProperties {
+            property "sonar.projectVersion", parentProject.version
+        }
+        parentProject.version = "1.2.3"
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.projectVersion"] == "1.2.3"
+    }
+
+    def "converts Sonar property values to strings"() {
+        def object = new Object() {
+            String toString() {
+                "object"
+            }
+        }
+
+        parentProject.sonarRunner.sonarProperties {
+            property "some.object", object
+            property "some.list", [1, object, 2]
+        }
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["some.object"] == "object"
+        properties["some.list"] == "1,object,2"
+    }
+
+    def "removes Sonar properties with null values"() {
+        parentProject.sonarRunner.sonarProperties {
+            property "some.key", null
+        }
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        !properties.containsKey("some.key")
+    }
+
+    def "allows to set Sonar properties for target project via 'sonar.xyz' system properties"() {
+        System.setProperty("sonar.some.key", "some value")
+        System.setProperty("sonar.projectVersion", "3.2")
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.some.key"] == "some value"
+        properties["sonar.projectVersion"] == "3.2"
+
+        and:
+        !properties.containsKey("child.sonar.some.key")
+        properties["child.sonar.projectVersion"] == "1.3"
+    }
+
+    def "handles system properties correctly if plugin is applied to root project"() {
+        def rootProject = ProjectBuilder.builder().withName("root").build()
+        def project = ProjectBuilder.builder().withName("parent").withParent(rootProject).build()
+
+        rootProject.allprojects { version = 1.3 }
+        rootProject.plugins.apply(SonarRunnerPlugin)
+        System.setProperty("sonar.some.key", "some value")
+        System.setProperty("sonar.projectVersion", "3.2")
+
+        when:
+        def properties = rootProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.some.key"] == "some value"
+        properties["sonar.projectVersion"] == "3.2"
+
+        and:
+        !properties.containsKey("parent.sonar.some.key")
+        properties["parent.sonar.projectVersion"] == "1.3"
+    }
+
+    def "system properties win over values set in build script"() {
+        System.setProperty("sonar.some.key", "win")
+        parentProject.sonarRunner.sonarProperties {
+            property "sonar.some.key", "lose"
+        }
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        properties["sonar.some.key"] == "win"
+    }
+
+    def "doesn't add Sonar properties for skipped projects"() {
+        childProject.sonarRunner.skipProject = true
+
+        when:
+        def properties = parentProject.tasks.sonarRunner.sonarProperties
+
+        then:
+        !properties.any { key, value -> key.startsWith("child.sonar.") }
+    }
+}
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 5701102..e3a6665 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
@@ -27,7 +27,7 @@ import org.junit.Rule
 
 class SamplesToolingApiIntegrationTest extends AbstractIntegrationSpec {
 
-    @Rule public final Sample sample = new Sample()
+    @Rule public final Sample sample = new Sample(temporaryFolder)
 
     private IntegrationTestBuildContext buildContext
 
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 d3fba5a..be64f48 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
@@ -163,8 +163,9 @@ allprojects {
         def stopTimeoutMs = 10000
         def retryIntervalMs = 500
 
-        def path = executer.gradleUserHomeDir.absolutePath
-        def path1 = distribution.gradleHomeDir.absolutePath
+        def gradleUserHomeDirPath = executer.gradleUserHomeDir.absolutePath
+        def gradleHomeDirPath = distribution.gradleHomeDir.absolutePath
+
         buildFile << """
             apply plugin: 'java'
             apply plugin: 'application'
@@ -182,7 +183,7 @@ allprojects {
             mainClassName = 'Main'
 
             run {
-                args = ["${TextUtil.escapeString(path1)}", "${TextUtil.escapeString(path)}"]
+                args = ["${TextUtil.escapeString(gradleHomeDirPath)}", "${TextUtil.escapeString(gradleUserHomeDirPath)}"]
                 systemProperty 'org.gradle.daemon.idletimeout', 10000
                 systemProperty 'org.gradle.daemon.registry.base', "${TextUtil.escapeString(projectDir.file("daemon").absolutePath)}"
             }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy
index fc6d9a1..b20c450 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/fixture/ConfigurableOperation.groovy
@@ -16,7 +16,7 @@
 
 package org.gradle.integtests.tooling.fixture
 
-import org.gradle.tooling.LongRunningOperation
+import org.gradle.tooling.ModelBuilder
 import org.gradle.tooling.ProgressListener
 
 /**
@@ -24,20 +24,20 @@ import org.gradle.tooling.ProgressListener
  */
 class ConfigurableOperation {
 
-    LongRunningOperation operation
+    def operation
 
     def progressMessages = []
     def listener = { event -> progressMessages << event.description } as ProgressListener
     def stdout = new ByteArrayOutputStream()
     def stderr = new ByteArrayOutputStream()
+    Object modelInstance
 
-    public ConfigurableOperation(LongRunningOperation operation) {
+    //LongRunningOperation is only available since milestone-7, hence omitting the type
+    public ConfigurableOperation(operation) {
         init(operation)
     }
 
-    public ConfigurableOperation() {}
-
-    void init(LongRunningOperation operation) {
+    void init(operation) {
         this.operation = operation
         this.operation.addProgressListener(listener)
         this.operation.standardOutput = stdout
@@ -60,4 +60,16 @@ class ConfigurableOperation {
     List getProgressMessages() {
         return progressMessages
     }
+
+    ConfigurableOperation buildModel() {
+        assert operation instanceof ModelBuilder
+        def model = (ModelBuilder) operation;
+        this.modelInstance = model.get()
+        this
+    }
+
+    Object getModel() {
+        assert modelInstance != null : "Model was not built."
+        this.modelInstance
+    }
 }
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 8d3077a..e0003cb 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
@@ -80,6 +80,24 @@ abstract class ToolingApiSpecification extends Specification {
         toolingApi.withConnection(connector, cl)
     }
 
+    public ConfigurableOperation withModel(Class modelType, Closure cl = {}) {
+        withConnection {
+            def model = it.model(modelType)
+            cl(model)
+            new ConfigurableOperation(model).buildModel()
+        }
+    }
+
+    public ConfigurableOperation withBuild(Closure cl = {}) {
+        withConnection {
+            def build = it.newBuild()
+            cl(build)
+            def out = new ConfigurableOperation(build)
+            build.run()
+            out
+        }
+    }
+
     def connector() {
         toolingApi.connector()
     }
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
index 8424762..5840fef 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiBuildExecutionCrossVersionSpec.groovy
@@ -19,7 +19,6 @@ import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
 import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.BuildException
-import org.gradle.tooling.ProgressListener
 import org.gradle.tooling.model.GradleProject
 import org.gradle.tooling.model.Task
 import org.gradle.tooling.model.eclipse.EclipseProject
@@ -82,19 +81,13 @@ apply plugin: 'java'
 System.out.println 'this is stdout'
 System.err.println 'this is stderr'
 '''
-        def progressMessages = []
-
         when:
-        withConnection { connection ->
-            def build = connection.newBuild()
-            build.addProgressListener({ event -> progressMessages << event.description } as ProgressListener)
-            build.run()
-        }
+        def progress = withBuild().progressMessages
 
         then:
-        progressMessages.size() >= 2
-        progressMessages.pop() == ''
-        progressMessages.every { it }
+        progress.size() >= 2
+        progress.pop() == ''
+        progress.every { it }
     }
 
     def "tooling api reports build failure"() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
index 8cd1688..fc782a4 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m5/ToolingApiModelCrossVersionSpec.groovy
@@ -19,7 +19,6 @@ import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
 import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
 import org.gradle.tooling.BuildException
-import org.gradle.tooling.ProgressListener
 import org.gradle.tooling.model.GradleProject
 
 @MinToolingApiVersion('1.0-milestone-5')
@@ -31,19 +30,13 @@ System.out.println 'this is stdout'
 System.err.println 'this is stderr'
 '''
 
-        def progressMessages = []
-
         when:
-        withConnection { connection ->
-            def model = connection.model(GradleProject.class)
-            model.addProgressListener({ event -> progressMessages << event.description } as ProgressListener)
-            return model.get()
-        }
+        def progress = withModel(GradleProject.class).progressMessages
 
         then:
-        progressMessages.size() >= 2
-        progressMessages.pop() == ''
-        progressMessages.every { it }
+        progress.size() >= 2
+        progress.pop() == ''
+        progress.every { it }
     }
 
     def "tooling api reports failure to build model"() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy
deleted file mode 100644
index c13685a..0000000
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesCrossVersionSpec.groovy
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2011 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.gradle.integtests.tooling.m8
-
-import org.gradle.integtests.fixtures.AvailableJavaHomes
-import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
-import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
-import org.gradle.integtests.tooling.fixture.TextUtil
-import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
-import org.gradle.tooling.model.build.BuildEnvironment
-import spock.lang.IgnoreIf
-
- at MinToolingApiVersion('1.0-milestone-8')
- at MinTargetGradleVersion('1.0-milestone-8')
-class GradlePropertiesCrossVersionSpec extends ToolingApiSpecification {
-
-    def setup() {
-        //this test does not make any sense in embedded mode
-        //as we don't own the process
-        toolingApi.isEmbedded = false
-    }
-
-    def "tooling api honours jvm args specified in gradle.properties"() {
-        file('build.gradle') << """
-assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx16m')
-assert System.getProperty('some-prop') == 'some-value'
-"""
-        file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
-
-        when:
-        BuildEnvironment env = toolingApi.withConnection { connection ->
-            connection.newBuild().run() //the assert
-            connection.getModel(BuildEnvironment.class)
-        }
-
-        then:
-        env.java.jvmArguments.contains('-Xmx16m')
-    }
-
-    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
-    def "tooling api honours java home specified in gradle.properties"() {
-        File javaHome = AvailableJavaHomes.bestAlternative
-        String javaHomePath = TextUtil.escapeString(javaHome.canonicalPath)
-
-        file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
-
-        file('gradle.properties') << "org.gradle.java.home=$javaHomePath"
-
-        when:
-        BuildEnvironment env = toolingApi.withConnection { connection ->
-            connection.newBuild().run() //the assert
-            connection.getModel(BuildEnvironment.class)
-        }
-
-        then:
-        env.java.javaHome == javaHome
-    }
-}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesToolingApiCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesToolingApiCrossVersionSpec.groovy
new file mode 100644
index 0000000..4573b64
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/GradlePropertiesToolingApiCrossVersionSpec.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.gradle.integtests.tooling.m8
+
+import org.gradle.integtests.fixtures.AvailableJavaHomes
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.TextUtil
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.build.BuildEnvironment
+import spock.lang.IgnoreIf
+
+ at MinToolingApiVersion('1.0-milestone-8')
+ at MinTargetGradleVersion('1.0-milestone-8')
+class GradlePropertiesToolingApiCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        //this test does not make any sense in embedded mode
+        //as we don't own the process
+        toolingApi.isEmbedded = false
+    }
+
+    def "tooling api honours jvm args specified in gradle.properties"() {
+        file('build.gradle') << """
+assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.contains('-Xmx16m')
+assert System.getProperty('some-prop') == 'some-value'
+"""
+        file('gradle.properties') << "org.gradle.jvmargs=-Dsome-prop=some-value -Xmx16m"
+
+        when:
+        BuildEnvironment env = toolingApi.withConnection { connection ->
+            connection.newBuild().run() //the assert
+            connection.getModel(BuildEnvironment.class)
+        }
+
+        then:
+        env.java.jvmArguments.contains('-Xmx16m')
+    }
+
+    @IgnoreIf({ AvailableJavaHomes.bestAlternative == null })
+    def "tooling api honours java home specified in gradle.properties"() {
+        File javaHome = AvailableJavaHomes.bestAlternative
+        String javaHomePath = TextUtil.escapeString(javaHome.canonicalPath)
+
+        file('build.gradle') << "assert new File(System.getProperty('java.home')).canonicalPath.startsWith('$javaHomePath')"
+
+        file('gradle.properties') << "org.gradle.java.home=$javaHomePath"
+
+        when:
+        BuildEnvironment env = toolingApi.withConnection { connection ->
+            connection.newBuild().run() //the assert
+            connection.getModel(BuildEnvironment.class)
+        }
+
+        then:
+        env.java.javaHome == javaHome
+    }
+}
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/m8/ToolingApiLoggingCrossVersionSpec.groovy
index b82dc6b..39ec358 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
@@ -50,18 +50,11 @@ project.logger.quiet("quiet logging yyy");
 project.logger.info ("info logging yyy");
 project.logger.debug("debug logging yyy");
 """
-        def output = new ByteArrayOutputStream()
-        def error = new ByteArrayOutputStream()
         when:
-        withConnection {
-            it.newBuild()
-                .setStandardOutput(output)
-                .setStandardError(error)
-                .run()
-        }
+        def op = withBuild()
 
         then:
-        def out = output.toString()
+        def out = op.standardOutput
         out.count("debug logging yyy") == 1
         out.count("info logging yyy") == 1
         out.count("quiet logging yyy") == 1
@@ -72,7 +65,7 @@ project.logger.debug("debug logging yyy");
 
         shouldNotContainProviderLogging(out)
 
-        def err = error.toString()
+        def err = op.standardError
         err.count("error logging") == 1
         err.toString().count("sys err") == 1
         err.toString().count("logging yyy") == 0
@@ -95,18 +88,11 @@ project.logger.quiet("quiet logging yyy");
 project.logger.info ("info logging yyy");
 project.logger.debug("debug logging yyy");
 """
-        def output = new ByteArrayOutputStream()
-        def error = new ByteArrayOutputStream()
         when:
-        withConnection {
-            it.newBuild()
-                    .setStandardOutput(output)
-                    .setStandardError(error)
-                    .run()
-        }
+        def op = withBuild()
 
         then:
-        def out = output.toString()
+        def out = op.standardOutput
         out.count("debug logging yyy") == 0
         out.count("info logging yyy") == 0
         out.count("quiet logging yyy") == 1
@@ -117,7 +103,7 @@ project.logger.debug("debug logging yyy");
 
         shouldNotContainProviderLogging(out)
 
-        def err = error.toString()
+        def err = op.standardError
         err.count("error logging") == 1
         err.count("sys err") == 1
         err.count("logging yyy") == 0
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
index 08a0e2b..3495b2c 100644
--- a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r10rc1/PassingCommandLineArgumentsCrossVersionSpec.groovy
@@ -18,7 +18,6 @@
 
 package org.gradle.integtests.tooling.r10rc1
 
-import org.gradle.integtests.tooling.fixture.ConfigurableOperation
 import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
 import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
@@ -92,20 +91,10 @@ class PassingCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecificatio
 """
 
         when:
-        def debug = withConnection {
-            def build = it.newBuild().withArguments('-d')
-            def op = new ConfigurableOperation(build)
-            build.run()
-            op.standardOutput
-        }
+        String debug = withBuild { it.withArguments('-d') }.standardOutput
 
         and:
-        def info = withConnection {
-            def build = it.newBuild().withArguments('-i')
-            def op = new ConfigurableOperation(build)
-            build.run()
-            op.standardOutput
-        }
+        String info = withBuild { it.withArguments('-i') }.standardOutput
 
         then:
         debug.count("debugging stuff") == 1
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 1e22319..b72c830 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
@@ -21,7 +21,6 @@ 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.tooling.ProjectConnection
 import spock.lang.Issue
 
 /**
@@ -49,11 +48,7 @@ class ToolingApiInitScriptCrossVersionIntegrationTest extends ToolingApiSpecific
             it.useGradleUserHomeDir(temporaryFolder.file("user home"))
             it.embedded(false)
         }
-        withConnection { ProjectConnection connection ->
-            def baos = new ByteArrayOutputStream()
-            connection.newBuild().forTasks("echo").setStandardOutput(baos).run()
-            baos.toString()
-        }
+        withBuild { it.forTasks("echo") }.standardOutput
     }
 
     def "init scripts from client distribution are used, not from the test"() {
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/CombiningCommandLineArgumentsCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/CombiningCommandLineArgumentsCrossVersionSpec.groovy
new file mode 100644
index 0000000..1abe493
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/CombiningCommandLineArgumentsCrossVersionSpec.groovy
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+
+
+package org.gradle.integtests.tooling.r15
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import spock.lang.Issue
+
+ at MinToolingApiVersion("1.0")
+ at MinTargetGradleVersion("1.5")
+class CombiningCommandLineArgumentsCrossVersionSpec extends ToolingApiSpecification {
+
+    @Issue("GRADLE-2635")
+    def "can configure build file name and logging"() {
+        file('buildX.gradle') << "logger.info('info message')"
+
+        when:
+        def out = withBuild { it.withArguments('-b', 'buildX.gradle', '-i') }.standardOutput
+
+        then:
+        out.contains('info message')
+    }
+
+    //below was working as expected
+    //the test was added to validate the behavior that was questioned on forums
+    def "supports gradle.properties and changed build file"() {
+        file('gradle.properties') << "systemProp.foo=bar"
+        file('buildX.gradle') << "logger.lifecycle('sys property: ' + System.properties['foo'])"
+
+        when:
+        def out = withBuild { it.withArguments('-b', 'buildX.gradle') }.standardOutput
+
+        then:
+        out.contains('sys property: bar')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/ToolingApiConfigurationOnDemandCrossVersionSpec.groovy b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/ToolingApiConfigurationOnDemandCrossVersionSpec.groovy
new file mode 100644
index 0000000..daaf1e3
--- /dev/null
+++ b/subprojects/tooling-api/src/integTest/groovy/org/gradle/integtests/tooling/r15/ToolingApiConfigurationOnDemandCrossVersionSpec.groovy
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+package org.gradle.integtests.tooling.r15
+
+import org.gradle.integtests.tooling.fixture.MinTargetGradleVersion
+import org.gradle.integtests.tooling.fixture.MinToolingApiVersion
+import org.gradle.integtests.tooling.fixture.ToolingApiSpecification
+import org.gradle.tooling.model.GradleProject
+
+ at MinToolingApiVersion("1.0-milestone-5") //because we acquire GradleProject model
+ at MinTargetGradleVersion("1.5")
+class ToolingApiConfigurationOnDemandCrossVersionSpec extends ToolingApiSpecification {
+
+    def setup() {
+        file("gradle.properties") << "org.gradle.configureondemand=true"
+    }
+
+    def "building model evaluates all projects regardless of configuration on demand mode"() {
+        given:
+        file("settings.gradle") << "include 'api', 'impl', 'other'"
+        file("build.gradle") << """
+            rootProject.description = 'Configure on demand: ' + gradle.startParameter.configureOnDemand + '. Projects configured: '
+            allprojects { afterEvaluate {
+                rootProject.description += project.path + ", "
+            }}
+        """
+
+        when:
+        def op = withModel(GradleProject.class)
+
+        then:
+        op.model.description.contains 'Configure on demand: true'
+        op.model.description.contains 'Projects configured: :, :api, :impl, :other'
+    }
+
+    def "running tasks takes advantage of configuration on demand"() {
+        given:
+        file("settings.gradle") << "include 'api', 'impl', 'other'"
+
+        file("build.gradle") << "allprojects { task foo }"
+        file("impl/build.gradle") << "task bar(dependsOn: ':api:foo')"
+        file("other/build.gradle") << "assert false: 'should not be evaluated'"
+
+        when:
+        withBuild { it.forTasks(":impl:bar") }
+
+        then:
+        noExceptionThrown()
+    }
+}
\ No newline at end of file
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 7d56519..856f2dd 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
@@ -123,8 +123,8 @@ public interface LongRunningOperation {
      * will be thrown at the time the operation is executed via {@link BuildLauncher#run()} or {@link ModelBuilder#get()}.
      * <p>
      * For the list of all Gradle command line options please refer to the user guide
-     * or take a look at the output of the 'gradle -?' command. Supported arguments are those modeled by
-     * {@link org.gradle.StartParameter}.
+     * or take a look at the output of the 'gradle -?' command. Majority of arguments modeled by
+     * {@link org.gradle.StartParameter} are supported.
      * <p>
      * The arguments can potentially override some other settings you have configured.
      * For example, the project directory or Gradle user home directory that are configured
diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java
index 7f95b4c..aaf5dfe 100644
--- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java
+++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/idea/DefaultIdeaModule.java
@@ -23,7 +23,6 @@ import org.gradle.tooling.model.Task;
 import org.gradle.tooling.model.idea.*;
 import org.gradle.tooling.model.internal.ImmutableDomainObjectSet;
 
-import java.io.File;
 import java.io.Serializable;
 import java.util.Collections;
 import java.util.LinkedList;
@@ -38,7 +37,6 @@ public class DefaultIdeaModule implements Serializable, IdeaModule {
     private List<? extends IdeaContentRoot> contentRoots = new LinkedList<IdeaContentRoot>();
     private IdeaProject parent;
 
-    private File moduleFileDir;
     private List<IdeaDependency> dependencies = new LinkedList<IdeaDependency>();
     private GradleProject gradleProject;
 
@@ -75,15 +73,6 @@ public class DefaultIdeaModule implements Serializable, IdeaModule {
         return this;
     }
 
-    public File getModuleFileDir() {
-        return moduleFileDir;
-    }
-
-    public DefaultIdeaModule setModuleFileDir(File moduleFileDir) {
-        this.moduleFileDir = moduleFileDir;
-        return this;
-    }
-
     public DomainObjectSet<IdeaDependency> getDependencies() {
         return new ImmutableDomainObjectSet<IdeaDependency>(dependencies);
     }
@@ -130,7 +119,6 @@ public class DefaultIdeaModule implements Serializable, IdeaModule {
                 + ", gradleProject='" + gradleProject + '\''
                 + ", contentRoots=" + contentRoots
                 + ", compilerOutput=" + compilerOutput
-                + ", moduleFileDir=" + moduleFileDir
                 + ", dependencies count=" + dependencies.size()
                 + '}';
     }
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
index 9bb20c6..b6a4cf1 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
@@ -50,7 +50,7 @@ class LiveOutputIntegrationTest extends AbstractIntegrationTest {
 
     private File javaprojectDir
 
-    @Rule public final Sample sample = new Sample('java/quickstart')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/quickstart')
 
     @Before
     void setUp() {
diff --git a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
index f0c3825..a16c95e 100644
--- a/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
+++ b/subprojects/ui/src/integTest/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
@@ -43,7 +43,7 @@ class MultiprojectProjectAndTaskListIntegrationTest extends AbstractIntegrationT
     static final String SERVICES_NAME = 'services'
     static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
 
-    @Rule public final Sample sample = new Sample('java/multiproject')
+    @Rule public final Sample sample = new Sample(testDirectoryProvider, 'java/multiproject')
     GradlePluginLord gradlePluginLord = new GradlePluginLord()
 
     @Before
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
index 565fa28..f4e3470 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
@@ -33,6 +33,7 @@ import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
 import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
 import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
 import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.internal.SystemProperties;
 import org.gradle.logging.ShowStacktrace;
 import org.gradle.util.GUtil;
 
@@ -143,7 +144,7 @@ public class GradlePluginLord {
         //create the queue that executes the commands. The contents of this interaction are where we actually launch gradle.
         executionQueue = new ExecutionQueue<Request>(new ExecutionQueueInteraction());
 
-        currentDirectory = new File(System.getProperty("user.dir"));
+        currentDirectory = SystemProperties.getCurrentDir();
 
         String gradleHomeProperty = System.getProperty("gradle.home");
         if (gradleHomeProperty != null) {
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
index c52e0d6..b3b888c 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
@@ -193,7 +193,7 @@ public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
     private File browseForDirectory(File initialFile) {
 
         if (initialFile == null) {
-            initialFile = new File(System.getProperty("user.dir"));
+            initialFile = SystemProperties.getCurrentDir();
         }
 
         JFileChooser chooser = new JFileChooser(initialFile);
diff --git a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
index dcfbf3b..7a3e958 100644
--- a/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
+++ b/subprojects/ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
@@ -21,6 +21,7 @@ import org.gradle.gradleplugin.foundation.settings.DOM4JSettingsNode;
 import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
 import org.gradle.gradleplugin.userinterface.swing.common.PreferencesAssistant;
 import org.gradle.gradleplugin.userinterface.swing.generic.SinglePaneUIInstance;
+import org.gradle.internal.SystemProperties;
 import org.gradle.internal.UncheckedException;
 
 import javax.swing.*;
@@ -307,7 +308,7 @@ public class Application implements AlternateUIInteraction {
      * @return the file that we save our settings to.
      */
     private File getSettingsFile() {
-        return new File(System.getProperty("user.dir"), "gradle-app" + SETTINGS_EXTENSION);
+        return new File(SystemProperties.getCurrentDir(), "gradle-app" + SETTINGS_EXTENSION);
     }
 
     private class SettingsImportInteraction implements DOM4JSerializer.ImportInteraction {
diff --git a/version.txt b/version.txt
index 840ca8c..400122e 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-1.4
\ No newline at end of file
+1.5
\ No newline at end of file

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



More information about the pkg-java-commits mailing list