diff --git a/build.gradle b/build.gradle index 5d425d3..8aa1daa 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ dependencies { implementation(libs.jopt.simple) implementation(libs.gson) implementation(libs.guava) - compileOnly(libs.nulls) + implementation(libs.nulls) testImplementation(libs.junit.api) testRuntimeOnly(libs.bundles.junit.runtime) @@ -65,6 +65,107 @@ tasks.named('assemble').configure { dependsOn 'shadowJar' } +def getArtifacts(coord) { + def udep = project.dependencies.create(coord) + def cfg = project.configurations.detachedConfiguration(udep) + cfg.setTransitive(true) + def files = cfg.resolve() + def ret = [ + main: null, + libs: [:] + ] + + cfg.resolvedConfiguration.resolvedArtifacts.each { + def art = [ + group: it.moduleVersion.id.group, + name: it.moduleVersion.id.name, + version: it.moduleVersion.id.version, + classifier: it.classifier, + extension: it.extension, + file: it.file + ] + + def desc = "${art.group}:${art.name}:${art.version}" + if (art.classifier != null) + desc += ":${art.classifier}" + if (art.extension != 'jar') + desc += "@${art.extension}" + + if (coord == desc) { + ret.main = art.file + } else { + ret.libs["${art.group}:${art.name}"] = [ + version: art.version, + name: desc, + file: art.file + ] + } + } + + return ret +} + +tasks.register('testLibrary').configure { + dependsOn 'shadowJar' + doFirst { + def libs = + //['cpw.mods:securejarhandler:2.1.10', 'net.minecraftforge:securemodules:2.2.2'] + ['net.minecraftforge:coremods:5.0.1', 'net.minecraftforge:coremods:5.1.2'] + //['cpw.mods:modlauncher:9.0.24', 'net.minecraftforge:modlauncher:10.1.1'] + def base = getArtifacts(libs[0]) + def input = getArtifacts(libs[1]) + + def cmd = [ + tasks.named('shadowJar').get().archiveFile.get().asFile.absolutePath, + '--quiet', + '--api' + ] + base.libs.each { ga, lib -> + cmd += ['--base-lib', lib.file.absolutePath] + } + input.libs.each { ga, lib -> + cmd += ['--input-lib', lib.file.absolutePath] + } + + logger.lifecycle(libs[0] + ' -> ' + libs[1]) + javaexec { + ignoreExitValue = true + main = '-jar' + args = cmd + [ + '--base-jar', base.main.absolutePath, + '--input-jar', input.main.absolutePath + ] + } + + base.libs.each { ga, lib -> + def ilib = input.libs[ga] + if (ilib == null) { + logger.lifecycle('') + logger.lifecycle("$ga: ${lib.version} -> removed") + } else if (ilib.version != lib.version) { + logger.lifecycle('') + logger.lifecycle("$ga: ${lib.version} -> ${ilib.version}") + javaexec { + ignoreExitValue = true + main = '-jar' + args = cmd + [ + '--base-jar', lib.file.absolutePath, + '--input-jar', ilib.file.absolutePath + ] + } + } + } + input.libs.each { ga, lib -> + if (base.libs[ga] == null) { + logger.lifecycle('') + logger.lifecycle("$ga: missing -> ${lib.version}") + } + } + + logger.lifecycle('') + } +} + tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation } diff --git a/src/main/java/net/minecraftforge/jarcompatibilitychecker/ConsoleTool.java b/src/main/java/net/minecraftforge/jarcompatibilitychecker/ConsoleTool.java index 7a5824b..2618db2 100644 --- a/src/main/java/net/minecraftforge/jarcompatibilitychecker/ConsoleTool.java +++ b/src/main/java/net/minecraftforge/jarcompatibilitychecker/ConsoleTool.java @@ -15,18 +15,20 @@ import java.io.File; import java.util.List; +import java.util.function.Consumer; public class ConsoleTool { public static void main(String[] args) { try { OptionParser parser = new OptionParser(); + OptionSpec quietO = parser.accepts("quiet", "Disabels some debug logging"); OptionSpec apiO = parser.accepts("api", "Enables the API compatibility checking mode"); OptionSpec binaryO = parser.accepts("binary", "Enables the binary compatibility checking mode. This option will override the API compatibility flag. Defaults to true."); OptionSpec baseJarO = parser.accepts("base-jar", "Base JAR file that will be matched against for compatibility").withRequiredArg().ofType(File.class).required(); OptionSpec inputJarO = parser.accepts("input-jar", "JAR file to validate against the base JAR").withRequiredArg().ofType(File.class).required(); OptionSpec libO = parser.acceptsAll(ImmutableList.of("lib", "library"), "Libraries that the base JAR and input JAR both use").withRequiredArg().ofType(File.class); OptionSpec baseLibO = parser.acceptsAll(ImmutableList.of("base-lib", "base-library"), "Libraries that only the base JAR uses").withRequiredArg().ofType(File.class); - OptionSpec concreteLibO = parser.acceptsAll(ImmutableList.of("concrete-lib", "concrete-library"), "Libraries that only the input JAR uses").withRequiredArg().ofType(File.class); + OptionSpec inputLibO = parser.acceptsAll(ImmutableList.of("input-lib", "input-libary", "concrete-lib", "concrete-library"), "Libraries that only the input JAR uses").withRequiredArg().ofType(File.class); OptionSpec annotationCheckModeO = parser.acceptsAll(ImmutableList.of("annotation-check-mode", "ann-mode"), "What mode to use for checking annotations") .withRequiredArg().withValuesConvertedBy(new EnumConverter(AnnotationCheckMode.class) {}); OptionSpec internalAnnotationO = parser.acceptsAll(ImmutableList.of("internal-annotation", "internal-ann"), "The fully resolved classname of an allowed internal API annotation") @@ -51,15 +53,17 @@ public static void main(String[] args) { File inputJar = options.valueOf(inputJarO); List commonLibs = options.valuesOf(libO); List baseLibs = options.valuesOf(baseLibO); - List concreteLibs = options.valuesOf(concreteLibO); + List concreteLibs = options.valuesOf(inputLibO); boolean checkBinary = !options.has(apiO) || options.has(binaryO); AnnotationCheckMode annotationCheckMode = options.valueOf(annotationCheckModeO); List internalAnnotations = options.valuesOf(internalAnnotationO); InternalAnnotationCheckMode internalAnnotationCheckMode = options.valueOf(internalAnnotationCheckModeO); + Consumer dbg = options.has(quietO) ? s -> {} : System.out::println; + // TODO allow logging to a file JarCompatibilityChecker checker = new JarCompatibilityChecker(baseJar, inputJar, checkBinary, annotationCheckMode, internalAnnotations, internalAnnotationCheckMode, - commonLibs, baseLibs, concreteLibs, System.out::println, System.err::println); + commonLibs, baseLibs, concreteLibs, System.out::println, System.err::println, dbg); int incompatibilities = checker.check(); // Clamp to a max of 125 to prevent conflicting with special meaning exit codes - https://tldp.org/LDP/abs/html/exitcodes.html diff --git a/src/main/java/net/minecraftforge/jarcompatibilitychecker/JarCompatibilityChecker.java b/src/main/java/net/minecraftforge/jarcompatibilitychecker/JarCompatibilityChecker.java index f87ab44..bcb4ce7 100644 --- a/src/main/java/net/minecraftforge/jarcompatibilitychecker/JarCompatibilityChecker.java +++ b/src/main/java/net/minecraftforge/jarcompatibilitychecker/JarCompatibilityChecker.java @@ -11,6 +11,7 @@ import net.minecraftforge.jarcompatibilitychecker.core.Incompatibility; import net.minecraftforge.jarcompatibilitychecker.core.InternalAnnotationCheckMode; import net.minecraftforge.jarcompatibilitychecker.data.ClassInfo; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -34,6 +35,7 @@ public class JarCompatibilityChecker { private final List concreteLibs; private final Consumer stdLogger; private final Consumer errLogger; + private final Consumer dbgLogger; /** * Constructs a new JarCompatibilityChecker. @@ -70,6 +72,14 @@ public JarCompatibilityChecker(File baseJar, File inputJar, boolean checkBinary, */ public JarCompatibilityChecker(File baseJar, File inputJar, boolean checkBinary, @Nullable AnnotationCheckMode annotationCheckMode, List internalAnnotations, InternalAnnotationCheckMode internalAnnotationCheckMode, List commonLibs, List baseLibs, List concreteLibs, Consumer stdLogger, Consumer errLogger) { + this(baseJar, inputJar, checkBinary, annotationCheckMode, InternalAnnotationCheckMode.DEFAULT_INTERNAL_ANNOTATIONS, InternalAnnotationCheckMode.DEFAULT_MODE, + commonLibs, baseLibs, concreteLibs, stdLogger, errLogger, stdLogger); + + } + + @ApiStatus.Internal + JarCompatibilityChecker(File baseJar, File inputJar, boolean checkBinary, @Nullable AnnotationCheckMode annotationCheckMode, List internalAnnotations, + InternalAnnotationCheckMode internalAnnotationCheckMode, List commonLibs, List baseLibs, List concreteLibs, Consumer stdLogger, Consumer errLogger, Consumer dbgLogger) { this.baseJar = baseJar; this.inputJar = inputJar; this.checkBinary = checkBinary; @@ -84,6 +94,7 @@ public JarCompatibilityChecker(File baseJar, File inputJar, boolean checkBinary, this.concreteLibs = concreteLibs; this.stdLogger = stdLogger; this.errLogger = errLogger; + this.dbgLogger = dbgLogger; } private void log(String message) { @@ -94,6 +105,10 @@ private void logError(String message) { this.errLogger.accept(message); } + private void logDebug(String message) { + this.dbgLogger.accept(message); + } + /** * Loads the base jar and input jar and compares them for compatibility based on the current mode, API or binary. * Any incompatibilities will be logged to the error logger. @@ -101,20 +116,20 @@ private void logError(String message) { * @return the number of incompatibilities detected based on the current mode */ public int check() throws IOException { - log("Compatibility mode: " + (this.checkBinary ? "Binary" : "API")); - log("Annotation check mode: " + (this.annotationCheckMode == null ? "NONE" : this.annotationCheckMode)); - log("Internal API annotation check mode: " + this.internalAnnotationCheckMode); - log("Internal API annotations: " + this.internalAnnotations); - log("Base JAR: " + this.baseJar.getAbsolutePath()); - log("Input JAR: " + this.inputJar.getAbsolutePath()); + logDebug("Compatibility mode: " + (this.checkBinary ? "Binary" : "API")); + logDebug("Annotation check mode: " + (this.annotationCheckMode == null ? "NONE" : this.annotationCheckMode)); + logDebug("Internal API annotation check mode: " + this.internalAnnotationCheckMode); + logDebug("Internal API annotations: " + this.internalAnnotations); + logDebug("Base JAR: " + this.baseJar.getAbsolutePath()); + logDebug("Input JAR: " + this.inputJar.getAbsolutePath()); for (File baseLib : this.baseLibs) { - log("Base Library: " + baseLib.getAbsolutePath()); + logDebug("Base Library: " + baseLib.getAbsolutePath()); } for (File concreteLib : this.concreteLibs) { - log("Concrete Library: " + concreteLib.getAbsolutePath()); + logDebug("Concrete Library: " + concreteLib.getAbsolutePath()); } for (File commonLib : this.commonLibs) { - log("Common Library: " + commonLib.getAbsolutePath()); + logDebug("Common Library: " + commonLib.getAbsolutePath()); } List baseFiles = new ArrayList<>(this.baseLibs);