diff --git a/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/ConsumeGradleDistributionDirectlyFromServicesPlugin.java b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/ConsumeGradleDistributionDirectlyFromServicesPlugin.java new file mode 100644 index 0000000..5a68e3c --- /dev/null +++ b/subprojects/gradle-plugin-development/src/main/java/dev/gradleplugins/internal/ConsumeGradleDistributionDirectlyFromServicesPlugin.java @@ -0,0 +1,514 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package dev.gradleplugins.internal; + +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.repositories.IvyArtifactRepository; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +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.model.ObjectFactory; +import org.gradle.api.provider.Provider; +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.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.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class ConsumeGradleDistributionDirectlyFromServicesPlugin implements Plugin { + 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").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()); + }); + }); + + + GradleDistributionVersions lister = new GradleDistributionVersions(project.getResources().getText().fromUri("https://services.gradle.org/versions/all").asFile()); + + IvyArtifactRepository distributionsRepository = project.getRepositories().ivy(ForGradle.distributions(lister)); + IvyArtifactRepository distributionsSnapshotsRepository = project.getRepositories().ivy(ForGradle.distributionsSnapshots(lister)); + + Bob cacheDir = project.getObjects().newInstance(Bob.class, project.getLayout().getBuildDirectory().dir("repo")); + project.getRepositories().maven(cacheDir.asMavenRepository()); // required + + MavenArtifactRepository mavenCentralRepository = project.getRepositories().mavenCentral(); + + distributionsRepository.content(it -> it.includeModule("dev.gradleplugins", "gradle-api")); + distributionsSnapshotsRepository.content(it -> it.includeModule("dev.gradleplugins", "gradle-api")); + mavenCentralRepository.mavenContent(it -> it.excludeModule("dev.gradleplugins", "gradle-api")); + project.getDependencies().components(it -> { + it.withModule("dev.gradleplugins:gradle-api", GradleDistributionToGeneratedGradleJarsMetadataRule.class, cacheDir.asMetadataRuleParameters()); + }); + + distributionsRepository.content(it -> it.includeModule("dev.gradleplugins", "gradle-test-kit")); + distributionsSnapshotsRepository.content(it -> it.includeModule("dev.gradleplugins", "gradle-test-kit")); + mavenCentralRepository.mavenContent(it -> it.excludeModule("dev.gradleplugins", "gradle-test-kit")); + project.getDependencies().components(it -> { + it.withModule("dev.gradleplugins:gradle-test-kit", GradleDistributionToGeneratedGradleJarsMetadataRule.class, cacheDir.asMetadataRuleParameters()); + }); + } + + private static final class GradleDistributionVersions implements Iterable { + private final File all; + + private 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 ForGradle implements Action { + enum DistributionType { + DISTRIBUTIONS("https://services.gradle.org/distributions/") { + @Override + boolean valid(GradleVersion version) { + return !version.isSnapshot(); + } + }, + DISTRIBUTIONS_SNAPSHOTS("https://services.gradle.org/distributions-snapshots/") { + @Override + boolean valid(GradleVersion version) { + return version.isSnapshot(); + } + }; + + private final String url; + + DistributionType(String url) { + this.url = url; + } + + abstract boolean valid(GradleVersion version); + } + + private final DistributionType type; + private final GradleDistributionVersions versions; + + private ForGradle(DistributionType type, GradleDistributionVersions versions) { + this.type = type; + this.versions = versions; + } + + @Override + public void execute(IvyArtifactRepository repo) { + repo.setUrl(type.url); + repo.patternLayout(it -> { + it.artifact("/gradle-[revision]-bin.zip"); + }); + repo.metadataSources(it -> { + it.artifact(); + }); + repo.setComponentVersionsLister(MyLister.class, spec -> spec.params(StreamSupport.stream(versions.spliterator(), false).filter(type::valid).map(GradleVersion::getVersion).collect(Collectors.toList()))); + } + + public static ForGradle distributions(GradleDistributionVersions gradleVersions) { + return new ForGradle(DistributionType.DISTRIBUTIONS, gradleVersions); + } + + public static ForGradle distributionsSnapshots(GradleDistributionVersions gradleVersions) { + return new ForGradle(DistributionType.DISTRIBUTIONS_SNAPSHOTS, gradleVersions); + } + } + + public interface GeneratedJarCache { + Action forGeneratedModuleOf(ModuleVersionIdentifier id); + } + + public static abstract class Bob implements GeneratedJarCache { + private final Provider cacheDir; + + @Inject + public Bob(Provider cacheDir) { + this.cacheDir = cacheDir; + } + + @Override + public Action forGeneratedModuleOf(ModuleVersionIdentifier t) { + return dependency -> { + dependency.add("dev.gradleplugins.generated:" + t.getName() + ":" + t.getVersion()); + File moduleFile = cacheDir.get().file("dev/gradleplugins/generated/" + t.getName() + "/" + t.getVersion() + "/" + t.getName() + "-" + t.getVersion() + ".module").getAsFile(); + writeModuleFile(moduleFile, t); + try { + new File(moduleFile.getParentFile(), t.getName() + "-" + t.getVersion() + ".generated-jar").createNewFile(); + new File(moduleFile.getParentFile(), t.getName() + "-" + t.getVersion() + "-sources.generated-jar").createNewFile(); + new File(moduleFile.getParentFile(), t.getName() + "-" + t.getVersion() + "-javadoc.generated-jar").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("dev.gradleplugins.generated"); + }); + }; + } + + // Because ActionConfiguration doesn't allow Gradle types + public Action asMetadataRuleParameters() { + return spec -> spec.params(cacheDir); + } + } + + public static abstract 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 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) { + ComponentMetadataDetails t = context.getDetails(); + t.addVariant("apiElements", x -> { + x.withDependencies(cache.forGeneratedModuleOf(t.getId())); + }); + } + } + + @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(getInputArtifact().map(FileSystemLocation::getAsFile).get().getName(), gradleJar -> { + 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); + } + }); + + }); + } + + private static void withOutput(Path outputFile, Consumer action) { + try (OutputStream outStream = new FileOutputStream(outputFile.toFile())) { + 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(String artifactName, Consumer action) { + assert artifactName.endsWith(".generated-jar"); + + action.accept(new GeneratedGradleJar() { + @Override + public String getGradleVersion() { + return artifactName.replace("gradle-api-", "") + .replace("gradle-test-kit-", "") + .replace(".generated-jar", ""); + } + + private String getBaseName() { + if (artifactName.startsWith("gradle-api")) { + return "gradle-api"; + } else if (artifactName.startsWith("gradle-test-kit")) { + return "gradle-test-kit"; + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public String asGroovyDsl() { + if (artifactName.startsWith("gradle-api")) { + return "gradleApi()"; + } else if (artifactName.startsWith("gradle-test-kit")) { + return "gradleTestKit()"; + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public String getName() { + return getBaseName() + "-" + getGradleVersion() + ".jar"; + } + }); + } + + private interface GeneratedGradleJar { + String getGradleVersion(); + String asGroovyDsl(); + + String getName(); + } + } + + private static void writeModuleFile(File moduleFile, ModuleVersionIdentifier id) { + moduleFile.getParentFile().mkdirs(); + try (PrintStream out = new PrintStream(moduleFile)) { + out.println("{"); + out.println(" \"formatVersion\": \"1.1\","); + out.println(" \"component\": {"); + out.println(" \"group\": \"dev.gradleplugins.generated\","); + 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\": \"" + id.getName() + "-" + id.getVersion() + ".generated-jar\","); + out.println(" \"url\": \"" + id.getName() + "-" + id.getVersion() + ".generated-jar\""); + 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\": \"" + id.getName() + "-" + id.getVersion() + ".generated-jar\","); + out.println(" \"url\": \"" + id.getName() + "-" + id.getVersion() + ".generated-jar\""); + 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\": \"" + id.getName() + "-" + id.getVersion() + "-sources.generated-jar\","); + out.println(" \"url\": \"" + id.getName() + "-" + id.getVersion() + "-sources.generated-jar\""); + 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\": \"" + id.getName() + "-" + id.getVersion() + "-javadoc.generated-jar\","); + out.println(" \"url\": \"" + id.getName() + "-" + id.getVersion() + "-javadoc.generated-jar\""); + out.println(" }"); + out.println(" ]"); + out.println(" }"); + out.println(" ]"); + out.println("}"); + } catch ( + FileNotFoundException e) { + throw new RuntimeException(e); + } + } +}