Replace custom caching with Gradle's incremental build (#1187)

Replaced custom caching of the downloaded contracts with Gradle's
incremental build. After that `InitContractsTask` is not needed any
more, as its main purpose was to be a bridge between custom caching
and Gradle's one. Simplified code to download contracts is now part of
the `ContractsCopyTask`.
Also fixed `PublishStubsToScmTask` and removed incremental stuff from
there for now.

Fixes gh-1133
This commit is contained in:
anatoliy-balakirev
2019-08-28 13:13:56 +02:00
committed by Marcin Grzejszczak
parent cdb93f178b
commit d4740706f0
11 changed files with 388 additions and 576 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.api.tasks.Internal
import javax.inject.Inject
import groovy.transform.CompileStatic
@@ -517,10 +519,11 @@ class ContractVerifierExtension {
@Optional
Property<String> proxyHost
/**
* If set to true then will cache the folder where non snapshot contract artifacts got downloaded.
* Not used any more, as we switched to Gradle's incremental build.
*/
@Input
Property<Boolean> cacheDownloadedContracts
@Internal
@Deprecated
boolean cacheDownloadedContracts
@Inject
ContractRepository(ObjectFactory objects) {
@@ -529,7 +532,6 @@ class ContractVerifierExtension {
this.password = objects.property(String)
this.proxyHost = objects.property(String)
this.proxyPort = objects.property(Integer)
this.cacheDownloadedContracts = objects.property(Boolean).convention(true)
}
@Override
@@ -540,7 +542,6 @@ class ContractVerifierExtension {
", password=" + password.getOrNull() +
", proxyPort=" + proxyPort.getOrNull() +
", proxyHost=" + proxyHost.getOrNull() +
", cacheDownloadedContracts=" + cacheDownloadedContracts.get() +
'}'
}
@@ -576,6 +577,10 @@ class ContractVerifierExtension {
this.proxyHost.set(proxyHost.toString())
}
void setProxyPort(Integer proxyPort) {
this.proxyPort.set(proxyPort)
}
void repositoryUrl(String repositoryUrl) {
this.repositoryUrl.set(repositoryUrl)
}

View File

@@ -18,6 +18,8 @@ package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.Immutable
import groovy.transform.ImmutableOptions
import groovy.transform.PackageScope
import org.gradle.api.Action
import org.gradle.api.DefaultTask
@@ -26,18 +28,28 @@ import org.gradle.api.Project
import org.gradle.api.file.CopySpec
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.logging.Logger
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.WorkResult
import org.springframework.cloud.contract.stubrunner.ContractDownloader
import org.springframework.cloud.contract.stubrunner.StubConfiguration
import org.springframework.cloud.contract.stubrunner.StubDownloader
import org.springframework.cloud.contract.stubrunner.StubDownloaderBuilderProvider
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.springframework.cloud.contract.verifier.converter.ToYamlConverter
import org.springframework.util.StringUtils
import java.time.Instant
// TODO: Convert to incremental task: https://docs.gradle.org/current/userguide/custom_tasks.html#incremental_tasks
/**
* Task that copies the contracts in order for the jar task to
@@ -65,12 +77,54 @@ class ContractsCopyTask extends DefaultTask {
Provider<Boolean> excludeBuildFolders
@Input
Provider<Boolean> failOnNoContracts
@Input
Provider<String> includedRootFolderAntPattern
@InputDirectory
Provider<Directory> contractsDirectory
// All fields inside `@Nested` one are properly marked as an `@Input` to work with incremental build:
@Nested
@Optional
ContractVerifierExtension.Dependency contractDependency
@Nested
@Optional
ContractVerifierExtension.ContractRepository contractRepository
@Input
Provider<String> contractsRepository
@Optional
Property<StubRunnerProperties.StubsMode> contractsMode
@Input
Property<Boolean> deleteStubsAfterTest
@Input
MapProperty<String, String> contractsProperties
@Input
@Optional
Property<String> contractsPath
@Input
@Optional
Instant getForceDownloadOfTheLatestContracts() {
// If we have `dynamic` version (`+` or `SNAPSHOT`) - we should mark this task as out of date for every run:
if (shouldDownloadContracts() && getStubConfiguration().isVersionChanging()) {
return Instant.now() // This will trigger re-download of contracts
} else {
return null // This will not trigger re-download of contracts
}
}
@Optional
@InputDirectory
Provider<Directory> getContractsDirectory() {
if (shouldDownloadContracts()) {
return null
} else {
return contractsDirectory
}
}
@Internal
boolean shouldDownloadContracts() {
return StringUtils.hasText(contractDependency.getArtifactId().getOrNull()) ||
StringUtils.hasText(contractDependency.getStringNotation().getOrNull()) ||
StringUtils.hasText(contractRepository.repositoryUrl.getOrNull())
}
@Internal
StubConfiguration getStubConfiguration() {
return GradleContractsDownloaderHelper.stubConfiguration(contractDependency)
}
@OutputDirectory
DirectoryProperty copiedContractsFolder
@@ -81,13 +135,23 @@ class ContractsCopyTask extends DefaultTask {
@TaskAction
void sync() {
File contractsDirectory = config.contractsDirectory.get().asFile
String contractsRepository = config.contractsRepository.get()
final DownloadedData downloadedData = downloadContractsIfNeeded()
final File contractsDirectory
final String antPattern
if (downloadedData) {
contractsDirectory = downloadedData.downloadedContracts
antPattern = "${downloadedData.inclusionProperties.includedRootFolderAntPattern}*.*"
} else {
contractsDirectory = config.contractsDirectory.get().asFile
antPattern = "**/"
}
logger.info("For project [{}] will use contracts provided in the folder [{}]", project.name, contractsDirectory)
final String contractsRepository = config.contractRepository.repositoryUrl.isPresent() ? config.contractRepository.repositoryUrl : ""
throwExceptionWhenFailOnNoContracts(contractsDirectory, contractsRepository)
String antPattern = "${config.includedRootFolderAntPattern.get()}*.*"
String slashSeparatedGroupId = project.group.toString().replace(".", File.separator)
String slashSeparatedAntPattern = antPattern.replace(slashSeparatedGroupId, project.group.toString())
File output = config.copiedContractsFolder.get().asFile
final String slashSeparatedGroupId = project.group.toString().replace(".", File.separator)
final String slashSeparatedAntPattern = antPattern.replace(slashSeparatedGroupId, project.group.toString())
final File output = config.copiedContractsFolder.get().asFile
logger.info("Downloading and unpacking files from [${contractsDirectory}] to [$output]. The inclusion ant patterns are [${antPattern}] and [${slashSeparatedAntPattern}]")
sync(contractsDirectory, antPattern, slashSeparatedAntPattern, config.excludeBuildFolders.get(), output)
if (config.convertToYaml.get()) {
@@ -95,16 +159,20 @@ class ContractsCopyTask extends DefaultTask {
}
}
static Config fromExtension(ContractVerifierExtension extension, TaskProvider<InitContractsTask> initContractsTask, String root, Project project) {
static Config fromExtension(ContractVerifierExtension extension, String root, Project project) {
return new Config(
convertToYaml: extension.convertToYaml,
excludeBuildFolders: extension.excludeBuildFolders,
failOnNoContracts: extension.failOnNoContracts,
includedRootFolderAntPattern: initContractsTask.flatMap { it.config.includedRootFolderAntPattern },
contractsDirectory: initContractsTask.flatMap { it.config.initialisedContractsDirectory },
contractsRepository: extension.contractRepository.repositoryUrl.isPresent() ? extension.contractRepository.repositoryUrl : project.provider({ String s -> "" }),
contractsDirectory: extension.contractsDslDir,
copiedContractsFolder: createTaskOutput(root, extension.stubsOutputDir, ContractsCopyTask.CONTRACTS, project),
backupContractsFolder: createTaskOutput(root, extension.stubsOutputDir, ContractsCopyTask.BACKUP, project)
backupContractsFolder: createTaskOutput(root, extension.stubsOutputDir, ContractsCopyTask.BACKUP, project),
contractDependency: extension.contractDependency,
contractRepository: extension.contractRepository,
contractsMode: extension.contractsMode,
deleteStubsAfterTest: extension.deleteStubsAfterTest,
contractsProperties: extension.contractsProperties,
contractsPath: extension.contractsPath
)
}
@@ -115,8 +183,6 @@ class ContractsCopyTask extends DefaultTask {
}
protected WorkResult sync(File file, String antPattern, String slashSeparatedAntPattern, boolean excludeBuildFolders, File outputContractsFolder) {
// TODO: Is there any better way to make it statically compiled, avoiding explicit creation of new Action?
// sync will remove files from target if they are removed from source. So using it here instead of copy:
return project.sync(new Action<CopySpec>() {
@Override
void execute(final CopySpec spec) {
@@ -135,6 +201,35 @@ class ContractsCopyTask extends DefaultTask {
})
}
private DownloadedData downloadContractsIfNeeded() {
if (config.shouldDownloadContracts()) {
logger.info("Project has group id [{}], artifact id [{}]", project.group, project.name)
logger.info("For project [${project.name}] Download dependency is provided - will download contract jars")
logger.info("Contract dependency [{}]", config.contractDependency)
StubConfiguration configuration = config.getStubConfiguration()
logger.info("Got the following contract dependency to download [{}]", configuration)
logger.info("The contract dependency is a changing one [{}]", configuration.isVersionChanging())
final StubDownloader downloader = new StubDownloaderBuilderProvider().get(
StubRunnerOptionsFactory.createStubRunnerOptions(config.contractRepository,
config.contractsMode.getOrNull(), config.deleteStubsAfterTest.get(),
config.contractsProperties.get(), config.failOnNoContracts.get()))
final ContractDownloader contractDownloader = new ContractDownloader(downloader, configuration,
config.contractsPath.getOrNull(), project.group as String, project.name, project.version as String)
final File downloadedContracts = contractDownloader.unpackAndDownloadContracts();
final ContractDownloader.InclusionProperties inclusionProperties =
contractDownloader.createNewInclusionProperties(downloadedContracts)
// TODO: inclusionProperties.includedContracts is never used eventually. Review this:
return new DownloadedData(
downloadedContracts: contractsSubDirIfPresent(downloadedContracts, logger),
inclusionProperties: inclusionProperties
)
} else {
return null
}
}
private static DirectoryProperty createTaskOutput(String root, DirectoryProperty stubsOutputDir, String suffix, Project project) {
Provider<Directory> provider = stubsOutputDir.flatMap {
Directory dir = it
@@ -162,4 +257,22 @@ class ContractsCopyTask extends DefaultTask {
+ "] .\nPlease make sure that the contracts were defined, or set the [failOnNoContracts] flag to [false]")
}
}
private static File contractsSubDirIfPresent(File contractsDirectory, Logger logger) {
File contracts = new File(contractsDirectory, "contracts")
if (contracts.exists()) {
if (logger.isDebugEnabled()) {
logger.debug("Contracts folder found [" + contracts + "]")
}
contractsDirectory = contracts
}
return contractsDirectory
}
@ImmutableOptions(knownImmutableClasses = [File, ContractDownloader.InclusionProperties])
@Immutable
private static class DownloadedData {
final File downloadedContracts
final ContractDownloader.InclusionProperties inclusionProperties
}
}

View File

@@ -54,8 +54,6 @@ class GenerateServerTestsTask extends DefaultTask {
@InputDirectory
Provider<Directory> contractsDslDir
@Input
Provider<String> includedContracts
@Input
@Optional
Provider<String> nameSuffixForTests
@Input
@@ -101,12 +99,6 @@ class GenerateServerTestsTask extends DefaultTask {
logger.info("Generated test sources dir [${generatedTestSources}]")
logger.info("Generated test resources dir [${generatedTestResources}]")
File contractsDslDir = config.contractsDslDir.get().asFile
// There used to be some bug in old code, which was always setting `includedContracts` to ".*" instead of
// getting it from the config (which is coming from contracts downloader). In fact, that `correct` behaviour
// doesn't even make sense here as contracts are already copied, so that inclusion filter will not include
// anything. So for now just restoring this old behaviour, but it must be reviewed. Probably that
// `includedContracts` should be used in the `copyContracts` task instead?
// String includedContracts = config.includedContracts.get()
String includedContracts = ".*"
project.logger.info("Spring Cloud Contract Verifier Plugin: Invoking test sources generation")
project.logger.info("Contracts are unpacked to [${contractsDslDir}]")
@@ -145,11 +137,9 @@ class GenerateServerTestsTask extends DefaultTask {
}
}
static Config fromExtension(ContractVerifierExtension extension, TaskProvider<InitContractsTask> initContractsTask,
TaskProvider<ContractsCopyTask> copyContractsTask) {
static Config fromExtension(ContractVerifierExtension extension, TaskProvider<ContractsCopyTask> copyContractsTask) {
return new Config(
contractsDslDir: copyContractsTask.flatMap { it.config.copiedContractsFolder },
includedContracts: initContractsTask.flatMap { it.config.includedContracts },
nameSuffixForTests: extension.nameSuffixForTests,
basePackageForTests: extension.basePackageForTests,
baseClassForTests: extension.baseClassForTests,

View File

@@ -1,139 +0,0 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.Immutable
import groovy.transform.ImmutableOptions
import groovy.transform.PackageScope
import org.gradle.api.Project
import org.gradle.api.logging.Logger
import org.springframework.cloud.contract.stubrunner.ContractDownloader
import org.springframework.cloud.contract.stubrunner.StubConfiguration
import org.springframework.cloud.contract.stubrunner.StubDownloader
import org.springframework.cloud.contract.stubrunner.StubDownloaderBuilderProvider
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import org.springframework.util.StringUtils
import java.util.concurrent.ConcurrentHashMap
/**
* @author Marcin Grzejszczak
*/
@PackageScope
@CompileStatic
class GradleContractsDownloader {
private static final String LATEST_VERSION = '+'
private final Project project
private final Logger logger
protected static final Map<StubConfiguration, File> downloadedContract = new ConcurrentHashMap<>()
GradleContractsDownloader(Project project, Logger logger) {
this.project = project
this.logger = logger
}
DownloadedData downloadAndUnpackContractsIfRequired(ContractVerifierExtension.Dependency contractDependency,
ContractVerifierExtension.ContractRepository contractRepository, String contractsPath,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
if (!shouldDownloadContracts(contractDependency, contractRepository)) {
return null
}
logger.info("Project has group id [{}], artifact id [{}]", project.group, project.name)
// download contracts, unzip them and pass as output directory
logger.info("For project [${project.name}] Download dependency is provided - will download contract jars")
logger.info("Contract dependency [{}]", contractDependency)
StubConfiguration configuration = stubConfiguration(contractDependency)
logger.info("Got the following contract dependency to download [{}]", configuration)
logger.info("The contract dependency is a changing one [{}] and cache download switch is set to [{}]",
configuration.isVersionChanging(), contractRepository.cacheDownloadedContracts.get())
if (!configuration.isVersionChanging() && contractRepository.cacheDownloadedContracts.get()) {
logger.info("Resolved a non changing version - will try to return the folder from a cache")
File cachedFolder = downloadedContract.get(configuration)
if (cachedFolder) {
logger.info("For project [{}] returning the cached location of the contracts", project.name)
final ContractDownloader.InclusionProperties inclusionProperties =
contractDownloader(configuration, contractRepository, contractsPath, contractsMode,
deleteStubsAfterTest, contractsProperties, failOnNoContracts).createNewInclusionProperties(cachedFolder)
return new DownloadedData(
downloadedContracts: contractsSubDirIfPresent(cachedFolder, logger),
inclusionProperties: inclusionProperties
)
}
}
final ContractDownloader contractDownloader =
contractDownloader(configuration, contractRepository, contractsPath, contractsMode, deleteStubsAfterTest,
contractsProperties, failOnNoContracts);
final File downloadedContracts = contractDownloader.unpackAndDownloadContracts();
final ContractDownloader.InclusionProperties inclusionProperties =
contractDownloader.createNewInclusionProperties(downloadedContracts)
downloadedContract.put(configuration, downloadedContracts)
return new DownloadedData(
downloadedContracts: contractsSubDirIfPresent(downloadedContracts, logger),
inclusionProperties: inclusionProperties
)
}
private static File contractsSubDirIfPresent(File contractsDirectory, Logger logger) {
File contracts = new File(contractsDirectory, "contracts")
if (contracts.exists()) {
if (logger.isDebugEnabled()) {
logger.debug("Contracts folder found [" + contracts + "]")
}
contractsDirectory = contracts
}
return contractsDirectory
}
@PackageScope
StubConfiguration stubConfiguration(ContractVerifierExtension.Dependency contractDependency) {
String groupId = contractDependency.groupId.getOrNull()
String artifactId = contractDependency.artifactId.getOrNull()
String version = StringUtils.hasText(contractDependency.version.getOrNull()) ?
contractDependency.version.getOrNull() : LATEST_VERSION
String classifier = contractDependency.classifier.getOrNull()
String stringNotation = contractDependency.stringNotation.getOrNull()
if (StringUtils.hasText(stringNotation)) {
StubConfiguration stubConfiguration = new StubConfiguration(stringNotation)
return new StubConfiguration(stubConfiguration.groupId, stubConfiguration.artifactId,
stubConfiguration.version, stubConfiguration.classifier)
}
return new StubConfiguration(groupId, artifactId, version, classifier)
}
protected ContractDownloader contractDownloader(StubConfiguration configuration,
ContractVerifierExtension.ContractRepository contractRepository,
String contractsPath, StubRunnerProperties.StubsMode contractsMode,
boolean deleteStubsAfterTest, Map<String, String> contractsProperties,
boolean failOnNoContracts) {
return new ContractDownloader(stubDownloader(contractRepository, contractsMode, deleteStubsAfterTest, contractsProperties, failOnNoContracts),
configuration, contractsPath, project.group as String, project.name, project.version as String)
}
protected StubDownloader stubDownloader(ContractVerifierExtension.ContractRepository contractRepository,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
StubDownloaderBuilderProvider provider = new StubDownloaderBuilderProvider()
return provider.get(StubRunnerOptionsFactory.createStubRunnerOptions(contractRepository, contractsMode,
deleteStubsAfterTest, contractsProperties, failOnNoContracts))
}
private static boolean shouldDownloadContracts(ContractVerifierExtension.Dependency contractDependency,
ContractVerifierExtension.ContractRepository contractRepository) {
return StringUtils.hasText(contractDependency.getArtifactId().getOrNull()) ||
StringUtils.hasText(contractDependency.getStringNotation().getOrNull()) ||
StringUtils.hasText(contractRepository.repositoryUrl.getOrNull())
}
@ImmutableOptions(knownImmutableClasses = [File, ContractDownloader.InclusionProperties])
@Immutable
static class DownloadedData {
final File downloadedContracts
final ContractDownloader.InclusionProperties inclusionProperties
}
}

View File

@@ -0,0 +1,33 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.springframework.cloud.contract.stubrunner.StubConfiguration
import org.springframework.util.StringUtils
/**
* @author Anatoliy Balakirev
*/
@PackageScope
@CompileStatic
class GradleContractsDownloaderHelper {
private static final String LATEST_VERSION = '+'
@PackageScope
static StubConfiguration stubConfiguration(ContractVerifierExtension.Dependency contractDependency) {
String groupId = contractDependency.groupId.getOrNull()
String artifactId = contractDependency.artifactId.getOrNull()
String version = StringUtils.hasText(contractDependency.version.getOrNull()) ?
contractDependency.version.getOrNull() : LATEST_VERSION
String classifier = contractDependency.classifier.getOrNull()
String stringNotation = contractDependency.stringNotation.getOrNull()
if (StringUtils.hasText(stringNotation)) {
StubConfiguration stubConfiguration = new StubConfiguration(stringNotation)
return new StubConfiguration(stubConfiguration.groupId, stubConfiguration.artifactId,
stubConfiguration.version, stubConfiguration.classifier)
}
return new StubConfiguration(groupId, artifactId, version, classifier)
}
}

View File

@@ -1,91 +0,0 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.TaskAction
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
/**
* @author Anatoliy Balakirev
*/
@PackageScope
@CompileStatic
class InitContractsTask extends DefaultTask {
static final String TASK_NAME = 'initContracts'
protected final GradleContractsDownloader contractDownloader = new GradleContractsDownloader(project, logger)
@Nested
Config config
static class Config {
// All fields inside `@Nested` one are properly marked as an `@Input` to work with incremental build:
@Nested
@Optional
ContractVerifierExtension.Dependency contractDependency
@Nested
@Optional
ContractVerifierExtension.ContractRepository contractRepository
@Input
@Optional
Property<StubRunnerProperties.StubsMode> contractsMode
@Input
Property<Boolean> deleteStubsAfterTest
@Input
Property<Boolean> failOnNoContracts
@Input
MapProperty<String, String> contractsProperties
@Input
@Optional
Property<String> contractsPath
@Internal
Property<String> includedContracts
@Internal
Property<String> includedRootFolderAntPattern
// TODO: Enable it when all caching from `GradleContractsDownloader` is replaced with Gradle's one here:
// @OutputDirectory
DirectoryProperty initialisedContractsDirectory
}
@TaskAction
void initContracts() {
GradleContractsDownloader.DownloadedData downloaded =
contractDownloader.downloadAndUnpackContractsIfRequired(config.contractDependency,
config.contractRepository, config.contractsPath.getOrNull(), config.contractsMode.getOrNull(),
config.deleteStubsAfterTest.get(), config.contractsProperties.get(), config.failOnNoContracts.get())
if (downloaded) {
config.includedContracts.set(downloaded.inclusionProperties.includedContracts)
config.includedRootFolderAntPattern.set(downloaded.inclusionProperties.includedRootFolderAntPattern)
config.initialisedContractsDirectory.set(downloaded.downloadedContracts)
}
logger.info("For project [{}] will use contracts provided in the folder [{}]", project.name,
config.initialisedContractsDirectory.get())
}
static Config fromExtension(ContractVerifierExtension extension, Project project) {
DirectoryProperty initialisedContractsDirectory = project.objects.directoryProperty()
initialisedContractsDirectory.set(extension.contractsDslDir)
return new Config(
contractDependency: extension.contractDependency,
contractRepository: extension.contractRepository,
contractsMode: extension.contractsMode,
deleteStubsAfterTest: extension.deleteStubsAfterTest,
failOnNoContracts: extension.failOnNoContracts,
contractsProperties: extension.contractsProperties,
contractsPath: extension.contractsPath,
includedContracts: project.objects.property(String).convention(".*"),
includedRootFolderAntPattern: project.objects.property(String).convention("**/"),
initialisedContractsDirectory: initialisedContractsDirectory
)
}
}

View File

@@ -17,14 +17,12 @@
package org.springframework.cloud.contract.verifier.plugin
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.TaskAction
import org.springframework.cloud.contract.stubrunner.ContractProjectUpdater
import org.springframework.cloud.contract.stubrunner.ScmStubDownloaderBuilder
@@ -44,30 +42,95 @@ import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
class PublishStubsToScmTask extends DefaultTask {
static final String TASK_NAME = 'publishStubsToScm'
@Nested
Config config
private Closure customizationClosure = Closure.IDENTITY
static class Config {
// All fields inside `@Nested` one are properly marked as an `@Input` to work with incremental build:
@Nested
@Optional
private ObjectFactory objects
ContractVerifierExtension.ContractRepository contractRepository
@Input
@Optional
Provider<StubRunnerProperties.StubsMode> contractsMode
@Input
Provider<Boolean> deleteStubsAfterTest
@Input
Provider<Boolean> failOnNoContracts
@Input
Property<StubRunnerProperties.StubsMode> contractsMode
Property<Boolean> deleteStubsAfterTest
Property<Boolean> failOnNoContracts
MapProperty<String, String> contractsProperties
@OutputDirectory
DirectoryProperty stubsOutputDir
void setContractsMode(Property<StubRunnerProperties.StubsMode> contractsMode) {
this.contractsMode = contractsMode
}
void setContractsMode(String contractsMode) {
if (contractsMode) {
this.contractsMode = objects.property(StubRunnerProperties.StubsMode).convention(StubRunnerProperties.StubsMode.valueOf(contractsMode.toUpperCase()))
}
}
void setDeleteStubsAfterTest(Property<Boolean> deleteStubsAfterTest) {
this.deleteStubsAfterTest = deleteStubsAfterTest
}
void setDeleteStubsAfterTest(Boolean deleteStubsAfterTest) {
if (deleteStubsAfterTest != null) {
this.deleteStubsAfterTest = objects.property(Boolean).convention(deleteStubsAfterTest)
}
}
void setFailOnNoContracts(Property<Boolean> failOnNoContracts) {
this.failOnNoContracts = failOnNoContracts
}
void setFailOnNoContracts(Boolean failOnNoContracts) {
if (failOnNoContracts != null) {
this.failOnNoContracts = objects.property(Boolean).convention(failOnNoContracts)
}
}
void setContractsProperties(MapProperty<String, String> contractsProperties) {
this.contractsProperties = contractsProperties
}
void setContractsProperties(Map<String, String> contractsProperties) {
if (contractsProperties) {
this.contractsProperties = objects.mapProperty(String, String).convention(contractsProperties)
}
}
void setStubsOutputDir(DirectoryProperty stubsOutputDir) {
this.stubsOutputDir = stubsOutputDir
}
void setStubsOutputDir(String stubsOutputDir) {
if (stubsOutputDir) {
this.stubsOutputDir = objects.directoryProperty()
this.stubsOutputDir.set(new File(stubsOutputDir))
}
}
void setStubsOutputDir(GString stubsOutputDir) {
if (stubsOutputDir) {
this.stubsOutputDir = objects.directoryProperty()
this.stubsOutputDir.set(new File(stubsOutputDir.toString()))
}
}
void contractRepository(@DelegatesTo(ContractVerifierExtension.ContractRepository) Closure closure) {
closure.delegate = contractRepository
closure.call()
}
@Deprecated
void contractDependency(Closure closure) {
// Here only to preserve backward compatibility. Was never used here.
}
@PackageScope
void setObjects(ObjectFactory objects) {
this.objects = objects
}
}
@TaskAction
void publishStubsToScm() {
applyConfigCustomizations()
if (!shouldRun()) {
return
}
@@ -79,15 +142,15 @@ class PublishStubsToScmTask extends DefaultTask {
new ContractProjectUpdater(stubRunnerOptions).updateContractProject(projectName, config.stubsOutputDir.get().asFile.toPath())
}
static Config fromExtension(ContractVerifierExtension extension) {
static Config fromExtension(ContractVerifierExtension extension, ObjectFactory objects) {
return new Config(
contractRepository: extension.contractRepository,
contractsMode: extension.contractsMode,
failOnNoContracts: extension.failOnNoContracts,
deleteStubsAfterTest: extension.deleteStubsAfterTest,
contractsProperties: extension.contractsProperties,
stubsOutputDir: extension.stubsOutputDir
stubsOutputDir: extension.stubsOutputDir,
objects: objects
)
}
@@ -99,4 +162,38 @@ class PublishStubsToScmTask extends DefaultTask {
}
return true
}
/**
* Some builds might want to customize this task only, overriding settings from plugin.
*/
void customize(@DelegatesTo(Config) Closure closure) {
customizationClosure = closure
}
private void applyConfigCustomizations() {
// Needs to be copied, otherwise properties won't be updated:
config.contractRepository = copy(config.contractRepository, project.objects)
customizationClosure.delegate = config
customizationClosure.call()
}
private static ContractVerifierExtension.ContractRepository copy(ContractVerifierExtension.ContractRepository original, ObjectFactory objects) {
ContractVerifierExtension.ContractRepository copied = new ContractVerifierExtension.ContractRepository(objects)
if (original.repositoryUrl.getOrNull()) {
copied.repositoryUrl.set(original.repositoryUrl.get())
}
if (original.username.getOrNull()) {
copied.username.set(original.username.get())
}
if (original.password.getOrNull()) {
copied.password.set(original.password.get())
}
if (original.proxyPort.getOrNull()) {
copied.proxyPort.set(original.proxyPort.get())
}
if (original.proxyHost.getOrNull()) {
copied.proxyHost.set(original.proxyHost.get())
}
return copied
}
}

View File

@@ -57,12 +57,11 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
ContractVerifierExtension extension = project.extensions.create(EXTENSION_NAME, ContractVerifierExtension)
setConfigurationDefaults(extension)
TaskProvider<InitContractsTask> initContracts = createAndConfigureContractsInitTask(extension)
TaskProvider<ContractsCopyTask> copyContracts = createAndConfigureCopyContractsTask(initContracts, extension)
TaskProvider<ContractsCopyTask> copyContracts = createAndConfigureCopyContractsTask(extension)
TaskProvider<GenerateClientStubsFromDslTask> generateClientStubs = createAndConfigureGenerateClientStubs(extension, copyContracts)
createAndConfigureStubsJarTasks(extension, copyContracts, generateClientStubs)
createGenerateTestsTask(extension, copyContracts, initContracts)
createGenerateTestsTask(extension, copyContracts)
createAndConfigurePublishStubsToScmTask(extension, generateClientStubs)
project.afterEvaluate {
addIdeaTestSources(project, extension)
@@ -112,14 +111,13 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
}
private void createGenerateTestsTask(ContractVerifierExtension extension,
TaskProvider<ContractsCopyTask> copyContracts, TaskProvider<InitContractsTask> initContracts) {
private void createGenerateTestsTask(ContractVerifierExtension extension, TaskProvider<ContractsCopyTask> copyContracts) {
TaskProvider<GenerateServerTestsTask> task = project.tasks.register(GenerateServerTestsTask.TASK_NAME, GenerateServerTestsTask)
task.configure {
it.description = "Generate server tests from the contracts"
it.group = GROUP_NAME
it.enabled = !project.gradle.startParameter.excludedTaskNames.contains("test")
it.config = GenerateServerTestsTask.fromExtension(extension, initContracts, copyContracts)
it.config = GenerateServerTestsTask.fromExtension(extension, copyContracts)
it.dependsOn copyContracts
}
@@ -133,7 +131,7 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
task.configure {
it.description = "The generated stubs get committed to the SCM repo and pushed to origin"
it.group = GROUP_NAME
it.config = PublishStubsToScmTask.fromExtension(extension)
it.config = PublishStubsToScmTask.fromExtension(extension, project.objects)
it.dependsOn generateClientStubs
}
@@ -242,25 +240,12 @@ class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
return task
}
private TaskProvider<ContractsCopyTask> createAndConfigureCopyContractsTask(TaskProvider<InitContractsTask> initContractsTask,
ContractVerifierExtension extension) {
private TaskProvider<ContractsCopyTask> createAndConfigureCopyContractsTask(ContractVerifierExtension extension) {
TaskProvider<ContractsCopyTask> task = project.tasks.register(ContractsCopyTask.TASK_NAME, ContractsCopyTask)
task.configure {
it.description = "Copies contracts to the output folder"
it.group = GROUP_NAME
it.config = ContractsCopyTask.fromExtension(extension, initContractsTask, buildRootPath(), project)
it.dependsOn initContractsTask
}
return task
}
private TaskProvider<InitContractsTask> createAndConfigureContractsInitTask(ContractVerifierExtension extension) {
TaskProvider<InitContractsTask> task = project.tasks.register(InitContractsTask.TASK_NAME, InitContractsTask)
task.configure {
it.description = "Initialises contracts either downloading or using default provided folder"
it.group = GROUP_NAME
it.config = InitContractsTask.fromExtension(extension, project)
it.config = ContractsCopyTask.fromExtension(extension, buildRootPath(), project)
}
return task
}

View File

@@ -70,16 +70,6 @@ class ContractVerifierSpec extends Specification {
project.tasks.verifierStubsJar.getDependsOn().contains(project.tasks.named("copyContracts"))
}
def "should create initContracts task"() {
expect:
project.tasks.named("initContracts") != null
}
def "should configure initContracts task as a dependency of the copyContracts task"() {
expect:
project.tasks.copyContracts.getDependsOn().contains(project.tasks.named("initContracts"))
}
/**
* project.evaluate() is used here in order to trigger the evaluation lifecycle of a project.
* This method is currently exposed via the internal API and is subject to change, however, Gradle

View File

@@ -0,0 +1,86 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.api.internal.provider.DefaultPropertyState
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.springframework.cloud.contract.stubrunner.StubConfiguration
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
*/
class GradleContractsDownloaderHelperSpec extends Specification {
ObjectFactory objectFactory = Mock(ObjectFactory)
def setup() {
// Is there any better way to say that I need a new object on each interaction with mock?
objectFactory.property(String) >>> [prop(String), prop(String), prop(String), prop(String), prop(String)]
}
def "should parse dependency via string notation"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.stringNotation.set(stringNotation)
when:
StubConfiguration stubConfig = GradleContractsDownloaderHelper.stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via direct setting"() {
given:
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.groupId.set("com.example")
dep.artifactId.set("foo")
dep.version.set("1.0.0")
dep.classifier.set("stubs")
when:
StubConfiguration stubConfig = GradleContractsDownloaderHelper.stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via string notation with methods"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.stringNotation.set(stringNotation)
when:
StubConfiguration stubConfig = GradleContractsDownloaderHelper.stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via direct setting with methods"() {
given:
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.groupId.set("com.example")
dep.artifactId.set("foo")
dep.version.set("1.0.0")
dep.classifier.set("stubs")
when:
StubConfiguration stubConfig = GradleContractsDownloaderHelper.stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
// Have to use this internal property impl here. Is there some better way?
static <T> Property<T> prop(Class<T> aClass) {
return new DefaultPropertyState(aClass)
}
}

View File

@@ -1,257 +0,0 @@
package org.springframework.cloud.contract.verifier.plugin
import org.gradle.api.Project
import org.gradle.api.internal.provider.DefaultPropertyState
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.springframework.cloud.contract.stubrunner.AetherStubDownloader
import org.springframework.cloud.contract.stubrunner.ContractDownloader
import org.springframework.cloud.contract.stubrunner.StubConfiguration
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties
import spock.lang.Specification
/**
* @author Marcin Grzejszczak
* @author Anatoliy Balakirev
*/
class GradleContractsDownloaderSpec extends Specification {
Project project = Stub(Project)
Logger logger = Stub(Logger)
ObjectFactory objectFactory = Mock(ObjectFactory)
def setup() {
// Is there any better way to say that I need a new object on each interaction with mock?
objectFactory.property(String) >>> [prop(String), prop(String), prop(String), prop(String), prop(String), prop(String), prop(String), prop(String), prop(String)]
objectFactory.property(Integer) >> prop(Integer)
objectFactory.property(Boolean) >> prop(Boolean)
}
def "should parse dependency via string notation"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.stringNotation.set(stringNotation)
when:
StubConfiguration stubConfig = new GradleContractsDownloader(null, null).stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via direct setting"() {
given:
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.groupId.set("com.example")
dep.artifactId.set("foo")
dep.version.set("1.0.0")
dep.classifier.set("stubs")
when:
StubConfiguration stubConfig = new GradleContractsDownloader(null, null).stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via string notation with methods"() {
given:
String stringNotation = "com.example:foo:1.0.0:stubs"
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.stringNotation.set(stringNotation)
when:
StubConfiguration stubConfig = new GradleContractsDownloader(null, null).stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should parse dependency via direct setting with methods"() {
given:
ContractVerifierExtension.Dependency dep = new ContractVerifierExtension.Dependency(objectFactory)
dep.groupId.set("com.example")
dep.artifactId.set("foo")
dep.version.set("1.0.0")
dep.classifier.set("stubs")
when:
StubConfiguration stubConfig = new GradleContractsDownloader(null, null).stubConfiguration(dep)
then:
stubConfig.groupId == "com.example"
stubConfig.artifactId == "foo"
stubConfig.version == "1.0.0"
stubConfig.classifier == "stubs"
}
def "should pick dependency from cache for a non snapshot contract dependency with new property"() {
given:
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
contractDependency.groupId.set("com.example")
contractDependency.artifactId.set("foo")
contractDependency.version.set("1.0.0")
contractDependency.classifier.set("stubs")
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
contractRepository.repositoryUrl.set("foo")
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
GradleContractsDownloader gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
and:
StubConfiguration expectedStubConfig = new StubConfiguration("com.example:foo:1.0.0:stubs")
File expectedFileFromCache = new File("foo/bar")
GradleContractsDownloader.downloadedContract.put(expectedStubConfig, expectedFileFromCache)
when:
GradleContractsDownloader.DownloadedData downloaded = gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.REMOTE, true, [:], true)
then:
downloaded.downloadedContracts == expectedFileFromCache
}
def "should not pick dependency from cache for a non snapshot contract dependency with cache switch off"() {
given:
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
contractDependency.groupId.set("com.example")
contractDependency.artifactId.set("foo")
contractDependency.version.set("1.0.0")
contractDependency.classifier.set("stubs")
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
contractRepository.repositoryUrl.set("foo")
contractRepository.cacheDownloadedContracts.set(false)
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
GradleContractsDownloader gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
and:
StubConfiguration expectedStubConfig = new StubConfiguration("com.example:foo:1.0.0:stubs")
File expectedFileFromCache = new File("foo/bar")
GradleContractsDownloader.downloadedContract.put(expectedStubConfig, expectedFileFromCache)
and:
File expectedFileNotFromCache = new File("foo/bar/baz")
contractDownloader.unpackAndDownloadContracts() >> expectedFileNotFromCache
when:
GradleContractsDownloader.DownloadedData downloaded = gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.REMOTE, true, [:], true)
then:
downloaded.downloadedContracts == expectedFileNotFromCache
}
def "should not pick dependency from cache for snapshot contract dependency"() {
given:
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
contractDependency.groupId.set("com.example")
contractDependency.artifactId.set("foo")
contractDependency.version.set("1.0.0.BUILD-SNAPSHOT")
contractDependency.classifier.set("stubs")
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
contractRepository.repositoryUrl.set("foo")
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
File expectedFileNotFromCache = new File("foo/bar/baz")
contractDownloader.unpackAndDownloadContracts() >> expectedFileNotFromCache
and:
GradleContractsDownloader gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
when:
GradleContractsDownloader.DownloadedData downloaded = gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.REMOTE, true, [:], true)
then:
downloaded.downloadedContracts == expectedFileNotFromCache
}
private GradleContractsDownloader stubbedContractDownloader(downloader, contractDownloader) {
new GradleContractsDownloader(project, logger) {
@Override
protected AetherStubDownloader stubDownloader(ContractVerifierExtension.ContractRepository contractRepository,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
return downloader
}
@Override
protected ContractDownloader contractDownloader(StubConfiguration configuration,
ContractVerifierExtension.ContractRepository contractRepository,
String contractsPath, StubRunnerProperties.StubsMode contractsMode,
boolean deleteStubsAfterTest, Map<String, String> contractsProperties,
boolean failOnNoContracts) {
return contractDownloader
}
}
}
def "should not start downloading"() {
given:
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
and:
GradleContractsDownloader gradleDownloader = stubbedContractDownloader(downloader, contractDownloader)
and:
StubConfiguration expectedStubConfig = new StubConfiguration("com.example:foo:1.0.0:stubs")
GradleContractsDownloader.downloadedContract.put(expectedStubConfig, new File("foo/bar"))
when:
GradleContractsDownloader.DownloadedData downloaded = gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.CLASSPATH, true, [:], true)
then:
downloaded == null
}
def "should pass contract dependency properties as a parameter to the builder"() {
given:
ContractVerifierExtension.Dependency contractDependency = new ContractVerifierExtension.Dependency(objectFactory)
contractDependency.groupId.set("com.example")
contractDependency.artifactId.set("foo")
contractDependency.version.set("1.0.0.BUILD-SNAPSHOT")
contractDependency.classifier.set("stubs")
ContractVerifierExtension.ContractRepository contractRepository = new ContractVerifierExtension.ContractRepository(objectFactory)
contractRepository.repositoryUrl.set("foo")
contractRepository.username.set("foo1")
contractRepository.password.set("foo2")
contractRepository.proxyHost.set("foo3")
contractRepository.proxyPort.set(12)
and:
final AetherStubDownloader downloader = Mock(AetherStubDownloader)
final ContractDownloader contractDownloader = Mock(ContractDownloader)
File expectedFileNotFromCache = new File("foo/bar/baz")
contractDownloader.unpackAndDownloadContracts() >> expectedFileNotFromCache
and:
GradleContractsDownloader gradleDownloader = assertingContractDownloader(downloader, contractDownloader)
when:
gradleDownloader.downloadAndUnpackContractsIfRequired(contractDependency, contractRepository, null, StubRunnerProperties.StubsMode.CLASSPATH, true, [:], true)
then:
noExceptionThrown()
}
private GradleContractsDownloader assertingContractDownloader(downloader, contractDownloader) {
new GradleContractsDownloader(project, logger) {
@Override
protected AetherStubDownloader stubDownloader(ContractVerifierExtension.ContractRepository contractRepository,
StubRunnerProperties.StubsMode contractsMode, boolean deleteStubsAfterTest,
Map<String, String> contractsProperties, boolean failOnNoContracts) {
assert extension.contractRepository.username == "foo1"
assert extension.contractRepository.password == "foo2"
assert extension.contractRepository.proxyHost == "foo3"
assert extension.contractRepository.proxyPort == 12
return downloader
}
@Override
protected ContractDownloader contractDownloader(StubConfiguration configuration,
ContractVerifierExtension.ContractRepository contractRepository,
String contractsPath, StubRunnerProperties.StubsMode contractsMode,
boolean deleteStubsAfterTest, Map<String, String> contractsProperties,
boolean failOnNoContracts) {
return contractDownloader
}
}
}
// Have to use this internal property impl here. Is there some better way?
static <T> Property<T> prop(Class<T> aClass) {
return new DefaultPropertyState(aClass)
}
}