Skip to content

Commit 91cedcf

Browse files
Plugin error handling (but this time it works) (#330)
* add Version * add EnigmaPlugin::supportsEnigmaVersion add current version part constants to Enigma extract individual part comparators in Version * check EnigmaPlugin::supportsEnigmaVersion and throw if false * improve error message * add TestConsistentVersions * tweak whitespace * minor tweaks * Integer::toString instead of Integer::parseInt * improve javadoc * remove redundant word from javadoc * javadoc Version * add missing 'the passed' * add copyAndBumpEnigma task * rename copyAndBumpEnigma -> syncBumpAndJarEngima Sync instead of Copy to ensure old output is deleted run jar task in bumped copy * add DummyEnigmaPlugin test input * got runDummyPluginAgainstBumpedEnigma working well enough to tell that the inlined constant approach doesn't work * add EnigmaVersionMarked annotation * make EnigmaVersionMarked annotation work * remove EnigmaVersionMarked remove default EnigmaPlugin::supportsEnigmaVersion implementation * add PreHandlingDummyEnigmaPlugin * update runDummyPluginAgainstBumpedEnigma task -> runRecommendedImplPluginAgainstBumpedEnigma * improve bumped jar filtering * add runPreHandlingPluginAgainstCurrentEnigma task * catch AbstractMethodError when calling supportsEnigmaVersion * check runPreHandlingPluginAgainstCurrentEnigma for expected output and rename -> testPreHandlingPluginAgainstCurrentEnigma * check runRecommendedImplPluginAgainstBumpedEnigma for expected output and rename -> testRecommendedImplPluginAgainstBumpedEnigma * make test depende on dummy plugin tests * exclude enigma-cli build dir from bumped copy * add AnyVersionEnigmaPlugin for test plugins * add reminder to update Enigma's version int constants * rename DummyEnigmaPlugin -> RecommendedImplEnigmaPlugin * add Version methods instead of exposing Comparators * remove unused import * improve comments, rename task * add error messages to TestConsistentVersions * remove commented code * replace getAsFile() with asFile --------- Co-authored-by: ix0rai <[email protected]>
1 parent 8fe952f commit 91cedcf

File tree

20 files changed

+539
-14
lines changed

20 files changed

+539
-14
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ subprojects {
3131
}
3232

3333
group = 'org.quiltmc'
34+
// When bumping version, remember to update the int constants in Enigma!
3435
version = '2.6.2'
3536

3637
var ENV = System.getenv()

enigma/build.gradle

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,43 @@
11
import proguard.gradle.ProGuardTask
22

3+
import java.nio.file.Files
4+
import java.util.regex.Pattern
5+
36
plugins {
47
id 'checkstyle'
58
id 'java-library'
69
id 'java-test-fixtures'
710
}
811

12+
interface ExecProvider {
13+
@javax.inject.Inject
14+
ExecOperations getOperations()
15+
}
16+
final EXEC = objects.newInstance(ExecProvider).operations
17+
18+
final dummyPluginPath = project.file('src/dummyPlugin').toPath()
19+
Closure<SourceSet> configureDummyPluginSourceSet = {
20+
final dir = dummyPluginPath.resolve(it.name);
21+
java {
22+
srcDirs dir.resolve('java')
23+
}
24+
25+
resources {
26+
srcDirs dir.resolve('resources')
27+
}
28+
}
29+
30+
sourceSets {
31+
recommendedImplPlugin configureDummyPluginSourceSet
32+
preHandlingPlugin configureDummyPluginSourceSet
33+
}
34+
35+
configurations {
36+
recommendedImplPluginRuntimeClasspath.extendsFrom runtimeClasspath
37+
recommendedImplPluginCompileClasspath.extendsFrom compileClasspath
38+
// preHandlingPlugin doesn't extend because it has its own EnigmaPlugin interface
39+
}
40+
941
dependencies {
1042
implementation libs.bundles.asm
1143

@@ -20,6 +52,9 @@ dependencies {
2052

2153
testFixturesImplementation libs.asm
2254
testFixturesImplementation libs.asm.tree
55+
56+
recommendedImplPluginImplementation(project)
57+
// preHandlingPlugin doesn't depend on project because it has its own EnigmaPlugin interface
2358
}
2459

2560
// Generate "version.properties" file
@@ -117,6 +152,214 @@ file('src/test/java/org/quiltmc/enigma/input').listFiles().each { f ->
117152
}
118153
}
119154

155+
final dummyPluginsDest = project.layout.buildDirectory.dir('dummy-plugin')
156+
final recommendedImplPluginJar = tasks.register('recommendedImplPluginJar', Jar) {
157+
from sourceSets.recommendedImplPlugin.output
158+
159+
archiveFileName = "recommended-impl-plugin.jar"
160+
destinationDirectory = dummyPluginsDest
161+
}
162+
163+
final pluginJarTask = tasks.register('preHandlingPluginJar', Jar) {
164+
from sourceSets.preHandlingPlugin.output
165+
166+
archiveFileName = "pre-handling-plugin.jar"
167+
destinationDirectory = dummyPluginsDest
168+
}
169+
170+
final testPreHandlingPluginAgainstCurrentEnigma = tasks
171+
.register('testPreHandlingPluginAgainstCurrentEnigma', JavaExec) { task ->
172+
final projectVersion = project.version
173+
174+
inputs.property('projectVersion', projectVersion)
175+
176+
final cliShadowJarTask = project(':enigma-cli').tasks.named('shadowJar', Jar)
177+
dependsOn(cliShadowJarTask, pluginJarTask)
178+
179+
final pluginJarFile = pluginJarTask.flatMap(Jar::getArchiveFile)
180+
final cliShadowJar = cliShadowJarTask.flatMap(Jar::getArchiveFile)
181+
182+
inputs.files(cliShadowJar, pluginJarFile)
183+
184+
mainClass = 'org.quiltmc.enigma.command.Main'
185+
186+
classpath(cliShadowJar, pluginJarFile)
187+
188+
errorOutput new ByteArrayOutputStream()
189+
190+
ignoreExitValue true
191+
192+
doFirst {
193+
args 'print-stats', pluginJarFile.get().asFile, pluginJarFile.get().asFile
194+
}
195+
196+
doLast {
197+
final err = errorOutput.toString()
198+
if (!err.contains('Plugin was probably built using a pre-2.7 version of Enigma.')) {
199+
throw new GradleException(
200+
"""
201+
PreHandlingPlugin did not fail as expected!
202+
Error output:
203+
${err}
204+
""".stripIndent()
205+
)
206+
}
207+
}
208+
}
209+
210+
// copying, bumping, and jar-ing must all be one task because they all output to bumped-enigma-copy
211+
final copyBumpAndJarEnigma = tasks.register("copyBumpAndJarEnigma", Sync) {
212+
inputs.property('projectVersion', project.version)
213+
214+
final destPath = 'bumped-enigma-copy'
215+
216+
from project.rootDir
217+
into project.layout.buildDirectory.map { it.dir(destPath) }
218+
219+
final excludedProjectDirs = [ project(':enigma-server'), project(':enigma-swing') ]
220+
.collect { it.projectDir }
221+
222+
final excludedPaths = [
223+
'.git', '.github', '.gradle', '.idea', 'gradlew', 'gradlew.bat', 'gradle/wrapper',
224+
'buildSrc/.gradle', 'buildSrc/build',
225+
project.projectDir.toPath().resolve('src/dummyPlugin'),
226+
project(':enigma-cli').projectDir.toPath().resolve('build')
227+
].collect { rootProject.file(it) }
228+
229+
final Set<File> excluded = excludedProjectDirs + excludedPaths
230+
231+
exclude {
232+
it.file in excluded
233+
}
234+
235+
// This can't be done lazily in doFirst because it needs to be excluded when task dependencies are checked
236+
// so the task doesn't depend on processDummyPluginResources.
237+
exclude {
238+
it.file in project.layout.buildDirectory.get().asFile
239+
}
240+
241+
// already excluded whole build dir, but just in case: explicitly exclude dest dir to avoid copy recursion
242+
exclude {
243+
it.file in destinationDir
244+
}
245+
246+
doFirst {
247+
// prevent accidental task recursion
248+
if (source.getElements().get().any { it.asFile.toPath().iterator().any { it.toString() == destPath } }) {
249+
throw new GradleException("${name} cannot be run from its own copy!")
250+
}
251+
}
252+
253+
final projectRootDir = project.rootDir
254+
doLast {
255+
final dest = destinationDir.toPath()
256+
257+
// only bump minor version, don't bother resetting patch to 0
258+
bumpCopyVersion(dest.resolve('build.gradle'), 'version', ~/(?m)^\s*version = '\d+.(\d+).\d+(?:\+.*)?'$/)
259+
bumpCopyVersion(
260+
dest.resolve('enigma/src/main/java/org/quiltmc/enigma/api/Enigma.java'),
261+
'MINOR_VERSION', ~/(?m)^\s*public static final int MINOR_VERSION = (\d+);$/
262+
)
263+
264+
// this causes the task to always be out-of-date
265+
// copying gradle/wrapper, etc. and executing the copy instead did not help
266+
EXEC.exec {
267+
workingDir(destinationDir)
268+
269+
final executableName = System.properties['os.name'].toLowerCase().contains('windows')
270+
? './gradlew.bat' : './gradlew'
271+
executable(projectRootDir.toPath().resolve(executableName))
272+
args ':enigma-cli:shadowJar'
273+
}
274+
}
275+
}
276+
277+
static void bumpCopyVersion(java.nio.file.Path path, String variableName, Pattern pattern) {
278+
final matcher = pattern.matcher(path.text)
279+
if (matcher.find()) {
280+
final firstStart = matcher.start()
281+
282+
final number = Integer.parseInt(matcher.group(1))
283+
284+
final numberStart = matcher.start(1)
285+
final numberEnd = matcher.end(1)
286+
287+
if (matcher.find()) {
288+
throw new GradleException(
289+
"Found multiple occurences of ${variableName} in ${path.last()} at ${firstStart} and ${matcher.start()}!"
290+
)
291+
}
292+
293+
path.text = path.text.substring(0, numberStart) + (number + 1) + path.text.substring(numberEnd)
294+
} else {
295+
throw new GradleException("Failed to find ${variableName} in copy's ${path.last()}!")
296+
}
297+
}
298+
299+
final testRecommendedImplPluginAgainstBumpedEnigma = tasks
300+
.register('testRecommendedImplPluginAgainstBumpedEnigma', JavaExec) {
301+
final projectVersion = project.version
302+
303+
inputs.property('projectVersion', projectVersion)
304+
305+
final projectDir = project.projectDir
306+
final libsDir = project.layout.buildDirectory.dir('libs')
307+
final Provider<java.nio.file.Path> bumpedJarPath = copyBumpAndJarEnigma
308+
.map(Sync::getDestinationDir)
309+
.zip(libsDir) { bumpDest, libs ->
310+
final relativeBuildLibs = projectDir.toPath().relativize(libs.asFile.toPath())
311+
final copyBuildLibs = bumpDest.toPath().resolve('enigma-cli').resolve(relativeBuildLibs)
312+
313+
final bumpedJars = Files
314+
.find(copyBuildLibs, 1) { path, attributes ->
315+
!path.fileName.toString().endsWith("${projectVersion}-all.jar")
316+
&& path.fileName.toString().endsWith('-all.jar')
317+
}
318+
.toList()
319+
320+
if (bumpedJars.isEmpty()) {
321+
throw new GradleException('No bumped Enigma jar to run!')
322+
} else if (bumpedJars.size() > 1) {
323+
throw new GradleException('Multiple bumped Enigma jars found!')
324+
} else {
325+
return bumpedJars.first()
326+
}
327+
}
328+
329+
dependsOn(copyBumpAndJarEnigma, recommendedImplPluginJar)
330+
331+
final pluginJarFile = recommendedImplPluginJar.flatMap(Jar::getArchiveFile)
332+
333+
inputs.files(bumpedJarPath, pluginJarFile)
334+
335+
mainClass = 'org.quiltmc.enigma.command.Main'
336+
337+
classpath(bumpedJarPath, pluginJarFile)
338+
339+
errorOutput new ByteArrayOutputStream()
340+
341+
ignoreExitValue true
342+
343+
doFirst {
344+
args 'print-stats', pluginJarFile.get().asFile, pluginJarFile.get().asFile
345+
}
346+
347+
doLast {
348+
final err = errorOutput.toString()
349+
if (!err.contains('Plugin does not support Enigma ')) {
350+
throw new GradleException(
351+
"""
352+
RecommendedImplPlugin did not fail as expected!
353+
Error output:
354+
${err}
355+
""".stripIndent()
356+
)
357+
}
358+
}
359+
}
360+
361+
test.dependsOn(testPreHandlingPluginAgainstCurrentEnigma, testRecommendedImplPluginAgainstBumpedEnigma)
362+
120363
publishing {
121364
publications {
122365
"$project.name"(MavenPublication) {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.quiltmc.enigma.api;
2+
3+
/**
4+
* Copy of Enigma 2.6.2's EnigmaPlugin
5+
*/
6+
public interface EnigmaPlugin {
7+
void init(EnigmaPluginContext ctx);
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.quiltmc.enigma.api;
2+
3+
/**
4+
* Copy of Enigma 2.6.2's EnigmaPluginContext with contents removed.
5+
*/
6+
public interface EnigmaPluginContext {
7+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.quiltmc.internal.dummy_plugin;
2+
3+
import org.quiltmc.enigma.api.EnigmaPluginContext;
4+
import org.quiltmc.enigma.api.EnigmaPlugin;
5+
6+
/**
7+
* A dummy plugin to be compiled against a (copied) pre-error-handling version of Enigma and run against
8+
* a post-error-handling version.
9+
*/
10+
public class PreHandlingDummyEnigmaPlugin implements EnigmaPlugin {
11+
@Override
12+
public void init(EnigmaPluginContext ctx) {
13+
throw new UnsupportedOperationException(PreHandlingDummyEnigmaPlugin.class.getSimpleName() + " initialized!");
14+
}
15+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.quiltmc.internal.dummy_plugin.PreHandlingDummyEnigmaPlugin
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.quiltmc.internal.dummy_plugin;
2+
3+
import org.quiltmc.enigma.api.Enigma;
4+
import org.quiltmc.enigma.api.EnigmaPlugin;
5+
import org.quiltmc.enigma.api.EnigmaPluginContext;
6+
import org.quiltmc.enigma.util.Version;
7+
8+
import javax.annotation.Nonnull;
9+
10+
/**
11+
* A dummy plugin to be compiled against the current Enigma version and run against the copied+bumped Enigma version
12+
* generated by the build script to test the recommended {@link EnigmaPlugin#supportsEnigmaVersion(Version)}
13+
* implementation.
14+
*/
15+
public class RecommendedImplEnigmaPlugin implements EnigmaPlugin {
16+
public static final String NAME = "Recommended Implementation Enigma Plugin";
17+
18+
@Override
19+
public void init(EnigmaPluginContext ctx) {
20+
throw new UnsupportedOperationException(NAME + " initialized!");
21+
}
22+
23+
@Override
24+
public boolean supportsEnigmaVersion(@Nonnull Version enigmaVersion) {
25+
return Enigma.MAJOR_VERSION == enigmaVersion.major()
26+
&& Enigma.MINOR_VERSION == enigmaVersion.minor();
27+
}
28+
29+
@Override
30+
public String getName() {
31+
return NAME;
32+
}
33+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.quiltmc.internal.dummy_plugin.RecommendedImplEnigmaPlugin

0 commit comments

Comments
 (0)