From 8daf68287cfcd86f954d0de04ad4f5ff37234aab Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 9 Jan 2020 09:36:55 +0000 Subject: [PATCH] Use artifact transforms - some hacky mess here unfortunately, very WIP --- .travis.yml | 52 ++++++ build.gradle | 36 +--- gradle-plugin/build.gradle | 8 +- .../minimallycorrect/MixinGradlePlugin.java | 98 ----------- .../TaskOutputSpecificFileCollection.java | 38 ----- .../{ => mixinplugin}/ApplyMixins.java | 71 ++++++-- .../{ => mixinplugin}/ApplyMixinsTask.java | 17 +- .../mixinplugin/MixinGradlePlugin.java | 161 ++++++++++++++++++ .../{PomUtils.java => mixinplugin/Utils.java} | 17 +- .../deps/DependencyDebugTask.java | 2 +- .../deps/GeneratedDependency.java | 20 +-- .../deps/MModuleComponentIdentifier.java | 8 +- .../transform/HackyCurrentGradleSource.java | 11 ++ .../mixinplugin/transform/MixinTransform.java | 103 +++++++++++ .../test/BuildLogicFunctionalTest.groovy | 21 +-- gradle-plugin/test-template/build.gradle | 11 +- gradlew | 0 version.properties | 4 + 18 files changed, 440 insertions(+), 238 deletions(-) create mode 100644 .travis.yml delete mode 100644 gradle-plugin/src/main/java/org/minimallycorrect/MixinGradlePlugin.java delete mode 100644 gradle-plugin/src/main/java/org/minimallycorrect/deps/TaskOutputSpecificFileCollection.java rename gradle-plugin/src/main/java/org/minimallycorrect/{ => mixinplugin}/ApplyMixins.java (68%) rename gradle-plugin/src/main/java/org/minimallycorrect/{ => mixinplugin}/ApplyMixinsTask.java (72%) create mode 100644 gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/MixinGradlePlugin.java rename gradle-plugin/src/main/java/org/minimallycorrect/{PomUtils.java => mixinplugin/Utils.java} (74%) rename gradle-plugin/src/main/java/org/minimallycorrect/{ => mixinplugin}/deps/DependencyDebugTask.java (95%) rename gradle-plugin/src/main/java/org/minimallycorrect/{ => mixinplugin}/deps/GeneratedDependency.java (79%) rename gradle-plugin/src/main/java/org/minimallycorrect/{ => mixinplugin}/deps/MModuleComponentIdentifier.java (86%) create mode 100644 gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/transform/HackyCurrentGradleSource.java create mode 100644 gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/transform/MixinTransform.java mode change 100644 => 100755 gradlew create mode 100644 version.properties diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8c07345 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,52 @@ +# More details on how to configure the Travis build +# https://docs.travis-ci.com/user/customizing-the-build/ + +# Speed up build with travis caches +cache: + directories: + - $HOME/.gradle/caches/modules-2/ + - $HOME/.gradle/wrapper/ + +# Remove often changing files to prevent cache re-upload on no changes in dependencies +before_cache: + # pom, xml and jar files are known not to change + # assume non-whitelisted extensions are changing + # (this will include .lock, .bin, .metadata and potential future additions to gradle's caches folder) + - find $HOME/.gradle/caches/modules-2 -type f -not \( -iname \*.pom -o -iname \*.jar -o -iname \*.xml \) -delete + # xml files which change + - find $HOME/.gradle/caches/modules-2 -name ivy.xml -delete + # remove left over empty directories + - find $HOME/.gradle/caches/modules-2 -type d -empty -delete + +language: java + +jdk: + - openjdk8 + - openjdk11 + - openjdk12 # TODO unsupported, remove soon? + - openjdk13 + +#Skipping install step to avoid having Travis run arbitrary './gradlew assemble' task +# https://docs.travis-ci.com/user/customizing-the-build/#Skipping-the-Installation-Step +install: + - true + +#Don't build tags +branches: + only: + - master + except: + - /^v\d/ + +#Build and perform release (if needed) +script: + - ./gradlew build -s + +deploy: + provider: script + script: ./gradlew ciPerformRelease + edge: true + cleanup: false + on: + jdk: openjdk12 + branch: master diff --git a/build.gradle b/build.gradle index 3c8030f..5df7e5d 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { jcenter() maven { url "https://plugins.gradle.org/m2/" } } - dependencies { classpath 'org.minimallycorrect.gradle:DefaultsPlugin:0.0.41' } + dependencies { classpath 'org.minimallycorrect.gradle:DefaultsPlugin:0.0.45' } } plugins { id 'java' @@ -13,44 +13,26 @@ plugins { apply plugin: 'org.minimallycorrect.gradle.DefaultsPlugin' group = 'org.minimallycorrect.mixin' -version = '1.0.1-SNAPSHOT' minimallyCorrectDefaults { description = "Applies Mixin-style monkey patches to .java source or compiled .class files" labels = ["mixin", "java", "aspect-oriented-programming", "bytecode-manipulation"] }() - -/* -// Source compiler configuration -tasks.withType(JavaCompile) { - sourceCompatibility = 8 - targetCompatibility = 8 - options.with { - deprecation = true - encoding = 'UTF-8' - compilerArgs << "-Xlint:all" << "-Xlint:-path" << "-Xlint:-processing" - } -} -*/ - - allprojects { tasks.withType(Test) { maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 } -} -configurations { - hidden - compile.extendsFrom hidden - all { - resolutionStrategy { - failOnVersionConflict() + configurations { + all { + resolutionStrategy { + failOnVersionConflict() - force('com.google.code.findbugs:jsr305:3.0.2') + force('com.google.code.findbugs:jsr305:3.0.2') - cacheChangingModulesFor 10, 'seconds' + cacheChangingModulesFor 10, 'seconds' + } } } } @@ -58,7 +40,7 @@ configurations { dependencies { testImplementation "junit:junit:4.12" implementation 'me.nallar.whocalled:WhoCalled:1.1' - api 'org.minimallycorrect.javatransformer:JavaTransformer:1.8.10' + api 'org.minimallycorrect.javatransformer:JavaTransformer:1.8.12' } def gradlePluginJar = tasks.getByPath(':gradle-plugin:jar') diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index b309b80..20f1970 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -5,10 +5,12 @@ plugins { id 'org.minimallycorrect.gradle.DefaultsPlugin' } -minimallyCorrectDefaults {}() +minimallyCorrectDefaults { + shipkit = false +}() dependencies { - compileOnly 'com.google.guava:guava:25.1-jre' // bundled with gradle + //compileOnly 'com.google.guava:guava:25.1-jre' // bundled with gradle implementation rootProject testImplementation('org.spockframework:spock-core:1.3-groovy-2.4') { exclude module: 'groovy-all' @@ -20,7 +22,7 @@ gradlePlugin { plugins { mixinGradlePlugin { id = 'org.minimallycorrect.mixin.gradle-plugin' - implementationClass = 'org.minimallycorrect.MixinGradlePlugin' + implementationClass = 'org.minimallycorrect.mixinplugin.MixinGradlePlugin' } } } diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/MixinGradlePlugin.java b/gradle-plugin/src/main/java/org/minimallycorrect/MixinGradlePlugin.java deleted file mode 100644 index ae15ccb..0000000 --- a/gradle-plugin/src/main/java/org/minimallycorrect/MixinGradlePlugin.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.minimallycorrect; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import lombok.Getter; -import lombok.val; - -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.artifacts.Dependency; -import org.gradle.api.plugins.JavaPlugin; -import org.gradle.api.plugins.JavaPluginConvention; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.minimallycorrect.deps.DependencyDebugTask; -import org.minimallycorrect.mixin.internal.ApplicationType; - -@SuppressWarnings("unused") -public class MixinGradlePlugin implements Plugin { - private Settings settings; - private Logger logger = LoggerFactory.getLogger(MixinGradlePlugin.class); - - public void apply(Project project) { - // TODO: Use artifact transforms when possible (gradle >= 5.5) - // https://docs.gradle.org/current/userguide/artifact_transforms.html#sec:abm_artifact_transforms - settings = new Settings(); - project.getExtensions().add("mixin", settings); - val mixinsTask = project.task("applySubprojectMixins"); - project.getTasks().getByName("compileJava").dependsOn(mixinsTask); - project.getTasks().register("mixinDependencyDebug", DependencyDebugTask.class); - - project.afterEvaluate(this::afterEvaluate); - } - - private void afterEvaluate(Project project) { - val allTargets = new ArrayList(); - - val mixinsTask = project.getTasks().getByName("applySubprojectMixins"); - val allMixedinCfg = project.getConfigurations().create("mixedin"); - project.getConfigurations().getByName("implementation").extendsFrom(allMixedinCfg); - - settings.targets.forEach((subproject, deps) -> { - allTargets.addAll(deps); - - val mixinProject = project.project(subproject); - val mixinTargetsCfg = mixinProject.getConfigurations().create("mixinTargets", it -> { - it.setVisible(false); - it.setTransitive(false); - it.getDependencies().addAll(deps); - }); - val mixinPrePatchedCfg = mixinProject.getConfigurations().create("mixinPrePatched"); - val mixinTransitive = mixinProject.getConfigurations().create("mixinTransitive", it -> it.getDependencies().addAll(deps)); - val mixinAppliedCfg = mixinProject.getConfigurations().create("mixinApplied", it -> it.extendsFrom(mixinTransitive)); - mixinProject.getPlugins().apply(JavaPlugin.class); - - val sourceSet = mixinProject.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); - val preApplyMixins = new ApplyMixins( - ApplicationType.PRE_PATCH, - mixinTargetsCfg, - new File(mixinProject.getBuildDir(), "mixin-pre"), - sourceSet.getAllJava().getSourceDirectories()); - val preApplyMixinTask = mixinProject.getTasks().create("preApplyMixins", ApplyMixinsTask.class, preApplyMixins); - mixinPrePatchedCfg.getDependencies().addAll(preApplyMixinTask.getGeneratedDependenciesForOutputs(mixinProject, preApplyMixinTask)); - mixinProject.getConfigurations().getByName("implementation").extendsFrom(mixinPrePatchedCfg); - - val applyMixins = new ApplyMixins( - ApplicationType.FINAL_PATCH, - mixinTargetsCfg, - new File(mixinProject.getBuildDir(), "mixin"), - mixinProject.getTasks().getByName("jar").getOutputs().getFiles()); - val mixinTask = mixinProject.getTasks().create("applyMixins", ApplyMixinsTask.class, applyMixins); - mixinsTask.dependsOn(mixinTask); - - for (val file : mixinTask.getOutputs().getFiles()) { - mixinProject.getArtifacts().add(mixinAppliedCfg.getName(), file, it -> it.builtBy(mixinTask)); - } - - val generatedDependencies = mixinTask.getGeneratedDependenciesForOutputs(project, mixinsTask); - project.getConfigurations().getByName("implementation").getDependencies().addAll(generatedDependencies); - - logger.debug("Mixin subproject {} set up, final dependencies {}", mixinProject.getPath(), generatedDependencies); - }); - } - - public static class Settings { - @Getter - Map> targets = new HashMap<>(); - - public void target(String subproject, List deps) { - targets.put(subproject, deps); - } - } -} diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/deps/TaskOutputSpecificFileCollection.java b/gradle-plugin/src/main/java/org/minimallycorrect/deps/TaskOutputSpecificFileCollection.java deleted file mode 100644 index 179f8d2..0000000 --- a/gradle-plugin/src/main/java/org/minimallycorrect/deps/TaskOutputSpecificFileCollection.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.minimallycorrect.deps; - -import java.io.File; -import java.util.Collections; -import java.util.Set; - -import org.gradle.api.Task; -import org.gradle.api.file.FileCollection; -import org.gradle.api.internal.file.AbstractFileCollection; -import org.gradle.api.internal.tasks.TaskDependencyResolveContext; -import org.jetbrains.annotations.NotNull; - -public class TaskOutputSpecificFileCollection extends AbstractFileCollection implements FileCollection { - Task task; - File file; - - public TaskOutputSpecificFileCollection(Task task, File file) { - this.task = task; - this.file = file; - } - - @NotNull - @Override - public String getDisplayName() { - return "Generated file " + file.getName() + " from " + task + ' ' + task.getDescription(); - } - - @NotNull - @Override - public Set getFiles() { - return Collections.singleton(file); - } - - @Override - public void visitDependencies(TaskDependencyResolveContext context) { - context.add(task); - } -} diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/ApplyMixins.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/ApplyMixins.java similarity index 68% rename from gradle-plugin/src/main/java/org/minimallycorrect/ApplyMixins.java rename to gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/ApplyMixins.java index 175ff0d..555e6ac 100644 --- a/gradle-plugin/src/main/java/org/minimallycorrect/ApplyMixins.java +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/ApplyMixins.java @@ -1,12 +1,14 @@ // ApplyMixins.java -package org.minimallycorrect; +package org.minimallycorrect.mixinplugin; -import static org.minimallycorrect.PomUtils.setPomRootVal; +import static org.minimallycorrect.mixinplugin.Utils.setPomRootVal; import java.io.File; +import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; import lombok.Getter; @@ -24,15 +26,17 @@ import org.gradle.api.tasks.*; import org.gradle.maven.MavenModule; import org.gradle.maven.MavenPomArtifact; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import org.minimallycorrect.deps.MModuleComponentIdentifier; import org.minimallycorrect.mixin.internal.ApplicationType; import org.minimallycorrect.mixin.internal.MixinApplicator; +import org.minimallycorrect.mixinplugin.deps.MModuleComponentIdentifier; +import org.minimallycorrect.mixinplugin.transform.HackyCurrentGradleSource; @Getter -public final class ApplyMixins { +public final class ApplyMixins implements Serializable { + private static final Logger logger = LoggerFactory.getLogger(ApplyMixins.class); @Input @NonNull private final String stage; @@ -41,20 +45,31 @@ public final class ApplyMixins { private final ApplicationType applicationType; @Internal @NonNull - private final Configuration mixinConfiguration; + private final transient Configuration mixinConfiguration; @OutputDirectory @NonNull private final File repo; @Classpath @InputFiles @NonNull - private final FileCollection mixinSource; + private transient FileCollection mixinSource; + @Internal + private transient MixinApplicator applicator; + @Input + @NonNull + private final String mixinProjectPath; - public ApplyMixins(ApplicationType applicationType, Configuration mixinConfiguration, File repo, FileCollection mixinSource) { + @Input + public List getTargetIDs() { + return mixinConfiguration.getDependencies().stream().map(ApplyMixins::getId).collect(Collectors.toList()); + } + + public ApplyMixins(@NonNull ApplicationType applicationType, @NonNull Configuration mixinConfiguration, @NonNull File repo, @NonNull FileCollection mixinSource, @NonNull String mixinProjectPath) { this.applicationType = applicationType; this.mixinConfiguration = mixinConfiguration; this.repo = repo; this.mixinSource = mixinSource; + this.mixinProjectPath = mixinProjectPath; String stage; switch (applicationType) { case PRE_PATCH: @@ -69,11 +84,6 @@ public ApplyMixins(ApplicationType applicationType, Configuration mixinConfigura this.stage = stage; } - @Input - public List getTargetIDs() { - return mixinConfiguration.getDependencies().stream().map(ApplyMixins::getId).collect(Collectors.toList()); - } - @Internal @NonNull public final Map getOutputDependencyFiles() { @@ -84,7 +94,7 @@ public final Map getOutputDependencyFiles() { return result; } - public final void run(@NonNull DependencyHandler dependencyHandler, @Nullable final Logger logger) { + private MixinApplicator makeApplicator() { val applicator = new MixinApplicator(); applicator.setNoMixinIsError(true); applicator.setNotAppliedIsError(true); @@ -94,12 +104,38 @@ public final void run(@NonNull DependencyHandler dependencyHandler, @Nullable fi for (File file : mixinSource.getFiles()) { applicator.addSource(file.toPath()); } + applicator.setApplicationType(applicationType); + return applicator; + } + + public final void run(@NonNull DependencyHandler dependencyHandler) { + remapMixinArtifacts(dependencyHandler); + } + + public void transformArtifact(File input, File output) { + logger.info("Transforming " + input + " to " + output); + badMixinSourceRecreate(); + if (applicator == null) { + applicator = makeApplicator(); + } + applicator.getMixinTransformer().transform(input.toPath(), output.toPath()); + } - remapMixinArtifacts(applicator, dependencyHandler); + private void badMixinSourceRecreate() { + //noinspection ConstantConditions + if (mixinSource == null) { + logger.warn("mixinSource disappeared from ApplyMixins, trying to recreate it."); + mixinSource = HackyCurrentGradleSource.gradleWeakReference.get().getRootProject().findProject(mixinProjectPath).getTasks().getByPath("jar").getOutputs().getFiles(); + } + Objects.requireNonNull(mixinSource, "mixinSource was null. Should not happen, final field initialised at construction."); } @SuppressWarnings("unchecked") - private void remapMixinArtifacts(MixinApplicator applicator, DependencyHandler dependencyHandler) { + private void remapMixinArtifacts(DependencyHandler dependencyHandler) { + if (applicator == null) { + applicator = makeApplicator(); + } + val applicator = this.applicator; val config = mixinConfiguration; config.resolve(); val resolved = config.getResolvedConfiguration(); @@ -109,7 +145,6 @@ private void remapMixinArtifacts(MixinApplicator applicator, DependencyHandler d val id = artifact.getId().getComponentIdentifier(); if (id instanceof ModuleComponentIdentifier) { val mcid = (ModuleComponentIdentifier) id; - applicator.setApplicationType(applicationType); val output = new File(repo, getMavenPath(mcid.getGroup(), mcid.getModule(), mcid.getVersion() + '-' + stage) + ".jar"); output.getParentFile().mkdirs(); applicator.getMixinTransformer().transform(artifact.getFile().toPath(), output.toPath()); @@ -140,7 +175,7 @@ private static ArtifactResolutionQuery artifactResolution(DependencyHandler depe return query; } - private static String getId(Dependency dep) { + public static String getId(Dependency dep) { return dep.getGroup() + ':' + dep.getName() + ':' + dep.getVersion(); } diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/ApplyMixinsTask.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/ApplyMixinsTask.java similarity index 72% rename from gradle-plugin/src/main/java/org/minimallycorrect/ApplyMixinsTask.java rename to gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/ApplyMixinsTask.java index 2c688f4..af3463f 100644 --- a/gradle-plugin/src/main/java/org/minimallycorrect/ApplyMixinsTask.java +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/ApplyMixinsTask.java @@ -1,11 +1,10 @@ -package org.minimallycorrect; +package org.minimallycorrect.mixinplugin; import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; - import lombok.Getter; +import lombok.Setter; import org.gradle.api.DefaultTask; import org.gradle.api.Project; @@ -15,23 +14,19 @@ import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.TaskAction; -import org.minimallycorrect.deps.GeneratedDependency; -import org.minimallycorrect.deps.MModuleComponentIdentifier; +import org.minimallycorrect.mixinplugin.deps.GeneratedDependency; +import org.minimallycorrect.mixinplugin.deps.MModuleComponentIdentifier; @CacheableTask @Getter +@Setter public class ApplyMixinsTask extends DefaultTask { @Nested private ApplyMixins applyMixins; - @Inject - public ApplyMixinsTask(ApplyMixins applyMixins) { - this.applyMixins = applyMixins; - } - @TaskAction public void run() { - applyMixins.run(getProject().getDependencies(), getLogger()); + applyMixins.run(getProject().getDependencies()); } public List getGeneratedDependenciesForOutputs(Project project, Task task) { diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/MixinGradlePlugin.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/MixinGradlePlugin.java new file mode 100644 index 0000000..52e2948 --- /dev/null +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/MixinGradlePlugin.java @@ -0,0 +1,161 @@ +package org.minimallycorrect.mixinplugin; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import lombok.val; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.type.ArtifactTypeDefinition; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginConvention; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.minimallycorrect.mixin.internal.ApplicationType; +import org.minimallycorrect.mixinplugin.deps.DependencyDebugTask; +import org.minimallycorrect.mixinplugin.transform.HackyCurrentGradleSource; +import org.minimallycorrect.mixinplugin.transform.MixinTransform; + +@SuppressWarnings("unused") +public class MixinGradlePlugin implements Plugin { + private Settings settings; + private static final Logger logger = LoggerFactory.getLogger(MixinGradlePlugin.class); + private static final Attribute artifactType = Attribute.of("artifactType", String.class); + private static final Attribute mixined = Attribute.of("mixined", Boolean.class); + + public void apply(@NonNull Project project) { + // TODO: Use artifact transforms when possible (gradle >= 5.5) + // https://docs.gradle.org/current/userguide/artifact_transforms.html#sec:abm_artifact_transforms + settings = new Settings(); + project.getExtensions().add("mixin", settings); + val mixinsTask = project.task("applySubprojectMixins"); + project.getTasks().getByName("compileJava").dependsOn(mixinsTask); + Utils.registerTask(project.getTasks(), "mixinDependencyDebug", DependencyDebugTask.class); + + project.afterEvaluate(this::afterEvaluate); + } + + private void registerTransform(Project project, String type, Map applyMixinsMap) { + applyMixinsMap.put(LocalDate.now().toString(), null); + project.getDependencies().registerTransform(MixinTransform.class, it -> { + it.getFrom().attribute(artifactType, type).attribute(mixined, false); + it.getTo().attribute(artifactType, type).attribute(mixined, true); + + it.parameters(params -> { + params.setPerDependencyApplyMixins(applyMixinsMap); + params.setArtifactType(type); + }); + }); + } + + private void afterEvaluate(Project project) { + HackyCurrentGradleSource.gradleWeakReference = new WeakReference<>(project.getGradle()); + + val useTransforms = settings.canApplyTransforms(); + if (useTransforms) { + project.getDependencies().attributesSchema(it -> it.attribute(mixined)); + project.getDependencies().getArtifactTypes().getByName(ArtifactTypeDefinition.JAR_TYPE).getAttributes().attribute(mixined, false); + project.getConfigurations().all(it -> it.getAttributes().attribute(mixined, true)); + } + + val applyMixinsMap = new HashMap(); + + val mixinsTask = project.getTasks().getByName("applySubprojectMixins"); + val allMixedinCfg = project.getConfigurations().create("mixedin"); + project.getConfigurations().getByName("implementation").extendsFrom(allMixedinCfg); + + settings.targets.forEach((subproject, deps) -> { + val mixinProject = project.project(subproject); + val mixinTargetsCfg = mixinProject.getConfigurations().create("mixinTargets", it -> { + it.setVisible(false); + it.setTransitive(false); + it.getDependencies().addAll(deps); + }); + val mixinPrePatchedCfg = mixinProject.getConfigurations().create("mixinPrePatched"); + val mixinTransitive = mixinProject.getConfigurations().create("mixinTransitive", it -> it.getDependencies().addAll(deps)); + val mixinAppliedCfg = mixinProject.getConfigurations().create("mixinApplied", it -> it.extendsFrom(mixinTransitive)); + mixinProject.getPlugins().apply(JavaPlugin.class); + + val sourceSet = mixinProject.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); + val preApplyMixins = new ApplyMixins( + ApplicationType.PRE_PATCH, + mixinTargetsCfg, + new File(mixinProject.getBuildDir(), "mixin-pre"), + sourceSet.getAllJava().getSourceDirectories(), + mixinProject.getPath()); + val preApplyMixinTask = mixinProject.getTasks().create("preApplyMixins", ApplyMixinsTask.class, it -> it.setApplyMixins(preApplyMixins)); + mixinPrePatchedCfg.getDependencies().addAll(preApplyMixinTask.getGeneratedDependenciesForOutputs(mixinProject, preApplyMixinTask)); + mixinProject.getConfigurations().getByName("implementation").extendsFrom(mixinPrePatchedCfg); + + val applyMixins = new ApplyMixins( + ApplicationType.FINAL_PATCH, + mixinTargetsCfg, + new File(mixinProject.getBuildDir(), "mixin"), + mixinProject.getTasks().getByName("jar").getOutputs().getFiles(), + mixinProject.getPath()); + val mixinTask = mixinProject.getTasks().create("applyMixins", ApplyMixinsTask.class, it -> it.setApplyMixins(applyMixins)); + mixinsTask.dependsOn(mixinTask); + + for (Dependency dep : deps) { + applyMixinsMap.put(ApplyMixins.getId(dep), applyMixins); + } + + for (val file : mixinTask.getOutputs().getFiles()) { + mixinProject.getArtifacts().add(mixinAppliedCfg.getName(), file, it -> it.builtBy(mixinTask)); + } + + if (useTransforms) { + project.getConfigurations().getByName("implementation").getDependencies().addAll(deps); + } else { + val generatedDependencies = mixinTask.getGeneratedDependenciesForOutputs(project, mixinsTask); + project.getConfigurations().getByName("implementation").getDependencies().addAll(generatedDependencies); + + logger.info("Mixin subproject {} set up with generated deps: {}", mixinProject.getPath(), generatedDependencies); + } + }); + + if (useTransforms) { + registerTransform(project, ArtifactTypeDefinition.JAR_TYPE, applyMixinsMap); + registerTransform(project, ArtifactTypeDefinition.JVM_CLASS_DIRECTORY, applyMixinsMap); + // TODO: this is intended to be for source jars but doesn't work? + registerTransform(project, "java", applyMixinsMap); + } + } + + @Getter + public static class Settings { + @Getter + Map> targets = new HashMap<>(); + + @Setter + boolean useArtifactTransforms = true; + + public void target(String subproject, List deps) { + targets.put(subproject, deps); + } + + public boolean canApplyTransforms() { + if (!useArtifactTransforms) { + return false; + } + try { + TransformAction.class.getCanonicalName(); + } catch (NoClassDefFoundError ignored) { + return false; + } + return true; + } + } +} diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/PomUtils.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/Utils.java similarity index 74% rename from gradle-plugin/src/main/java/org/minimallycorrect/PomUtils.java rename to gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/Utils.java index 87702b1..f57da87 100644 --- a/gradle-plugin/src/main/java/org/minimallycorrect/PomUtils.java +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/Utils.java @@ -1,4 +1,4 @@ -package org.minimallycorrect; +package org.minimallycorrect.mixinplugin; import java.io.File; @@ -12,12 +12,25 @@ import lombok.SneakyThrows; import lombok.val; +import org.gradle.api.Task; +import org.gradle.api.tasks.TaskContainer; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -public final class PomUtils { +public final class Utils { + /** + * Wrapper for {@link TaskContainer#register(String, Class)} which falls back to create on older gradle versions + */ + public static void registerTask(TaskContainer taskContainer, String name, Class clazz) { + try { + taskContainer.register(name, clazz); + } catch (NoSuchMethodError ignored) { + taskContainer.create(name, clazz); + } + } + @SneakyThrows public static void setPomRootVal(@NonNull File input, @NonNull File output, @NonNull String tag, @NonNull String value) { output.getParentFile().mkdirs(); diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/deps/DependencyDebugTask.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/DependencyDebugTask.java similarity index 95% rename from gradle-plugin/src/main/java/org/minimallycorrect/deps/DependencyDebugTask.java rename to gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/DependencyDebugTask.java index 829c2b5..7f46194 100644 --- a/gradle-plugin/src/main/java/org/minimallycorrect/deps/DependencyDebugTask.java +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/DependencyDebugTask.java @@ -1,4 +1,4 @@ -package org.minimallycorrect.deps; +package org.minimallycorrect.mixinplugin.deps; import org.gradle.api.DefaultTask; import org.gradle.api.artifacts.Configuration; diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/deps/GeneratedDependency.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/GeneratedDependency.java similarity index 79% rename from gradle-plugin/src/main/java/org/minimallycorrect/deps/GeneratedDependency.java rename to gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/GeneratedDependency.java index 0f8a0eb..2b5e805 100644 --- a/gradle-plugin/src/main/java/org/minimallycorrect/deps/GeneratedDependency.java +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/GeneratedDependency.java @@ -1,4 +1,4 @@ -package org.minimallycorrect.deps; +package org.minimallycorrect.mixinplugin.deps; import java.io.File; @@ -28,7 +28,11 @@ public GeneratedDependency(MModuleComponentIdentifier identifier, FileCollection name = identifier.name; version = identifier.version; this.source = source; - because("Generated from Mixins"); + try { + because("Generated from Mixins"); + } catch (NoSuchMethodError ignored) { + // not supported in gradle 4.2.1 + } } @Override @@ -41,16 +45,6 @@ public static GeneratedDependency makeGeneratedDependency(Project project, Task } static FileCollection taskDependentFileCollection(Project project, Task task, File file) { - /* - TODO: is that even worth having? - try { - return TaskOutputSpecificFileCollection(task, file) - } catch (e: Throwable) { - task.logger.warn("Failed to construct custom task dependent file collection", e) - }*/ - - return project.files(file, it -> { - it.builtBy(task); - }); + return project.files(file, it -> it.builtBy(task)); } } diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/deps/MModuleComponentIdentifier.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/MModuleComponentIdentifier.java similarity index 86% rename from gradle-plugin/src/main/java/org/minimallycorrect/deps/MModuleComponentIdentifier.java rename to gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/MModuleComponentIdentifier.java index 3ba8e56..bb361fe 100644 --- a/gradle-plugin/src/main/java/org/minimallycorrect/deps/MModuleComponentIdentifier.java +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/deps/MModuleComponentIdentifier.java @@ -1,6 +1,7 @@ -package org.minimallycorrect.deps; +package org.minimallycorrect.mixinplugin.deps; import lombok.Data; +import lombok.NonNull; import org.gradle.api.artifacts.ModuleIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; @@ -17,19 +18,23 @@ public MModuleComponentIdentifier(String group, String name, String version) { this.version = version; } + @NonNull @Override public String getModule() { return name; } + @NonNull @Override public ModuleIdentifier getModuleIdentifier() { return new ModuleIdentifier() { + @NonNull @Override public String getGroup() { return group; } + @NonNull @Override public String getName() { return name; @@ -37,6 +42,7 @@ public String getName() { }; } + @NonNull @Override public String getDisplayName() { return group + ':' + name + ':' + version; diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/transform/HackyCurrentGradleSource.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/transform/HackyCurrentGradleSource.java new file mode 100644 index 0000000..b535c6a --- /dev/null +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/transform/HackyCurrentGradleSource.java @@ -0,0 +1,11 @@ +package org.minimallycorrect.mixinplugin.transform; + +import java.lang.ref.WeakReference; + +import org.gradle.api.invocation.Gradle; + +public class HackyCurrentGradleSource { + // TODO this is not workable + // bodge due to issues with ArtifactTransforms (may be better workaround?) + public static WeakReference gradleWeakReference; +} diff --git a/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/transform/MixinTransform.java b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/transform/MixinTransform.java new file mode 100644 index 0000000..3967386 --- /dev/null +++ b/gradle-plugin/src/main/java/org/minimallycorrect/mixinplugin/transform/MixinTransform.java @@ -0,0 +1,103 @@ +package org.minimallycorrect.mixinplugin.transform; + +import java.io.File; +import java.util.Map; + +import lombok.NonNull; +import lombok.val; + +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.file.FileSystemLocation; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.minimallycorrect.mixinplugin.ApplyMixins; + +public abstract class MixinTransform implements TransformAction { + private static final Logger logger = LoggerFactory.getLogger(MixinTransform.class); + + public interface Parameters extends TransformParameters { + @Input + Map getPerDependencyApplyMixins(); + + void setPerDependencyApplyMixins(Map value); + + @Input + String getArtifactType(); + + void setArtifactType(String value); + } + + @InputArtifact + public abstract Provider getInputArtifact(); + + @Override + public void transform(@NonNull TransformOutputs outputs) { + val input = getInputArtifact().get().getAsFile(); + + val id = guessIdentifier(input); + logger.debug("Received " + id + " at " + input); + + val applier = getParameters().getPerDependencyApplyMixins().get(id); + + if (applier == null) { + outputs.file(input); + return; + } + + applier.transformArtifact(input, outputs.file(input.getName())); + } + + // TODO this is awful but we don't get module data any other way? :C + private static String guessIdentifier(File input) { + for (int i = 0; i < 2; i++) { + val result = tryIdentifier(input, i); + if (result != null) { + return result; + } + } + return null; + } + + private static String tryIdentifier(File input, int skips) { + File skipped = input; + for (int i = 0; i < skips; i++) { + skipped = skipped.getParentFile(); + } + val version = skipped.getParentFile(); + val name = version.getParentFile(); + File groupFile = name.getParentFile(); + + if (!input.getName().startsWith(name.getName() + '-' + version.getName())) { + // name + version not as expected, fail + return null; + } + + StringBuilder group = new StringBuilder(groupFile.getName()); + + // special case for mavenLocal() + if (!groupFile.getName().contains(".")) { + int tries = 7; + while (groupFile.exists()) { + groupFile = groupFile.getParentFile(); + + if (groupFile.getName().equals("repository") && groupFile.getParentFile().getName().equals(".m2")) { + break; + } + + group.insert(0, groupFile.getName() + "."); + + if (tries-- <= 0) { + return null; + } + } + } + + return group.append(':').append(name.getName()).append(':').append(version.getName()).toString(); + } +} diff --git a/gradle-plugin/src/test/groovy/org/minimallycorrect/test/BuildLogicFunctionalTest.groovy b/gradle-plugin/src/test/groovy/org/minimallycorrect/test/BuildLogicFunctionalTest.groovy index a04eb12..e864414 100644 --- a/gradle-plugin/src/test/groovy/org/minimallycorrect/test/BuildLogicFunctionalTest.groovy +++ b/gradle-plugin/src/test/groovy/org/minimallycorrect/test/BuildLogicFunctionalTest.groovy @@ -1,19 +1,3 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package org.minimallycorrect.test import io.github.classgraph.ClassGraph @@ -28,9 +12,6 @@ import spock.lang.Unroll import java.nio.file.Files import java.nio.file.StandardCopyOption -import static org.junit.Assert.assertThat -import static org.hamcrest.CoreMatchers.* - import static org.gradle.testkit.runner.TaskOutcome.* class BuildLogicFunctionalTest extends Specification { @@ -68,7 +49,7 @@ class BuildLogicFunctionalTest extends Specification { } result = result .withProjectDir(testProjectDir.root) - .withArguments('dependencies', 'build', '--stacktrace') + .withArguments('clean', 'dependencies', 'build', '--stacktrace', '--info') .withPluginClasspath() .build() diff --git a/gradle-plugin/test-template/build.gradle b/gradle-plugin/test-template/build.gradle index 11da29d..0bb1680 100644 --- a/gradle-plugin/test-template/build.gradle +++ b/gradle-plugin/test-template/build.gradle @@ -13,6 +13,11 @@ allprojects { implementation 'com.google.guava:guava:25.1-jre' implementation includes } + // java plugin extension added after gradle 4.2.1 +// java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +// } } mixin { target("mixins", [dependencies.create('com.google.guava:guava:25.1-jre')]) @@ -21,9 +26,3 @@ test { testLogging.showStandardStreams = true testLogging.exceptionFormat = 'full' } -allprojects { - java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } -} diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/version.properties b/version.properties new file mode 100644 index 0000000..f178717 --- /dev/null +++ b/version.properties @@ -0,0 +1,4 @@ +#Version of the produced binaries. This file is intended to be checked-in. +#It will be automatically bumped by release automation. +version=1.0.1 +previousVersion=1.0-SNAPSHOT