From 9c4ef7de23fe95e74bdae9bd1a49769cb47642b9 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Tue, 16 Jul 2024 18:14:08 -0400 Subject: [PATCH 1/4] Introduce ad-hoc versioned generated Gradle JARs generation Signed-off-by: Daniel Lacasse --- ...entGeneratedGradleJarsFunctionalTests.java | 100 +++ ...ePluginDevelopmentRepositoryExtension.java | 15 + .../GradleDistributionRepositories.java | 744 ++++++++++++++++++ .../GradlePluginDevelopmentBasePlugin.java | 1 + .../GradlePluginDevelopmentPlugin.java | 2 + .../ProjectDependenciesExtensionRule.java | 1 + .../rules/RepositoriesExtensionRules.java | 47 +- .../internal/util/DoNothingAction.java | 16 + ...MinimumDependencyResolutionManagement.java | 78 ++ .../internal/util/OnceAction.java | 24 + .../kotlin/RepositoryHandlerExtensions.kt | 9 + ...radle-distribution-repositories.properties | 1 + 12 files changed, 1022 insertions(+), 16 deletions(-) create mode 100644 subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentGeneratedGradleJarsFunctionalTests.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/DoNothingAction.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/MinimumDependencyResolutionManagement.java create mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/OnceAction.java create mode 100644 subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-distribution-repositories.properties diff --git a/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentGeneratedGradleJarsFunctionalTests.java b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentGeneratedGradleJarsFunctionalTests.java new file mode 100644 index 00000000..e56ad3fc --- /dev/null +++ b/subprojects/gradle-plugin-development/src/functionalTest/groovy/dev/gradleplugins/GradlePluginDevelopmentGeneratedGradleJarsFunctionalTests.java @@ -0,0 +1,100 @@ +package dev.gradleplugins; + +import dev.gradleplugins.buildscript.io.GradleBuildFile; +import dev.gradleplugins.buildscript.io.GradleSettingsFile; +import dev.gradleplugins.runnerkit.GradleExecutor; +import dev.gradleplugins.runnerkit.GradleRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; + +import static dev.gradleplugins.buildscript.syntax.Syntax.groovyDsl; + + +class GradlePluginDevelopmentGeneratedGradleJarsFunctionalTests { + @TempDir(cleanup = CleanupMode.ON_SUCCESS) Path testDirectory; + GradleRunner runner = GradleRunner.create(GradleExecutor.gradleTestKit()).withGradleVersion(System.getProperty("dev.gradleplugins.defaultGradleVersion")).withPluginClasspath().inDirectory(() -> testDirectory); + GradleBuildFile buildFile; + GradleSettingsFile settingsFile; + + @BeforeEach + void setup() { + settingsFile = GradleSettingsFile.inDirectory(testDirectory); + buildFile = GradleBuildFile.inDirectory(testDirectory); + buildFile.plugins(it -> { + it.id("dev.gradleplugins.gradle-plugin-base"); + it.id("java-library"); + }); + + buildFile.append(groovyDsl( + "Set allDependencies(Configuration configuration) {", + " return configuration.incoming.resolutionResult.allDependencies.collect { result ->", + " def it = result.requested", + " if (it instanceof ModuleComponentSelector) {", + " return \"${it.group}:${it.module}:${it.version}\".toString()", + " } else if (it instanceof ProjectComponentSelector) {", + " return it.projectPath", + " } else {", + " throw new RuntimeException()", + " }", + " }", + "}" + )); + } + + @Nested + class GradleApiDependencyTest { + @Test + void test() { + buildFile.append(groovyDsl( + "repositories {", + " gradleDistributions()", + " mavenCentral()", + "}", + "dependencies {", + " implementation gradleApi('8.9-rc-1')", + "}", + "tasks.register('verify') {", + " doLast {", + " assert allDependencies(configurations.compileClasspath)", + " .containsAll(['dev.gradleplugins.generated:gradle-api:8.9-rc-1', 'org.codehaus.groovy:groovy:3.0.21'])", + " assert allDependencies(configurations.runtimeClasspath)", + " .containsAll(['dev.gradleplugins.generated:gradle-api:8.9-rc-1', 'org.codehaus.groovy:groovy-all:3.0.21', 'org.jetbrains.kotlin:kotlin-stdlib:1.9.23'])", + " }", + "}" + )); + + runner.withArgument("verify").build(); + } + } + + @Nested + class GradleTestKitDependencyTest { + @Test + void test() { + buildFile.append(groovyDsl( + "repositories {", + " gradleDistributions()", + " mavenCentral()", + "}", + "dependencies {", + " implementation gradleTestKit('8.9-rc-1')", + "}", + "tasks.register('verify') {", + " doLast {", + " assert allDependencies(configurations.compileClasspath)", + " .containsAll(['dev.gradleplugins.generated:gradle-test-kit:8.9-rc-1', 'org.codehaus.groovy:groovy:3.0.21'])", + " assert allDependencies(configurations.runtimeClasspath)", + " .containsAll(['dev.gradleplugins.generated:gradle-test-kit:8.9-rc-1', 'org.codehaus.groovy:groovy-all:3.0.21', 'org.jetbrains.kotlin:kotlin-stdlib:1.9.23'])", + " }", + "}" + )); + + runner.withArgument("verify").build(); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentRepositoryExtension.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentRepositoryExtension.java index 8134fb94..0527c53e 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentRepositoryExtension.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/GradlePluginDevelopmentRepositoryExtension.java @@ -2,6 +2,7 @@ import org.gradle.api.Action; import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.artifacts.repositories.ArtifactRepository; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.plugins.ExtensionAware; @@ -28,6 +29,20 @@ public interface GradlePluginDevelopmentRepositoryExtension { */ MavenArtifactRepository gradlePluginDevelopment(Action action); + /** + * Adds a Gradle Distributions repository containing the Gradle API/TestKit. + * + * @return the Gradle Distributions repository instance added to the repository handler. + */ + ArtifactRepository gradleDistributions(); + + /** + * Adds a Gradle Distributions Snapshots repository containing the Gradle API/TestKit. + * + * @return the Gradle Distributions Snapshots repository instance added to the repository handler. + */ + ArtifactRepository gradleDistributionsSnapshots(); + /** * Returns {@link RepositoryHandler} extension methods. * diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java new file mode 100644 index 00000000..edc319a2 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java @@ -0,0 +1,744 @@ +package dev.gradleplugins.internal; + +import dev.gradleplugins.internal.util.MinimumDependencyResolutionManagement; +import org.gradle.api.Action; +import org.gradle.api.ActionConfiguration; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ComponentMetadataContext; +import org.gradle.api.artifacts.ComponentMetadataDetails; +import org.gradle.api.artifacts.ComponentMetadataListerDetails; +import org.gradle.api.artifacts.ComponentMetadataRule; +import org.gradle.api.artifacts.ComponentMetadataVersionLister; +import org.gradle.api.artifacts.DirectDependenciesMetadata; +import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.artifacts.repositories.ArtifactRepository; +import org.gradle.api.artifacts.repositories.IvyArtifactRepository; +import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +import org.gradle.api.artifacts.repositories.MetadataSupplierAware; +import org.gradle.api.artifacts.repositories.RepositoryContentDescriptor; +import org.gradle.api.artifacts.transform.CacheableTransform; +import org.gradle.api.artifacts.transform.InputArtifact; +import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformOutputs; +import org.gradle.api.artifacts.transform.TransformParameters; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.LibraryElements; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.file.ProjectLayout; +import org.gradle.api.initialization.Settings; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.PluginAware; +import org.gradle.api.provider.Provider; +import org.gradle.api.specs.Spec; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.tooling.BuildLauncher; +import org.gradle.tooling.GradleConnector; +import org.gradle.tooling.ProjectConnection; +import org.gradle.util.GradleVersion; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static dev.gradleplugins.internal.util.MinimumDependencyResolutionManagement.dependencyResolutionManagement; +import static dev.gradleplugins.internal.util.MinimumDependencyResolutionManagement.dependencyResolutionManagement; +import static dev.gradleplugins.internal.util.OnceAction.once; + +public final class GradleDistributionRepositories { + private static final Spec GRADLE_DISTRIBUTIONS_REPOSITORY_SPEC = repo -> repo.getName().startsWith("Gradle Distributions"); + private static final String GENERATED_JAR_ARTIFACT_TYPE = "generated-jar"; + + private GradleDistributionRepositories() {} + + public static final class FileName implements Callable { + private final String baseName; + private final String version; + private final String classifier; + private final String extension; + + private FileName(String baseName, String version, @Nullable String classifier, String extension) { + this.baseName = baseName; + this.version = version; + this.classifier = classifier; + this.extension = extension; + } + + public String getExtension() { + return extension; + } + + public String getBaseName() { + return baseName; + } + + public String getClassifier() { + return classifier; + } + + public String getVersion() { + return version; + } + + public FileName withExtension(String ext) { + return new FileName(baseName, version, classifier, ext); + } + + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(baseName); + builder.append("-").append(version); + if (classifier != null) { + builder.append("-").append(classifier); + } + builder.append(".").append(extension); + return builder.toString(); + } + + private static final Pattern FILE_NAME_PATTERN = Pattern.compile("^(.*?)-(\\d.*\\d)(?:-([^-]*))?\\.(.+)$"); + public static FileName parse(String name) { + Matcher matcher = FILE_NAME_PATTERN.matcher(name); + + if (matcher.matches()) { + String baseName = matcher.group(1); + String version = matcher.group(2); + String classifier = null; + String extension = null; + if (matcher.groupCount() == 5) { + classifier = matcher.group(4); + extension = matcher.group(5); + } else { + extension = matcher.group(4); + } + return new FileName(baseName, version, classifier, extension); + } else { + throw new RuntimeException("Invalid file name"); + } + } + + @Override + public String call() throws Exception { + return toString(); + } + } + + private static final class Filenames { + public Filename file(ModuleVersionIdentifier id) { + return new Filename() { + private final FileName name = new FileName(id.getName(), id.getVersion(), null, null); + + @Override + public String generatedJar() { + return name.withExtension(GENERATED_JAR_ARTIFACT_TYPE).toString(); + } + + @Override + public String module() { + return name.withExtension("module").toString(); + } + + @Override + public String jar() { + return name.withExtension("jar").toString(); + } + }; + } + + public Filename file(ModuleVersionIdentifier id, String classifier) { + return new Filename() { + private final FileName name = new FileName(id.getName(), id.getVersion(), classifier, null); + + @Override + public String generatedJar() { + return name.withExtension(GENERATED_JAR_ARTIFACT_TYPE).toString(); + } + + @Override + public String module() { + return name.withExtension("module").toString(); + } + + @Override + public String jar() { + return name.withExtension("jar").toString(); + } + }; + } + + public interface Filename { + String generatedJar(); + String module(); + String jar(); + } + } + + private static final class ModuleGroupIdentifier { + private final String value; + + private ModuleGroupIdentifier(String value) { + this.value = value; + } + + public static ModuleGroupIdentifier of(String value) { + return new ModuleGroupIdentifier(value); + } + + public String asMavenPath() { + return value.replace('.', '/'); + } + + @Override + public String toString() { + return value; + } + } + + public static void writeModuleFile(File moduleFile, Filenames names, ModuleGroupIdentifier group, ModuleVersionIdentifier id) { + moduleFile.getParentFile().mkdirs(); + try (PrintStream out = new PrintStream(moduleFile)) { + out.println("{"); + out.println(" \"formatVersion\": \"1.1\","); + out.println(" \"component\": {"); + out.println(" \"group\": \"" + group + "\","); + out.println(" \"module\": \"" + id.getName() + "\","); + out.println(" \"version\": \"" + id.getVersion() + "\","); + out.println(" \"attributes\": {"); + out.println(" \"org.gradle.status\": \"release\""); + out.println(" }"); + out.println(" },"); + out.println(" \"createdBy\": {"); + out.println(" \"gradle\": {"); + out.println(" \"version\": \"6.8.1\""); + out.println(" }"); + out.println(" },"); + out.println(" \"variants\": ["); + out.println(" {"); + out.println(" \"name\": \"apiElements\","); + out.println(" \"attributes\": {"); + out.println(" \"org.gradle.category\": \"library\","); + out.println(" \"org.gradle.dependency.bundling\": \"external\","); + out.println(" \"org.gradle.jvm.version\": 8,"); + out.println(" \"org.gradle.libraryelements\": \"jar\","); + out.println(" \"org.gradle.usage\": \"java-api\""); + out.println(" },"); + out.println(" \"dependencies\": ["); + out.println(" {"); + out.println(" \"group\": \"org.codehaus.groovy\","); + out.println(" \"module\": \"groovy\","); + out.println(" \"version\": {"); + out.println(" \"requires\": \"3.0.21\""); + out.println(" }"); + out.println(" }"); + out.println(" ],"); + out.println(" \"files\": ["); + out.println(" {"); + out.println(" \"name\": \"" + names.file(id).generatedJar() + "\","); + out.println(" \"url\": \"" + names.file(id).generatedJar() + "\""); + out.println(" }"); + out.println(" ]"); + out.println(" },"); + out.println(" {"); + out.println(" \"name\": \"runtimeElements\","); + out.println(" \"attributes\": {"); + out.println(" \"org.gradle.category\": \"library\","); + out.println(" \"org.gradle.dependency.bundling\": \"external\","); + out.println(" \"org.gradle.jvm.version\": 8,"); + out.println(" \"org.gradle.libraryelements\": \"jar\","); + out.println(" \"org.gradle.usage\": \"java-runtime\""); + out.println(" },"); + out.println(" \"dependencies\": ["); + out.println(" {"); + out.println(" \"group\": \"org.codehaus.groovy\","); + out.println(" \"module\": \"groovy-all\","); + out.println(" \"version\": {"); + out.println(" \"requires\": \"3.0.21\""); + out.println(" },"); + out.println(" \"thirdPartyCompatibility\": {"); + out.println(" \"artifactSelector\": {"); + out.println(" \"name\": \"groovy-all\","); + out.println(" \"type\": \"pom\","); + out.println(" \"extension\": \"pom\""); + out.println(" }"); + out.println(" }"); + out.println(" },"); + out.println(" {"); + out.println(" \"group\": \"org.jetbrains.kotlin\","); + out.println(" \"module\": \"kotlin-stdlib\","); + out.println(" \"version\": {"); + out.println(" \"requires\": \"1.9.23\""); + out.println(" }"); + out.println(" }"); + out.println(" ],"); + out.println(" \"files\": ["); + out.println(" {"); + out.println(" \"name\": \"" + names.file(id).generatedJar() + "\","); + out.println(" \"url\": \"" + names.file(id).generatedJar() + "\""); + out.println(" }"); + out.println(" ]"); + out.println(" },"); + out.println(" {"); + out.println(" \"name\": \"sourcesElements\","); + out.println(" \"attributes\": {"); + out.println(" \"org.gradle.category\": \"documentation\","); + out.println(" \"org.gradle.dependency.bundling\": \"external\","); + out.println(" \"org.gradle.docstype\": \"sources\","); + out.println(" \"org.gradle.jvm.version\": 8,"); + out.println(" \"org.gradle.usage\": \"java-runtime\""); + out.println(" },"); + out.println(" \"files\": ["); + out.println(" {"); + out.println(" \"name\": \"" + names.file(id, "sources").generatedJar() + "\","); + out.println(" \"url\": \"" + names.file(id, "sources").generatedJar() + "\""); + out.println(" }"); + out.println(" ]"); + out.println(" },"); + out.println(" {"); + out.println(" \"name\": \"javadocElements\","); + out.println(" \"attributes\": {"); + out.println(" \"org.gradle.category\": \"documentation\","); + out.println(" \"org.gradle.dependency.bundling\": \"external\","); + out.println(" \"org.gradle.docstype\": \"javadoc\","); + out.println(" \"org.gradle.jvm.version\": 8,"); + out.println(" \"org.gradle.usage\": \"java-runtime\""); + out.println(" },"); + out.println(" \"files\": ["); + out.println(" {"); + out.println(" \"name\": \"" + names.file(id, "javadoc").generatedJar() + "\","); + out.println(" \"url\": \"" + names.file(id, "javadoc").generatedJar() + "\""); + out.println(" }"); + out.println(" ]"); + out.println(" }"); + out.println(" ]"); + out.println("}"); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + /*private*/ static abstract /*final*/ class ProjectRule implements Plugin { + @Inject + public ProjectRule() {} + + @Override + public void apply(Project project) { + Attribute att = Attribute.of("my-att", String.class); + project.getDependencies().attributesSchema(it -> it.attribute(att)); + + project.getDependencies().getArtifactTypes().create(GENERATED_JAR_ARTIFACT_TYPE).getAttributes().attribute(att, "foo"); + + project.afterEvaluate(__ -> { + project.getConfigurations().all(config -> { + if (config.isCanBeResolved()) { + config.attributes(attributes -> { + attributes.attribute(att, "bar"); + }); + } + }); + }); + + project.getDependencies().getAttributesSchema().attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE); + project.getDependencies().registerTransform(GeneratedGradleJarTransform.class, spec -> { + spec.getFrom() + .attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR)) + .attribute(att, "foo") + ; + spec.getTo() + .attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR)) + .attribute(att, "bar") + ; + spec.parameters(parameters -> { + parameters.getGradleUserHomeDirectory().set(project.getGradle().getGradleHomeDir()); + }); + }); + } + + @CacheableTransform + /*private*/ static abstract /*final*/ class GeneratedGradleJarTransform implements TransformAction { + public interface Parameters extends TransformParameters { + @Internal + DirectoryProperty getGradleUserHomeDirectory(); + } + + @PathSensitive(PathSensitivity.NAME_ONLY) + @InputArtifact + public abstract Provider getInputArtifact(); + + @Inject + public GeneratedGradleJarTransform() {} + + @Override + public void transform(TransformOutputs outputs) { + withWorkingDirectory(workingDirectory -> { + forGradleJar(FileName.parse(getInputArtifact().map(FileSystemLocation::getAsFile).get().getName()), gradleJar -> { + if (gradleJar.getName().getClassifier() == null) { + try { + Files.write(workingDirectory.resolve("build.gradle"), Arrays.asList( + "def configuration = configurations.create('generator')", + "dependencies {", + " generator " + gradleJar.asGroovyDsl(), + "}", + "tasks.create('generate') {", + " doLast {", + " configuration.resolve()", + " }", + "}" + )); + Files.createFile(workingDirectory.resolve("settings.gradle")); + + final GradleConnector connector = GradleConnector.newConnector(); + connector.useGradleVersion(gradleJar.getGradleVersion()); + connector.useGradleUserHomeDir(getParameters().getGradleUserHomeDirectory().getAsFile().get()); + connector.forProjectDirectory(workingDirectory.toFile()); + + withOutput(workingDirectory.resolve("output.txt"), outStream -> { + try (ProjectConnection connection = connector.connect()) { + final BuildLauncher launcher = connection.newBuild().forTasks("generate"); + launcher.setStandardOutput(outStream); + launcher.setStandardError(outStream); + launcher.run(); + } + }); + + File jarFile = outputs.file(gradleJar.getName()); + File generatedJarFile = getParameters().getGradleUserHomeDirectory().file("caches/" + gradleJar.getGradleVersion() + "/generated-gradle-jars/" + gradleJar.getName()).map(FileSystemLocation::getAsFile).get(); + Files.copy(generatedJarFile.toPath(), jarFile.toPath()); + } catch ( + IOException e) { + throw new RuntimeException(e); + } + } else if (gradleJar.getName().getClassifier().equals("javadoc")) { + throw new UnsupportedOperationException("Javadoc generation not supported"); + } else if (gradleJar.getName().getClassifier().equals("sources")) { + throw new UnsupportedOperationException("Sources generation not supported"); + } + }); + + }); + } + + private static void withOutput(Path outputFile, Consumer action) { + try (OutputStream outStream = Files.newOutputStream(outputFile)) { + action.accept(outStream); + } catch (Throwable ex) { + throw new RuntimeException("See " + outputFile, ex); + } + } + + private static void withWorkingDirectory(Consumer action) { + try { + Path workingDirectory = Files.createTempDirectory("generated-gradle-jars"); + action.accept(workingDirectory); + // TODO: Clear directory + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void forGradleJar(FileName artifactName, Consumer action) { + assert artifactName.getExtension().equals(GENERATED_JAR_ARTIFACT_TYPE); + + action.accept(new GeneratedGradleJar() { + @Override + public String getGradleVersion() { + return artifactName.getVersion(); + } + + private String getBaseName() { + return artifactName.getBaseName(); + } + + @Override + public String asGroovyDsl() { + if (artifactName.getBaseName().equals("gradle-api")) { + return "gradleApi()"; + } else if (artifactName.getBaseName().equals("gradle-test-kit")) { + return "gradleTestKit()"; + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public FileName getName() { + return artifactName.withExtension("jar"); + } + }); + } + + private interface GeneratedGradleJar { + String getGradleVersion(); + String asGroovyDsl(); + + FileName getName(); + } + } + } + + /*private*/ static abstract /*final*/ class Rule implements Plugin { + private final ObjectFactory objects; + private final ProjectLayout layout; + + @Inject + public Rule(ObjectFactory objects, ProjectLayout layout) { + this.objects = objects; + this.layout = layout; + } + + @Override + public void apply(PluginAware target) { + if (target instanceof Settings) { + applyTo((Settings) target); + } else if (target instanceof Project) { + applyTo((Project) target); + } else { + throw new UnsupportedOperationException(); + } + } + + private void applyTo(Settings settings) { + dependencyResolutionManagement(settings, it -> { + // TODO: Add support for dynamic version + settings.getExtensions().add(Factory.class, "$factory", new BaseFactory(it.getRepositories())); + }); + dependencyResolutionManagement(settings, configureDependencyResolution(a -> settings.getGradle().allprojects(a))); + } + + private void applyTo(Project project) { + project.getExtensions().add(Factory.class, "$factory", new DynamicVersionSupportFactory(new GradleDistributionVersions(project.getResources().getText().fromUri("https://services.gradle.org/versions/all").asFile()), new BaseFactory(project.getRepositories()))); + dependencyResolutionManagement(project, configureDependencyResolution(a -> a.execute(project))); + } + + private Action configureDependencyResolution(Consumer> action) { + return dependencyResolutionManagement -> { + dependencyResolutionManagement.repositories(repositories -> { + repositories.matching(GRADLE_DISTRIBUTIONS_REPOSITORY_SPEC).all(once(() -> { + // TODO: Potential cache collision, differentiate between settings vs project cache dir + Bob cacheDir = objects.newInstance(Bob.class, layout.getBuildDirectory().dir("repo")); + dependencyResolutionManagement.repositories(repo ->repo.maven(cacheDir.asMavenRepository())); // required + + dependencyResolutionManagement.component(it -> { + it.withModule("dev.gradleplugins:gradle-api", GradleDistributionToGeneratedGradleJarsMetadataRule.class, cacheDir.asMetadataRuleParameters()); + it.withModule("dev.gradleplugins:gradle-test-kit", GradleDistributionToGeneratedGradleJarsMetadataRule.class, cacheDir.asMetadataRuleParameters()); + }); + + action.accept(project -> { + project.getPluginManager().apply(ProjectRule.class); + }); + })); + }); + }; + } + + public interface GeneratedJarCache { + Action forGeneratedModuleOf(ModuleVersionIdentifier id); + } + + public static abstract class Bob implements GeneratedJarCache { + private static final ModuleGroupIdentifier GENERATED_GROUP = ModuleGroupIdentifier.of("dev.gradleplugins.generated"); + private static final Filenames names = new Filenames(); + private final Provider cacheDir; + + @Inject + public Bob(Provider cacheDir) { + this.cacheDir = cacheDir; + } + + @Override + public Action forGeneratedModuleOf(ModuleVersionIdentifier t) { + return dependency -> { + dependency.add(GENERATED_GROUP + ":" + t.getName() + ":" + t.getVersion()); + File moduleFile = cacheDir.get().file(GENERATED_GROUP.asMavenPath() + "/" + t.getName() + "/" + t.getVersion() + "/" + names.file(t).module()).getAsFile(); + writeModuleFile(moduleFile, names, GENERATED_GROUP, t); + try { + new File(moduleFile.getParentFile(), names.file(t).generatedJar()).createNewFile(); + new File(moduleFile.getParentFile(), names.file(t, "sources").generatedJar()).createNewFile(); + new File(moduleFile.getParentFile(), names.file(t, "javadoc").generatedJar()).createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }; + } + + public Action asMavenRepository() { + return repo -> { + repo.setUrl(cacheDir); + repo.metadataSources(it -> { + it.gradleMetadata(); + it.artifact(); + }); + repo.mavenContent(it -> { + it.includeGroup(GENERATED_GROUP.toString()); + }); + }; + } + + // Because ActionConfiguration doesn't allow Gradle types + public Action asMetadataRuleParameters() { + return spec -> spec.params(cacheDir); + } + } + + + /*private*/ static abstract /*final*/ class GradleDistributionToGeneratedGradleJarsMetadataRule implements ComponentMetadataRule { + private final GeneratedJarCache cache; + + @Inject + public GradleDistributionToGeneratedGradleJarsMetadataRule(ObjectFactory objects, Provider cacheDir) { + this(objects.newInstance(Bob.class, cacheDir)); + } + + private GradleDistributionToGeneratedGradleJarsMetadataRule(GeneratedJarCache cache) { + this.cache = cache; + } + + @Override + public void execute(ComponentMetadataContext context) { + GradleVersion version = GradleVersion.version(context.getDetails().getId().getVersion()); + if (version.isSnapshot() || !version.getBaseVersion().getVersion().equals(version.getVersion()) || GradleVersion.version("8.9").compareTo(version) < 0) { + ComponentMetadataDetails details = context.getDetails(); + details.addVariant("apiElements", x -> { + x.withDependencies(cache.forGeneratedModuleOf(details.getId())); + }); + } + } + } + } + + public interface Factory { + T gradleDistributions(Action action); + T gradleDistributionsSnapshots(Action action); + } + + public static final class GradleDistributionVersions implements Iterable { + private final File all; + + public GradleDistributionVersions(File all) { + this.all = all; + } + + @Override + public Iterator iterator() { + try (Stream stream = Files.lines(all.toPath())) { + return stream.filter(it -> it.contains("version")) + .map(String::trim) + .map(it -> it.replace("\"version\" : \"", "").replace("\",", "")) + .map(GradleVersion::version) + .collect(Collectors.toList()).iterator(); + } catch ( + IOException e) { + throw new RuntimeException(e); + } + } + } + + private static final class DynamicVersionSupportFactory implements Factory { + private final GradleDistributionVersions versions; + private final Factory delegate; + + public DynamicVersionSupportFactory(GradleDistributionVersions versions, Factory delegate) { + this.versions = versions; + this.delegate = delegate; + } + + @Override + public T gradleDistributions(Action action) { + return delegate.gradleDistributions(withDynamicVersionSupport(it -> !it.isSnapshot(), action)); + } + + @Override + public T gradleDistributionsSnapshots(Action action) { + return delegate.gradleDistributionsSnapshots(withDynamicVersionSupport(it -> it.isSnapshot(), action)); + } + + private Action withDynamicVersionSupport(Predicate predicate, Action action) { + return repo -> { + action.execute(repo); + + repo.setComponentVersionsLister(MyLister.class, spec -> spec.params(StreamSupport.stream(versions.spliterator(), false).filter(predicate).map(GradleVersion::getVersion).collect(Collectors.toList()))); + }; + } + } + + /*private*/ static abstract /*final*/ class MyLister implements ComponentMetadataVersionLister { + private final List versions; + + @Inject + public MyLister(List versions) { + this.versions = versions; + } + + @Override + public void execute(ComponentMetadataListerDetails details) { + details.listed(versions); + } + } + + private static final class BaseFactory implements Factory { + private final RepositoryHandler repositories; + + public BaseFactory(RepositoryHandler repositories) { + this.repositories = repositories; + } + + @Override + @SuppressWarnings("unchecked") + public T gradleDistributions(Action action) { + return (T) repositories.ivy(repo -> { + action.execute((T) repo); + + repo.setName("Gradle Distributions"); + repo.setUrl("https://services.gradle.org/distributions/"); + repo.patternLayout(this::toVersionedBinDistribution); + repo.metadataSources(IvyArtifactRepository.MetadataSources::artifact); + repo.content(this::onlyGeneratedGradleJars); + }); + } + + @Override + @SuppressWarnings("unchecked") + public T gradleDistributionsSnapshots(Action action) { + return (T) repositories.ivy(repo -> { + action.execute((T) repo); + + repo.setName("Gradle Distributions Snapshots"); + repo.setUrl("https://services.gradle.org/distributions-snapshots/"); + repo.patternLayout(this::toVersionedBinDistribution); + repo.metadataSources(IvyArtifactRepository.MetadataSources::artifact); + repo.content(this::onlyGeneratedGradleJars); + }); + } + + private void toVersionedBinDistribution(IvyPatternRepositoryLayout layout) { + layout.artifact("/gradle-[revision]-bin.zip"); + } + + private void onlyGeneratedGradleJars(RepositoryContentDescriptor content) { + content.includeModule("dev.gradleplugins", "gradle-api"); + content.includeModule("dev.gradleplugins", "gradle-test-kit"); + } + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java index ea97afec..498c8239 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java @@ -12,6 +12,7 @@ public GradlePluginDevelopmentBasePlugin() {} @Override public void apply(Project project) { + project.getPluginManager().apply("gradlepluginsdev.rules.gradle-distribution-repositories"); project.getPluginManager().apply("gradlepluginsdev.rules.gradle-jvm-compatibilities"); project.getPluginManager().apply("gradlepluginsdev.rules.project-extensions"); project.getPluginManager().apply("gradlepluginsdev.rules.gradle-plugin-compatibility-extension"); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java index 6238b087..ab647c95 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java @@ -37,10 +37,12 @@ public void apply(Object target) { } private void doApply(Project project) { + project.getPluginManager().apply("gradlepluginsdev.rules.gradle-distribution-repositories"); project.getPluginManager().apply("gradlepluginsdev.rules.project-extensions"); } private void doApply(Settings settings) { + settings.getPluginManager().apply("gradlepluginsdev.rules.gradle-distribution-repositories"); settings.getPluginManager().apply("gradlepluginsdev.rules.settings-repositories-extension"); settings.getGradle().addBuildListener(new BuildAdapter() { @Override diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java index ca15a273..845235e9 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectDependenciesExtensionRule.java @@ -37,6 +37,7 @@ public void apply(Project project) { GroovyHelper.instance().addNewInstanceMethod(dependencies, "gradlePlugin", new MethodClosure(extension, "gradlePlugin")); } + // NOTE: The class MUST NOT BE a Gradle type because of the Groovy method injection private static final class DefaultGradlePluginDevelopmentDependencyExtension implements GradlePluginDevelopmentDependencyExtension, HasPublicType { private final DependencyFactory factory; private final Transformer gradleApiTransformer; diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java index 65ff4062..4430aae7 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java @@ -1,6 +1,7 @@ package dev.gradleplugins.internal.rules; import dev.gradleplugins.GradlePluginDevelopmentRepositoryExtension; +import dev.gradleplugins.internal.GradleDistributionRepositories; import dev.gradleplugins.internal.runtime.dsl.GroovyHelper; import dev.gradleplugins.internal.util.ClosureWrappedConfigureAction; import groovy.lang.Closure; @@ -10,6 +11,7 @@ import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.artifacts.repositories.ArtifactRepository; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.initialization.Settings; import org.gradle.api.logging.Logger; @@ -20,8 +22,10 @@ import org.gradle.internal.Actions; import javax.inject.Inject; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.util.function.Supplier; + +import static dev.gradleplugins.internal.util.DoNothingAction.doNothing; +import static dev.gradleplugins.internal.util.MinimumDependencyResolutionManagement.dependencyResolutionManagement; /*private*/ final class RepositoriesExtensionRules { private static final Logger LOGGER = Logging.getLogger(RepositoriesExtensionRules.class); @@ -33,7 +37,7 @@ private RepositoriesExtensionRules() {} public ForProject() {} public void apply(Project project) { - decorate(project.getRepositories()); + decorate(project.getRepositories(), () -> project.getExtensions().getByType(GradleDistributionRepositories.Factory.class)); } } @@ -43,32 +47,33 @@ public ForSettings() {} @Override public void apply(Settings settings) { - try { - Method Settings__getDependencyResolutionManagement = settings.getClass().getDeclaredMethod("getDependencyResolutionManagement"); - Object dependencyResolutionManagement = Settings__getDependencyResolutionManagement.invoke(settings); - Method DependencyResolutionManagement__getRepositories = dependencyResolutionManagement.getClass().getDeclaredMethod("getRepositories"); - RepositoryHandler repositories = (RepositoryHandler) DependencyResolutionManagement__getRepositories.invoke(dependencyResolutionManagement); - - decorate(repositories); - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - // ignore, lower Gradle - } + dependencyResolutionManagement(settings, it -> { + it.repositories(repositories -> { + decorate(repositories, () -> settings.getExtensions().getByType(GradleDistributionRepositories.Factory.class)); + }); + }); } } - private static void decorate(RepositoryHandler repositories) { - final GradlePluginDevelopmentRepositoryExtension extension = new DefaultGradlePluginDevelopmentRepositoryExtension(repositories); + private static void decorate(RepositoryHandler repositories, Supplier + factory) { + final GradlePluginDevelopmentRepositoryExtension extension = new DefaultGradlePluginDevelopmentRepositoryExtension(repositories, factory); ((ExtensionAware) repositories).getExtensions().add("gradlePluginDevelopment", extension); GroovyHelper.instance().addNewInstanceMethod(repositories, "gradlePluginDevelopment", new MethodClosure(extension, "gradlePluginDevelopment")); + GroovyHelper.instance().addNewInstanceMethod(repositories, "gradleDistributions", new MethodClosure(extension, "gradleDistributions")); + GroovyHelper.instance().addNewInstanceMethod(repositories, "gradleDistributionsSnapshots", new MethodClosure(extension, "gradleDistributionsSnapshots")); } + // NOTE: The class MUST NOT BE a Gradle type because of the Groovy method injection private static final class DefaultGradlePluginDevelopmentRepositoryExtension implements GradlePluginDevelopmentRepositoryExtension, HasPublicType { private final RepositoryHandler repositories; + private final Supplier factory; @Inject - public DefaultGradlePluginDevelopmentRepositoryExtension(RepositoryHandler repositories) { + public DefaultGradlePluginDevelopmentRepositoryExtension(RepositoryHandler repositories, Supplier factory) { this.repositories = repositories; + this.factory = factory; } @Override @@ -97,6 +102,16 @@ public MavenArtifactRepository gradlePluginDevelopment(@DelegatesTo(MavenArtifac return gradlePluginDevelopment(new ClosureWrappedConfigureAction<>(action)); } + @Override + public ArtifactRepository gradleDistributions() { + return factory.get().gradleDistributions(doNothing()); + } + + @Override + public ArtifactRepository gradleDistributionsSnapshots() { + return factory.get().gradleDistributionsSnapshots(doNothing()); + } + @Override public TypeOf getPublicType() { return TypeOf.typeOf(GradlePluginDevelopmentRepositoryExtension.class); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/DoNothingAction.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/DoNothingAction.java new file mode 100644 index 00000000..d420b085 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/DoNothingAction.java @@ -0,0 +1,16 @@ +package dev.gradleplugins.internal.util; + +import org.gradle.api.Action; + +public final class DoNothingAction implements Action { + private DoNothingAction() {} + + @Override + public void execute(T t) { + // do nothing + } + + public static Action doNothing() { + return new DoNothingAction<>(); + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/MinimumDependencyResolutionManagement.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/MinimumDependencyResolutionManagement.java new file mode 100644 index 00000000..921b8974 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/MinimumDependencyResolutionManagement.java @@ -0,0 +1,78 @@ +package dev.gradleplugins.internal.util; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.artifacts.dsl.ComponentMetadataHandler; +import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.initialization.Settings; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +// Adapter for a dependency resolution management-ish adapter for Settings/Project +public abstract class MinimumDependencyResolutionManagement { + + public abstract RepositoryHandler getRepositories(); + public abstract void repositories(Action action); + public abstract void component(Action action); + + + public static void dependencyResolutionManagement(Settings settings, Action action) { + try { + Method Settings__getDependencyResolutionManagement = settings.getClass().getDeclaredMethod("getDependencyResolutionManagement"); + Object dependencyResolutionManagement = Settings__getDependencyResolutionManagement.invoke(settings); + + action.execute(new MinimumDependencyResolutionManagement() { + @Override + public RepositoryHandler getRepositories() { + try { + Method DependencyResolutionManagement__getRepositories = dependencyResolutionManagement.getClass().getDeclaredMethod("getRepositories"); + RepositoryHandler repositories = (RepositoryHandler) DependencyResolutionManagement__getRepositories.invoke(dependencyResolutionManagement); + + return repositories; + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + @Override + public void repositories(Action action) { + action.execute(getRepositories()); + } + + @Override + public void component(Action action) { + try { + Method DependencyResolutionManagement__getComponents = dependencyResolutionManagement.getClass().getDeclaredMethod("getComponents"); + ComponentMetadataHandler components = (ComponentMetadataHandler) DependencyResolutionManagement__getComponents.invoke(dependencyResolutionManagement); + + action.execute(components); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + }); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + // ignore, lower Gradle + } + } + + public static void dependencyResolutionManagement(Project project, Action action) { + action.execute(new MinimumDependencyResolutionManagement() { + @Override + public RepositoryHandler getRepositories() { + return project.getRepositories(); + } + + @Override + public void repositories(Action action) { + action.execute(project.getRepositories()); + } + + @Override + public void component(Action action) { + project.getDependencies().components(action); + } + }); + } +} diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/OnceAction.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/OnceAction.java new file mode 100644 index 00000000..4559a171 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/util/OnceAction.java @@ -0,0 +1,24 @@ +package dev.gradleplugins.internal.util; + +import org.gradle.api.Action; + +public final class OnceAction implements Action { + private final Runnable delegate; + private boolean executed = false; + + private OnceAction(Runnable delegate) { + this.delegate = delegate; + } + + @Override + public void execute(T t) { + if (!executed) { + executed = true; + delegate.run(); + } + } + + public static OnceAction once(Runnable action) { + return new OnceAction<>(action); + } +} diff --git a/subprojects/gradle-plugin-development/src/main/kotlin/RepositoryHandlerExtensions.kt b/subprojects/gradle-plugin-development/src/main/kotlin/RepositoryHandlerExtensions.kt index 22088c49..32ae16ff 100644 --- a/subprojects/gradle-plugin-development/src/main/kotlin/RepositoryHandlerExtensions.kt +++ b/subprojects/gradle-plugin-development/src/main/kotlin/RepositoryHandlerExtensions.kt @@ -1,8 +1,17 @@ import dev.gradleplugins.GradlePluginDevelopmentRepositoryExtension import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.artifacts.repositories.ArtifactRepository import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.api.plugins.ExtensionAware fun RepositoryHandler.gradlePluginDevelopment(): MavenArtifactRepository { return ExtensionAware::class.java.cast(this).extensions.getByType(GradlePluginDevelopmentRepositoryExtension::class.java).gradlePluginDevelopment() } + +fun RepositoryHandler.gradleDistributions(): ArtifactRepository { + return ExtensionAware::class.java.cast(this).extensions.getByType(GradlePluginDevelopmentRepositoryExtension::class.java).gradleDistributions() +} + +fun RepositoryHandler.gradleDistributionsSnapshots(): ArtifactRepository { + return ExtensionAware::class.java.cast(this).extensions.getByType(GradlePluginDevelopmentRepositoryExtension::class.java).gradleDistributionsSnapshots() +} diff --git a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-distribution-repositories.properties b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-distribution-repositories.properties new file mode 100644 index 00000000..7367bc17 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-distribution-repositories.properties @@ -0,0 +1 @@ +implementation-class=dev.gradleplugins.internal.GradleDistributionRepositories$Rule \ No newline at end of file From cba4bea3526103de745510db5660ef8d22f8ddd4 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 17 Jul 2024 14:21:41 -0400 Subject: [PATCH 2/4] Simplify plugin nesting Signed-off-by: Daniel Lacasse --- .../GradlePluginDevelopmentBasePlugin.java | 3 +- .../GradlePluginDevelopmentPlugin.java | 5 ++- ...a => GradleRepositoriesExtensionRule.java} | 44 +++++++++---------- .../internal/rules/ProjectExtensionsRule.java | 17 ------- ...s.gradle-repositories-extension.properties | 1 + ...insdev.rules.project-extensions.properties | 1 - ....project-repositories-extension.properties | 1 - ...settings-repositories-extension.properties | 1 - 8 files changed, 27 insertions(+), 46 deletions(-) rename subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/{RepositoriesExtensionRules.java => GradleRepositoriesExtensionRule.java} (81%) delete mode 100644 subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectExtensionsRule.java create mode 100644 subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-repositories-extension.properties delete mode 100644 subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.project-extensions.properties delete mode 100644 subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.project-repositories-extension.properties delete mode 100644 subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.settings-repositories-extension.properties diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java index 498c8239..f91d5ea4 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentBasePlugin.java @@ -13,8 +13,9 @@ public GradlePluginDevelopmentBasePlugin() {} @Override public void apply(Project project) { project.getPluginManager().apply("gradlepluginsdev.rules.gradle-distribution-repositories"); + project.getPluginManager().apply("gradlepluginsdev.rules.gradle-repositories-extension"); + project.getPluginManager().apply("gradlepluginsdev.rules.project-dependencies-extension"); project.getPluginManager().apply("gradlepluginsdev.rules.gradle-jvm-compatibilities"); - project.getPluginManager().apply("gradlepluginsdev.rules.project-extensions"); project.getPluginManager().apply("gradlepluginsdev.rules.gradle-plugin-compatibility-extension"); project.getPluginManager().apply("gradlepluginsdev.rules.gradle-plugin-api-extension"); project.getPluginManager().apply("gradlepluginsdev.rules.gradle-plugin-dependencies"); diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java index ab647c95..e8f14552 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/plugins/GradlePluginDevelopmentPlugin.java @@ -38,12 +38,13 @@ public void apply(Object target) { private void doApply(Project project) { project.getPluginManager().apply("gradlepluginsdev.rules.gradle-distribution-repositories"); - project.getPluginManager().apply("gradlepluginsdev.rules.project-extensions"); + project.getPluginManager().apply("gradlepluginsdev.rules.gradle-repositories-extension"); + project.getPluginManager().apply("gradlepluginsdev.rules.project-dependencies-extension"); } private void doApply(Settings settings) { settings.getPluginManager().apply("gradlepluginsdev.rules.gradle-distribution-repositories"); - settings.getPluginManager().apply("gradlepluginsdev.rules.settings-repositories-extension"); + settings.getPluginManager().apply("gradlepluginsdev.rules.gradle-repositories-extension"); settings.getGradle().addBuildListener(new BuildAdapter() { @Override public void buildFinished(BuildResult result) { diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradleRepositoriesExtensionRule.java similarity index 81% rename from subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java rename to subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradleRepositoriesExtensionRule.java index 4430aae7..1e0d1447 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/RepositoriesExtensionRules.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/GradleRepositoriesExtensionRule.java @@ -14,9 +14,8 @@ import org.gradle.api.artifacts.repositories.ArtifactRepository; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.initialization.Settings; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.api.plugins.ExtensionAware; +import org.gradle.api.plugins.PluginAware; import org.gradle.api.reflect.HasPublicType; import org.gradle.api.reflect.TypeOf; import org.gradle.internal.Actions; @@ -27,32 +26,31 @@ import static dev.gradleplugins.internal.util.DoNothingAction.doNothing; import static dev.gradleplugins.internal.util.MinimumDependencyResolutionManagement.dependencyResolutionManagement; -/*private*/ final class RepositoriesExtensionRules { - private static final Logger LOGGER = Logging.getLogger(RepositoriesExtensionRules.class); - - private RepositoriesExtensionRules() {} - - /*private*/ static abstract /*final*/ class ForProject implements Plugin { - @Inject - public ForProject() {} - - public void apply(Project project) { - decorate(project.getRepositories(), () -> project.getExtensions().getByType(GradleDistributionRepositories.Factory.class)); +/*private*/ abstract /*final*/ class GradleRepositoriesExtensionRule implements Plugin { + @Inject + public GradleRepositoriesExtensionRule() {} + + @Override + public void apply(PluginAware target) { + if (target instanceof Project) { + applyTo((Project) target); + } else if (target instanceof Settings) { + applyTo((Settings) target); + } else { + throw new UnsupportedOperationException(); } } - /*private*/ static abstract /*final*/ class ForSettings implements Plugin { - @Inject - public ForSettings() {} + private void applyTo(Project project) { + decorate(project.getRepositories(), () -> project.getExtensions().getByType(GradleDistributionRepositories.Factory.class)); + } - @Override - public void apply(Settings settings) { - dependencyResolutionManagement(settings, it -> { - it.repositories(repositories -> { - decorate(repositories, () -> settings.getExtensions().getByType(GradleDistributionRepositories.Factory.class)); - }); + private void applyTo(Settings settings) { + dependencyResolutionManagement(settings, it -> { + it.repositories(repositories -> { + decorate(repositories, () -> settings.getExtensions().getByType(GradleDistributionRepositories.Factory.class)); }); - } + }); } private static void decorate(RepositoryHandler repositories, Supplier diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectExtensionsRule.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectExtensionsRule.java deleted file mode 100644 index 3a849334..00000000 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/rules/ProjectExtensionsRule.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.gradleplugins.internal.rules; - -import org.gradle.api.Plugin; -import org.gradle.api.Project; - -import javax.inject.Inject; - -/*private*/ abstract /*final*/ class ProjectExtensionsRule implements Plugin { - @Inject - public ProjectExtensionsRule() {} - - @Override - public void apply(Project project) { - project.getPluginManager().apply("gradlepluginsdev.rules.project-repositories-extension"); - project.getPluginManager().apply("gradlepluginsdev.rules.project-dependencies-extension"); - } -} diff --git a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-repositories-extension.properties b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-repositories-extension.properties new file mode 100644 index 00000000..581f97c2 --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.gradle-repositories-extension.properties @@ -0,0 +1 @@ +implementation-class=dev.gradleplugins.internal.rules.GradleRepositoriesExtensionRule \ No newline at end of file diff --git a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.project-extensions.properties b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.project-extensions.properties deleted file mode 100644 index 8fad3ec2..00000000 --- a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.project-extensions.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=dev.gradleplugins.internal.rules.ProjectExtensionsRule \ No newline at end of file diff --git a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.project-repositories-extension.properties b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.project-repositories-extension.properties deleted file mode 100644 index 78d4d531..00000000 --- a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.project-repositories-extension.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=dev.gradleplugins.internal.rules.RepositoriesExtensionRules$ForProject \ No newline at end of file diff --git a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.settings-repositories-extension.properties b/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.settings-repositories-extension.properties deleted file mode 100644 index 3a41c803..00000000 --- a/subprojects/gradle-plugin-development/src/main/resources/META-INF/gradle-plugins/gradlepluginsdev.rules.settings-repositories-extension.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=dev.gradleplugins.internal.rules.RepositoriesExtensionRules$ForSettings \ No newline at end of file From 75c84662b92457d5593568574dfd5f806b95cae1 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 17 Jul 2024 14:44:29 -0400 Subject: [PATCH 3/4] Use the correct Groovy and Kotlin version in transitive dependencies Signed-off-by: Daniel Lacasse --- .../GradleDistributionRepositories.java | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java index edc319a2..3795ecc9 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java @@ -1,5 +1,6 @@ package dev.gradleplugins.internal; +import dev.gradleplugins.GradleRuntimeCompatibility; import dev.gradleplugins.internal.util.MinimumDependencyResolutionManagement; import org.gradle.api.Action; import org.gradle.api.ActionConfiguration; @@ -47,7 +48,6 @@ import javax.inject.Inject; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; @@ -65,7 +65,8 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import static dev.gradleplugins.internal.util.MinimumDependencyResolutionManagement.dependencyResolutionManagement; +import static dev.gradleplugins.GradleRuntimeCompatibility.groovyVersionOf; +import static dev.gradleplugins.GradleRuntimeCompatibility.kotlinVersionOf; import static dev.gradleplugins.internal.util.MinimumDependencyResolutionManagement.dependencyResolutionManagement; import static dev.gradleplugins.internal.util.OnceAction.once; @@ -217,7 +218,8 @@ public String toString() { } } - public static void writeModuleFile(File moduleFile, Filenames names, ModuleGroupIdentifier group, ModuleVersionIdentifier id) { + // TODO: Add support for old Gradle API jars + private static void writeModuleFile(File moduleFile, Filenames names, ModuleGroupIdentifier group, ModuleVersionIdentifier id) { moduleFile.getParentFile().mkdirs(); try (PrintStream out = new PrintStream(moduleFile)) { out.println("{"); @@ -230,11 +232,6 @@ public static void writeModuleFile(File moduleFile, Filenames names, ModuleGroup out.println(" \"org.gradle.status\": \"release\""); out.println(" }"); out.println(" },"); - out.println(" \"createdBy\": {"); - out.println(" \"gradle\": {"); - out.println(" \"version\": \"6.8.1\""); - out.println(" }"); - out.println(" },"); out.println(" \"variants\": ["); out.println(" {"); out.println(" \"name\": \"apiElements\","); @@ -250,7 +247,7 @@ public static void writeModuleFile(File moduleFile, Filenames names, ModuleGroup out.println(" \"group\": \"org.codehaus.groovy\","); out.println(" \"module\": \"groovy\","); out.println(" \"version\": {"); - out.println(" \"requires\": \"3.0.21\""); + out.println(" \"requires\": \"" + groovyVersionOf(id.getVersion()) + "\""); out.println(" }"); out.println(" }"); out.println(" ],"); @@ -275,7 +272,7 @@ public static void writeModuleFile(File moduleFile, Filenames names, ModuleGroup out.println(" \"group\": \"org.codehaus.groovy\","); out.println(" \"module\": \"groovy-all\","); out.println(" \"version\": {"); - out.println(" \"requires\": \"3.0.21\""); + out.println(" \"requires\": \"" + groovyVersionOf(id.getVersion()) + "\""); out.println(" },"); out.println(" \"thirdPartyCompatibility\": {"); out.println(" \"artifactSelector\": {"); @@ -284,13 +281,15 @@ public static void writeModuleFile(File moduleFile, Filenames names, ModuleGroup out.println(" \"extension\": \"pom\""); out.println(" }"); out.println(" }"); - out.println(" },"); - out.println(" {"); - out.println(" \"group\": \"org.jetbrains.kotlin\","); - out.println(" \"module\": \"kotlin-stdlib\","); - out.println(" \"version\": {"); - out.println(" \"requires\": \"1.9.23\""); - out.println(" }"); + if (kotlinVersionOf(id.getVersion()).isPresent()) { + out.println(" },"); + out.println(" {"); + out.println(" \"group\": \"org.jetbrains.kotlin\","); + out.println(" \"module\": \"kotlin-stdlib\","); + out.println(" \"version\": {"); + out.println(" \"requires\": \"" + kotlinVersionOf(id.getVersion()).get() + "\""); + out.println(" }"); + } out.println(" }"); out.println(" ],"); out.println(" \"files\": ["); @@ -426,8 +425,7 @@ public void transform(TransformOutputs outputs) { File jarFile = outputs.file(gradleJar.getName()); File generatedJarFile = getParameters().getGradleUserHomeDirectory().file("caches/" + gradleJar.getGradleVersion() + "/generated-gradle-jars/" + gradleJar.getName()).map(FileSystemLocation::getAsFile).get(); Files.copy(generatedJarFile.toPath(), jarFile.toPath()); - } catch ( - IOException e) { + } catch (IOException e) { throw new RuntimeException(e); } } else if (gradleJar.getName().getClassifier().equals("javadoc")) { From ed561bcf788b73bd2bc2b0d0aa238ddcd75b02b4 Mon Sep 17 00:00:00 2001 From: Daniel Lacasse Date: Wed, 17 Jul 2024 14:44:51 -0400 Subject: [PATCH 4/4] Migrate javadoc/sources JARs generation from gradle-plugins/gradle-api Signed-off-by: Daniel Lacasse --- .../GradleDistributionRepositories.java | 133 +++++++++++++++++- 1 file changed, 129 insertions(+), 4 deletions(-) diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java index 3795ecc9..06759465 100644 --- a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/GradleDistributionRepositories.java @@ -428,13 +428,138 @@ public void transform(TransformOutputs outputs) { } catch (IOException e) { throw new RuntimeException(e); } - } else if (gradleJar.getName().getClassifier().equals("javadoc")) { - throw new UnsupportedOperationException("Javadoc generation not supported"); } else if (gradleJar.getName().getClassifier().equals("sources")) { - throw new UnsupportedOperationException("Sources generation not supported"); + try { + Files.write(workingDirectory.resolve("build.gradle"), Arrays.asList( + "buildscript {", + " dependencies {", + " classpath 'io.github.http-builder-ng:http-builder-ng-core:1.0.4'", + " }", + " repositories {", + " mavenCentral()", + " }", + "}", + "", + "apply plugin: 'java'", + "import groovyx.net.http.HttpBuilder", + "import groovyx.net.http.optional.Download", + "tasks.create('download') {", + " ext.outputFile = file(\"gradle-src-" + gradleJar.getGradleVersion() + ".zip\")", + " outputs.file(outputFile)", + " inputs.property('gradleVersion', gradle.gradleVersion)", + " doFirst {", + " File file = HttpBuilder.configure {", + // TODO: Use the right distributions link + " request.uri = \"https://services.gradle.org/distributions/gradle-" + gradleJar.getGradleVersion() + "-src.zip\"", + " request.headers.put('User-Agent', 'gradle-api-extractor');", + " }.get {", + " Download.toFile(delegate, outputFile)", + " }", + " }", + "}", + "tasks.create('generate', Zip) {", + " dependsOn(tasks.download)", + " from({zipTree(tasks.download.outputFile).matching { include('*/subprojects/*/src/*/**/*.java', '*/subprojects/*/src/*/**/*.groovy', '*/subprojects/*/src/*/**/*.kt').exclude('buildSrc/**/*') } }) {", + " eachFile {", + " relativePath = new RelativePath(relativePath.file, relativePath.segments.drop(6))", + " }", + " includeEmptyDirs = false", + " }", + " baseName = 'gradle'", + " version = '" + gradleJar.getGradleVersion() + "'", + " extension = 'jar'", + " classifier = 'src'", + "}" + )); + Files.createFile(workingDirectory.resolve("settings.gradle")); + + final GradleConnector connector = GradleConnector.newConnector(); + connector.useGradleVersion("6.5"); // doesn't need to be align with requested + connector.useGradleUserHomeDir(getParameters().getGradleUserHomeDirectory().getAsFile().get()); + connector.forProjectDirectory(workingDirectory.toFile()); + + withOutput(workingDirectory.resolve("output.txt"), outStream -> { + try (ProjectConnection connection = connector.connect()) { + final BuildLauncher launcher = connection.newBuild().forTasks("generate"); + launcher.setStandardOutput(outStream); + launcher.setStandardError(outStream); + launcher.run(); + } + }); + + File jarFile = outputs.file(gradleJar.getName()); + File generatedJarFile = workingDirectory.resolve("build/distributions/gradle-" + gradleJar.getGradleVersion() + "-src.jar").toFile(); + Files.copy(generatedJarFile.toPath(), jarFile.toPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else if (gradleJar.getName().getClassifier().equals("javadoc")) { + try { + Files.write(workingDirectory.resolve("build.gradle"), Arrays.asList( + "buildscript {", + " dependencies {", + " classpath 'io.github.http-builder-ng:http-builder-ng-core:1.0.4'", + " }", + " repositories {", + " mavenCentral()", + " }", + "}", + "", + "apply plugin: 'java'", + "import groovyx.net.http.HttpBuilder", + "import groovyx.net.http.optional.Download", + "tasks.create('download') {", + " ext.outputFile = file(\"gradle-javadoc-" + gradleJar.getGradleVersion() + ".zip\")", + " outputs.file(outputFile)", + " inputs.property('gradleVersion', gradle.gradleVersion)", + " doFirst {", + " File file = HttpBuilder.configure {", + // TODO: Use the right distributions link + " request.uri = \"https://services.gradle.org/distributions/gradle-" + gradleJar.getGradleVersion() + "-all.zip\"", + " request.headers.put('User-Agent', 'gradle-api-extractor');", + " }.get {", + " Download.toFile(delegate, outputFile)", + " }", + " }", + "}", + "tasks.create('generate', Zip) {", + " dependsOn(tasks.download)", + " from({zipTree(tasks.download.outputFile).matching { include('*/docs/javadoc/**/*') } }) {", + " eachFile {", + " relativePath = new RelativePath(relativePath.file, relativePath.segments.drop(3))", + " }", + " includeEmptyDirs = false", + " }", + " baseName = 'gradle'", + " version = '" + gradleJar.getGradleVersion() + "'", + " extension = 'jar'", + " classifier = 'javadoc'", + "}" + )); + Files.createFile(workingDirectory.resolve("settings.gradle")); + + final GradleConnector connector = GradleConnector.newConnector(); + connector.useGradleVersion("6.5"); // doesn't need to be align with requested + connector.useGradleUserHomeDir(getParameters().getGradleUserHomeDirectory().getAsFile().get()); + connector.forProjectDirectory(workingDirectory.toFile()); + + withOutput(workingDirectory.resolve("output.txt"), outStream -> { + try (ProjectConnection connection = connector.connect()) { + final BuildLauncher launcher = connection.newBuild().forTasks("generate"); + launcher.setStandardOutput(outStream); + launcher.setStandardError(outStream); + launcher.run(); + } + }); + + File jarFile = outputs.file(gradleJar.getName()); + File generatedJarFile = workingDirectory.resolve("build/distributions/gradle-" + gradleJar.getGradleVersion() + "-javadoc.jar").toFile(); + Files.copy(generatedJarFile.toPath(), jarFile.toPath()); + } catch (IOException e) { + throw new RuntimeException(e); + } } }); - }); }