Gradle plugin - improve compatibility with Configuration Cache (#1825)

* fix javadoc

* avoid use of Gradle Project by using properties and injected build services

* tidy up buildRootPath

* fix long message to use projectGroupNameVersion
This commit is contained in:
aSemy
2022-11-29 15:52:38 +01:00
committed by GitHub
parent 063171b63f
commit 7b876aa6df
5 changed files with 121 additions and 45 deletions

View File

@@ -34,10 +34,12 @@ import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileSystemOperations;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.Input;
@@ -49,6 +51,7 @@ import org.gradle.api.tasks.OutputDirectory;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.TaskAction;
import org.gradle.process.ExecOperations;
import org.springframework.cloud.contract.stubrunner.ContractDownloader;
import org.springframework.cloud.contract.stubrunner.ScmStubDownloaderBuilder;
import org.springframework.cloud.contract.stubrunner.StubConfiguration;
@@ -100,7 +103,7 @@ class ContractsCopyTask extends DefaultTask {
private final Property<Boolean> excludeBuildFolders;
/**
* @see ContractVerifierExtension#deleteStubsAfterTest
* @see ContractVerifierExtension#getDeleteStubsAfterTest()
*
* This property will delete the temporary dependency or Git repository from which
* stubs were copied to this task's output directory.
@@ -114,8 +117,23 @@ class ContractsCopyTask extends DefaultTask {
private final DirectoryProperty backupContractsFolder;
private final Property<String> projectGroup;
private final Property<String> projectName;
private final Property<String> projectVersion;
private final ExecOperations executors;
private final FileSystemOperations files;
@Inject
public ContractsCopyTask(ObjectFactory objects) {
public ContractsCopyTask(
final ObjectFactory objects,
final ProviderFactory providers,
final ExecOperations executors,
final FileSystemOperations files
) {
this.executors = executors;
this.files = files;
convertToYaml = objects.property(Boolean.class);
failOnNoContracts = objects.property(Boolean.class);
contractsDirectory = objects.directoryProperty();
@@ -140,6 +158,10 @@ class ContractsCopyTask extends DefaultTask {
copiedContractsFolder = objects.directoryProperty();
backupContractsFolder = objects.directoryProperty();
projectGroup = objects.property(String.class).convention(providers.provider(() -> getProject().getGroup().toString()));
projectName = objects.property(String.class).convention(providers.provider(() -> getProject().getName()));
projectVersion = objects.property(String.class).convention(providers.provider(() -> getProject().getVersion().toString()));
this.getOutputs().upToDateWhen(task -> !(this.shouldDownloadContracts()
&& this.getContractDependency().toStubConfiguration().isVersionChanging()));
}
@@ -158,14 +180,13 @@ class ContractsCopyTask extends DefaultTask {
contractsDirectory = this.contractsDirectory.getAsFile().getOrNull();
antPattern = "**/";
}
getLogger().info("For project [{}] will use contracts provided in the folder [{}]", getProject().getName(),
getLogger().info("For project [{}] will use contracts provided in the folder [{}]", projectName.get(),
contractsDirectory);
final String contractsRepository = this.contractRepository.getRepositoryUrl().getOrElse("");
throwExceptionWhenFailOnNoContracts(contractsDirectory, contractsRepository);
final String slashSeparatedGroupId = getProject().getGroup().toString().replace(".", File.separator);
final String dotSeparatedAntPattern = antPattern.replace(slashSeparatedGroupId,
getProject().getGroup().toString());
final String slashSeparatedGroupId = projectGroup.get().replace(".", File.separator);
final String dotSeparatedAntPattern = antPattern.replace(slashSeparatedGroupId, projectGroup.get());
File output = copiedContractsFolder.get().getAsFile();
getLogger().info(
"Downloading and unpacking files from [{}] to [{}]. The inclusion ant patterns are [{}] and [{}]",
@@ -187,8 +208,8 @@ class ContractsCopyTask extends DefaultTask {
os = NullOutputStream.INSTANCE;
}
try {
getProject().javaexec(exec -> {
exec.setMain("org.springframework.cloud.contract.verifier.converter.ToYamlConverterApplication");
executors.javaexec(exec -> {
exec.getMainClass().set("org.springframework.cloud.contract.verifier.converter.ToYamlConverterApplication");
exec.classpath(classpath);
exec.args(quoteAndEscape(outputContractsFolder.getAbsolutePath()));
exec.setStandardOutput(os);
@@ -206,7 +227,7 @@ class ContractsCopyTask extends DefaultTask {
private void sync(File file, String antPattern, String dotSeparatedAntPattern, boolean excludeBuildFolders,
File outputContractsFolder) {
getProject().sync(spec -> {
files.sync(spec -> {
spec.from(file);
// by default group id is slash separated...
spec.include(antPattern);
@@ -220,8 +241,8 @@ class ContractsCopyTask extends DefaultTask {
}
private DownloadedData downloadContracts() {
String groupId = getProject().getGroup().toString();
String artifactId = getProject().getName();
String groupId = projectGroup.get();
String artifactId = projectName.get();
getLogger().info("Project has group id [{}], artifact id [{}]", groupId, artifactId);
getLogger().info("For project [{}] Download dependency is provided - will download contract jars", artifactId);
getLogger().info("Contract dependency [{}]", contractDependency);
@@ -231,7 +252,7 @@ class ContractsCopyTask extends DefaultTask {
final StubDownloader downloader = new StubDownloaderBuilderProvider().get(createStubRunnerOptions());
final ContractDownloader contractDownloader = new ContractDownloader(downloader, configuration,
contractsPath.getOrNull(), groupId, artifactId, getProject().getVersion().toString());
contractsPath.getOrNull(), groupId, artifactId, projectVersion.get());
final File downloadedContracts = contractDownloader.unpackAndDownloadContracts();
final ContractDownloader.InclusionProperties inclusionProperties = contractDownloader
.createNewInclusionProperties(downloadedContracts);
@@ -280,6 +301,10 @@ class ContractsCopyTask extends DefaultTask {
}
@Input protected Property<String> getProjectGroup() { return projectGroup; }
@Input protected Property<String> getProjectName() { return projectName; }
@Input protected Property<String> getProjectVersion() { return projectVersion; }
@Input
Property<Boolean> getConvertToYaml() {
return convertToYaml;

View File

@@ -40,6 +40,7 @@ import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
import org.gradle.process.ExecOperations;
import org.springframework.util.StringUtils;
//TODO: Implement as an incremental task: https://gradle.org/docs/current/userguide/custom_tasks.html#incremental_tasks ?
@@ -67,8 +68,15 @@ class GenerateClientStubsFromDslTask extends DefaultTask {
private final DirectoryProperty stubsOutputDir;
final ExecOperations executors;
@Inject
public GenerateClientStubsFromDslTask(ObjectFactory objects) {
public GenerateClientStubsFromDslTask(
final ObjectFactory objects,
final ExecOperations executors
) {
this.executors = executors;
contractsDslDir = objects.directoryProperty();
excludedFiles = objects.listProperty(String.class);
excludeBuildFolders = objects.property(Boolean.class);
@@ -90,7 +98,7 @@ class GenerateClientStubsFromDslTask extends DefaultTask {
os = NullOutputStream.INSTANCE;
}
try {
getProject().javaexec(exec -> {
executors.javaexec(exec -> {
exec.getMainClass().set("org.springframework.cloud.contract.verifier.converter.RecursiveFilesConverterApplication");
exec.classpath(classpath);
exec.args(quoteAndEscape(output.getAbsolutePath()), quoteAndEscape(contractsDslDir.get().getAsFile().getAbsolutePath()),

View File

@@ -42,6 +42,7 @@ import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.api.tasks.SkipWhenEmpty;
import org.gradle.api.tasks.TaskAction;
import org.gradle.process.ExecOperations;
import org.springframework.cloud.contract.verifier.config.ContractVerifierConfigProperties;
import org.springframework.cloud.contract.verifier.config.TestFramework;
import org.springframework.cloud.contract.verifier.config.TestMode;
@@ -97,8 +98,15 @@ class GenerateServerTestsTask extends DefaultTask {
private final DirectoryProperty generatedTestResourcesDir;
final ExecOperations executors;
@Inject
public GenerateServerTestsTask(ObjectFactory objects) {
public GenerateServerTestsTask(
final ObjectFactory objects,
final ExecOperations executors
) {
this.executors = executors;
this.contractsDslDir = objects.directoryProperty();
this.nameSuffixForTests = objects.property(String.class);
this.basePackageForTests = objects.property(String.class);
@@ -139,8 +147,8 @@ class GenerateServerTestsTask extends DefaultTask {
}
try {
String propertiesJson = new ObjectMapper().writeValueAsString(properties);
getProject().javaexec(exec -> {
exec.setMain("org.springframework.cloud.contract.verifier.TestGeneratorApplication");
executors.javaexec(exec -> {
exec.getMainClass().set("org.springframework.cloud.contract.verifier.TestGeneratorApplication");
exec.classpath(classpath);
exec.args(quoteAndEscape(propertiesJson));
exec.setStandardOutput(os);

View File

@@ -23,6 +23,7 @@ import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.MapProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputDirectory;
import org.gradle.api.tasks.Nested;
@@ -40,7 +41,7 @@ import org.springframework.util.StringUtils;
/**
* For SCM based repositories will copy the generated stubs to the cloned repo with
* contracts and stubs. Will also commit the changes and push them to origin.
*
* <p>
* NOTE: starting with 2.3.0.RELEASE the <code>customize{}</code> closure previously used
* for {@link PublishStubsToScmTask} customisation is no longer available. The settings
* should be applied directly within the <code>publishStubsToScm</code> closure as in the
@@ -60,7 +61,7 @@ class PublishStubsToScmTask extends DefaultTask {
private final Property<StubRunnerProperties.StubsMode> contractsMode;
/**
* @see ContractVerifierExtension#deleteStubsAfterTest
* @see ContractVerifierExtension#getDeleteStubsAfterTest()
*
* This property will delete the Git repository where the input stubs to this task
* have been committed.
@@ -73,8 +74,15 @@ class PublishStubsToScmTask extends DefaultTask {
private final DirectoryProperty stubsDir;
private final Property<String> projectGroup;
private final Property<String> projectName;
private final Property<String> projectVersion;
@Inject
public PublishStubsToScmTask(ObjectFactory objects) {
public PublishStubsToScmTask(
final ObjectFactory objects,
final ProviderFactory providers
) {
this.contractRepository = objects.newInstance(Repository.class);
this.contractsMode = objects.property(StubRunnerProperties.StubsMode.class);
this.deleteStubsAfterTest = objects.property(Boolean.class);
@@ -82,6 +90,10 @@ class PublishStubsToScmTask extends DefaultTask {
this.contractsProperties = objects.mapProperty(String.class, String.class);
this.stubsDir = objects.directoryProperty();
projectGroup = objects.property(String.class).convention(providers.provider(() -> getProject().getGroup().toString()));
projectName = objects.property(String.class).convention(providers.provider(() -> getProject().getName()));
projectVersion = objects.property(String.class).convention(providers.provider(() -> getProject().getVersion().toString()));
this.onlyIf(task -> {
String contractRepoUrl = contractRepository.repositoryUrl.getOrElse("");
if (!StringUtils.hasText(contractRepoUrl) || !ScmStubDownloaderBuilder.isProtocolAccepted(contractRepoUrl)) {
@@ -96,11 +108,10 @@ class PublishStubsToScmTask extends DefaultTask {
@TaskAction
void publishStubsToScm() {
String projectName = getProject().getGroup().toString() + ":" + getProject().getName() + ":"
+ getProject().getVersion().toString();
getLogger().info("Pushing Stubs to SCM for project [{}]", projectName);
String projectGroupNameVersion = projectGroup.get() + ":" + projectName.get() + ":" + projectVersion.get();
getLogger().info("Pushing Stubs to SCM for project [{}]", projectGroupNameVersion);
StubRunnerOptions stubRunnerOptions = createStubRunnerOptions();
new ContractProjectUpdater(stubRunnerOptions).updateContractProject(projectName,
new ContractProjectUpdater(stubRunnerOptions).updateContractProject(projectGroupNameVersion,
stubsDir.get().getAsFile().toPath());
}
@@ -202,4 +213,7 @@ class PublishStubsToScmTask extends DefaultTask {
return options.build();
}
@Input protected Property<String> getProjectGroup() { return projectGroup; }
@Input protected Property<String> getProjectName() { return projectName; }
@Input protected Property<String> getProjectVersion() { return projectVersion; }
}

View File

@@ -24,19 +24,18 @@ import org.gradle.api.attributes.Bundling;
import org.gradle.api.attributes.Category;
import org.gradle.api.attributes.LibraryElements;
import org.gradle.api.attributes.Usage;
import org.gradle.api.file.Directory;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.file.FileCollection;
import org.gradle.api.file.*;
import org.gradle.api.internal.HasConvention;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.plugins.GroovyPlugin;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.Provider;
import org.gradle.api.publish.PublishingExtension;
import org.gradle.api.publish.maven.MavenPublication;
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin;
import org.gradle.api.provider.ProviderFactory;
import org.gradle.api.tasks.GroovySourceSet;
import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.SourceSetContainer;
@@ -46,6 +45,7 @@ import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.testing.Test;
import org.springframework.cloud.contract.verifier.config.TestFramework;
import javax.inject.Inject;
import java.io.File;
/**
@@ -63,6 +63,8 @@ import java.io.File;
*/
public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project> {
private static final Logger logger = Logging.getLogger(SpringCloudContractVerifierGradlePlugin.class);
private static final String SPRING_CLOUD_VERSION = VersionExtractor.forClass(SpringCloudContractVerifierGradlePlugin.class);
private static final String GROUP_NAME = "Verification";
@@ -87,6 +89,21 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
private Project project;
private final ProjectLayout layout;
private final ProviderFactory providers;
private final ObjectFactory objects;
@Inject
public SpringCloudContractVerifierGradlePlugin(
final ProjectLayout layout,
final ProviderFactory providers,
final ObjectFactory objects
) {
this.layout = layout;
this.providers = providers;
this.objects = objects;
}
@Override
public void apply(Project project) {
this.project = project;
@@ -169,10 +186,10 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
conf.setCanBeResolved(true);
conf.setCanBeConsumed(false);
conf.attributes(attributes -> {
attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME));
attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY));
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR));
attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL));
attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.JAVA_RUNTIME));
attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.class, Category.LIBRARY));
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.class, LibraryElements.JAR));
attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.class, Bundling.EXTERNAL));
});
conf.extendsFrom(configurations.getByName(CONTRACT_TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME));
});
@@ -316,7 +333,7 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
verifierStubsJar.configure(stubsJar -> {
stubsJar.setDescription("Creates the stubs JAR task");
stubsJar.setGroup(GROUP_NAME);
stubsJar.getArchiveBaseName().convention(project.provider(project::getName));
stubsJar.getArchiveBaseName().convention(providers.provider(project::getName));
stubsJar.getArchiveClassifier().convention(extension.getStubsSuffix());
stubsJar.from(extension.getStubsOutputDir());
@@ -335,15 +352,15 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
contractsCopyTask.getFailOnNoContracts().convention(extension.getFailOnNoContracts());
contractsCopyTask.getContractsDirectory()
.convention(extension.getContractsDslDir().flatMap(contractsDslDir -> {
return project.provider(() -> {
return providers.provider(() -> {
if (contractsDslDir.getAsFile().exists()) {
return contractsDslDir;
}
else {
Directory legacyContractsDslDir = project.getLayout().getProjectDirectory()
Directory legacyContractsDslDir = layout.getProjectDirectory()
.dir("src/test/resources/contracts");
if (legacyContractsDslDir.getAsFile().exists()) {
project.getLogger().warn(
logger.warn(
"Spring Cloud Contract Verifier Plugin: Locating contracts in <src/test/resources/contracts> has been removed. Please move them to <src/contractTest/resources/contracts>. This warning message will be removed in a future release.");
return contractsDslDir;
}
@@ -390,13 +407,17 @@ public class SpringCloudContractVerifierGradlePlugin implements Plugin<Project>
}
private Provider<String> buildRootPath(String path) {
return project.provider(() -> {
StringBuilder builder = new StringBuilder();
builder.append("META-INF").append(File.separator).append(project.getGroup()).append(File.separator)
.append(project.getName()).append(File.separator).append(project.getVersion())
.append(File.separator).append(path);
return builder.toString();
});
return providers.provider(() ->
"META-INF"
+ File.separator
+ project.getGroup()
+ File.separator
+ project.getName()
+ File.separator
+ project.getVersion()
+ File.separator
+ path
);
}
}