From c14a972ab8bf026b423525fc3cb991b7b8e65ca6 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sun, 13 Sep 2026 17:05:05 -0400 Subject: [PATCH] Fixed Maven publishing - ItemsAdder API resolves from Maven Central - Integration repositories only resolve their assigned groups and modules - Shared VolmLib classes and packaging use the same pinned revision - JitPack builds and publishes with remote dependencies - Local Maven publication uses art.arcane:iris with the configured version - API generation tracks the final archive filename lazily - API jars retain signatures and metadata with valid throwing method stubs - Publication tests verify metadata, bytecode, and consumer compilation --- adapters/fabric/build.gradle | 6 +- adapters/forge/build.gradle | 6 +- adapters/neoforge/build.gradle | 6 +- build.gradle | 58 ++++- buildSrc/build.gradle | 4 + buildSrc/src/main/java/ApiGenerator.java | 73 +++---- buildSrc/src/test/java/ApiGeneratorTest.java | 215 +++++++++++++++++++ gradle.properties | 2 +- gradle/libs.versions.toml | 4 +- jitpack.yml | 4 + 10 files changed, 322 insertions(+), 56 deletions(-) create mode 100644 buildSrc/src/test/java/ApiGeneratorTest.java create mode 100644 jitpack.yml diff --git a/adapters/fabric/build.gradle b/adapters/fabric/build.gradle index 60dddbdda..e3fd0c27a 100644 --- a/adapters/fabric/build.gradle +++ b/adapters/fabric/build.gradle @@ -32,7 +32,11 @@ buildscript { maven { url = uri('https://jitpack.io') } } dependencies { - classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT') + Properties libraryProperties = new Properties() + file('../../gradle.properties').withInputStream { InputStream stream -> libraryProperties.load(stream) } + String libraryCoordinate = providers.gradleProperty('volmLibCoordinate') + .getOrElse(libraryProperties.getProperty('volmLibCoordinate')) + classpath("com.github.VolmitSoftware.VolmLib:packaging:${libraryCoordinate.tokenize(':').last()}") } } diff --git a/adapters/forge/build.gradle b/adapters/forge/build.gradle index 3f9ee942a..3b6e34538 100644 --- a/adapters/forge/build.gradle +++ b/adapters/forge/build.gradle @@ -32,7 +32,11 @@ buildscript { maven { url = uri('https://jitpack.io') } } dependencies { - classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT') + Properties libraryProperties = new Properties() + file('../../gradle.properties').withInputStream { InputStream stream -> libraryProperties.load(stream) } + String libraryCoordinate = providers.gradleProperty('volmLibCoordinate') + .getOrElse(libraryProperties.getProperty('volmLibCoordinate')) + classpath("com.github.VolmitSoftware.VolmLib:packaging:${libraryCoordinate.tokenize(':').last()}") } } diff --git a/adapters/neoforge/build.gradle b/adapters/neoforge/build.gradle index 68abae4a0..8f80fe229 100644 --- a/adapters/neoforge/build.gradle +++ b/adapters/neoforge/build.gradle @@ -32,7 +32,11 @@ buildscript { maven { url = uri('https://jitpack.io') } } dependencies { - classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT') + Properties libraryProperties = new Properties() + file('../../gradle.properties').withInputStream { InputStream stream -> libraryProperties.load(stream) } + String libraryCoordinate = providers.gradleProperty('volmLibCoordinate') + .getOrElse(libraryProperties.getProperty('volmLibCoordinate')) + classpath("com.github.VolmitSoftware.VolmLib:packaging:${libraryCoordinate.tokenize(':').last()}") } } diff --git a/build.gradle b/build.gradle index 0e6c3ada1..e25d665fd 100644 --- a/build.gradle +++ b/build.gradle @@ -46,7 +46,8 @@ buildscript { maven { url = uri('https://jitpack.io') } } dependencies { - classpath('com.github.VolmitSoftware.VolmLib:packaging:master-SNAPSHOT') + String volmLibRevision = providers.gradleProperty('volmLibCoordinate').get().tokenize(':').last() + classpath("com.github.VolmitSoftware.VolmLib:packaging:${volmLibRevision}") } } @@ -750,16 +751,51 @@ allprojects { maven { url = uri('https://repo.papermc.io/repository/maven-public/') } maven { url = uri('https://repo.codemc.org/repository/maven-public/') } - maven { url = uri('https://jitpack.io') } // score - maven { url = uri('https://repo.auxilor.io/repository/maven-releases/') } // eco - maven { url = uri('https://repo.nexomc.com/releases/') } // nexo - maven { url = uri('https://repo.oraxen.com/releases/') } - maven { url = uri('https://maven.devs.beer/') } // itemsadder - maven { url = uri('https://repo.extendedclip.com/releases/') } // placeholderapi - maven { url = uri('https://mvn.lumine.io/repository/maven-public/') } // mythic - maven { url = uri('https://nexus.phoenixdevt.fr/repository/maven-public/') } //MMOItems - maven { url = uri('https://repo.onarandombox.com/content/groups/public/') } //Multiverse Core - maven { url = uri('https://repo.momirealms.net/releases/') } // CraftEngine + maven { + url = uri('https://jitpack.io') + content { includeGroupByRegex('com\\.github\\..*') } + } // score + maven { + url = uri('https://repo.auxilor.io/repository/maven-releases/') + content { includeGroupByRegex('com\\.willfp(\\..*)?') } + } // eco + maven { + url = uri('https://repo.nexomc.com/releases/') + content { + includeGroup('com.nexomc') + includeGroup('team.unnamed') + } + } // nexo + maven { + url = uri('https://repo.oraxen.com/releases/') + content { includeGroup('io.th0rgal') } + } + maven { + url = uri('https://repo.extendedclip.com/releases/') + content { includeGroup('me.clip') } + } // placeholderapi + maven { + url = uri('https://mvn.lumine.io/repository/maven-public/') + content { + includeModule('io.lumine', 'Mythic-Dist') + includeModule('io.lumine', 'MythicCrucible-Dist') + } + } // mythic + maven { + url = uri('https://nexus.phoenixdevt.fr/repository/maven-public/') + content { + includeGroup('net.Indyuce') + includeModule('io.lumine', 'MythicLib-dist') + } + } //MMOItems + maven { + url = uri('https://repo.onarandombox.com/content/groups/public/') + content { includeGroupByRegex('org\\.mvplugins\\.multiverse(\\..*)?') } + } //Multiverse Core + maven { + url = uri('https://repo.momirealms.net/releases/') + content { includeGroup('net.momirealms') } + } // CraftEngine } /** diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index 0270c1deb..4f9835e62 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -24,6 +24,10 @@ tasks.named('jar').configure { dependsOn(tasks.named('test')) } +tasks.named('test').configure { + jvmArgs('--add-opens=java.base/java.lang=ALL-UNNAMED') +} + dependencies { implementation(gradleApi()) implementation('org.ow2.asm:asm:9.10.1') diff --git a/buildSrc/src/main/java/ApiGenerator.java b/buildSrc/src/main/java/ApiGenerator.java index 4a90df4e7..2c6b48b32 100644 --- a/buildSrc/src/main/java/ApiGenerator.java +++ b/buildSrc/src/main/java/ApiGenerator.java @@ -2,6 +2,7 @@ import org.gradle.api.DefaultTask; import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.publish.PublishingExtension; import org.gradle.api.publish.maven.MavenPublication; import org.gradle.api.tasks.InputFile; @@ -19,6 +20,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; @@ -40,7 +43,8 @@ public class ApiGenerator implements Plugin { }); publishing.getPublications().create("maven", MavenPublication.class, publication -> { - publication.setGroupId(target.getName()); + publication.setGroupId(target.getGroup().toString()); + publication.setArtifactId("iris"); publication.setVersion(target.getVersion().toString()); publication.artifact(task); }); @@ -56,69 +60,60 @@ public class ApiGenerator implements Plugin { } abstract class GenerateApiTask extends DefaultTask { - private final File inputFile; - private final File outputFile; - public GenerateApiTask() { setGroup("iris"); dependsOn("jar"); finalizedBy("publishMavenPublicationToDeployDirRepository"); - doLast(task -> getLogger().lifecycle("The API is located at " + getOutputFile().getAbsolutePath())); + doLast(task -> getLogger().lifecycle("The API is located at " + getOutputFile().get().getAsFile().getAbsolutePath())); TaskProvider jarTask = getProject().getTasks().named("jar", Jar.class); - this.inputFile = jarTask.get().getArchiveFile().get().getAsFile(); - this.outputFile = ApiGenerator.targetDirectory(getProject()).toPath().resolve(this.inputFile.getName()).toFile(); + getInputFile().convention(jarTask.flatMap(Jar::getArchiveFile)); + getOutputFile().convention(getProject().getLayout().file(getInputFile().getLocationOnly().map(input -> + new File(ApiGenerator.targetDirectory(getProject()), input.getAsFile().getName())))); } @InputFile - public File getInputFile() { - return inputFile; - } + public abstract RegularFileProperty getInputFile(); @OutputFile - public File getOutputFile() { - return outputFile; - } + public abstract RegularFileProperty getOutputFile(); @TaskAction public void generate() throws IOException { + File inputFile = getInputFile().get().getAsFile(); + File outputFile = getOutputFile().get().getAsFile(); File parent = outputFile.getParentFile(); if (parent != null) { - parent.mkdirs(); + Files.createDirectories(parent.toPath()); } try (JarFile jar = new JarFile(inputFile); JarOutputStream out = new JarOutputStream(new FileOutputStream(outputFile))) { - jar.stream() - .parallel() - .filter(entry -> !entry.isDirectory()) - .filter(entry -> entry.getName().endsWith(".class")) - .forEach(entry -> writeStrippedClass(jar, out, entry)); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (!entry.isDirectory() && entry.getName().endsWith(".class")) { + writeStrippedClass(jar, out, entry); + } + } } } - private static void writeStrippedClass(JarFile jar, JarOutputStream out, JarEntry entry) { + private static void writeStrippedClass(JarFile jar, JarOutputStream out, JarEntry entry) throws IOException { byte[] bytes; try (InputStream input = jar.getInputStream(entry)) { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor visitor = new MethodClearingVisitor(writer); ClassReader reader = new ClassReader(input); - reader.accept(visitor, 0); + reader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); bytes = writer.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); } - synchronized (out) { - try { - JarEntry outputEntry = new JarEntry(entry.getName()); - out.putNextEntry(outputEntry); - out.write(bytes); - out.closeEntry(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } + JarEntry outputEntry = new JarEntry(entry.getName()); + outputEntry.setTime(0L); + out.putNextEntry(outputEntry); + out.write(bytes); + out.closeEntry(); } } @@ -129,7 +124,11 @@ class MethodClearingVisitor extends ClassVisitor { @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - return new ExceptionThrowingMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions)); + MethodVisitor method = super.visitMethod(access, name, descriptor, signature, exceptions); + if (method == null || (access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != 0) { + return method; + } + return new ExceptionThrowingMethodVisitor(method); } } @@ -139,11 +138,7 @@ class ExceptionThrowingMethodVisitor extends MethodVisitor { } @Override - public void visitCode() { - if (mv == null) { - return; - } - + public void visitEnd() { mv.visitCode(); mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); mv.visitInsn(Opcodes.DUP); diff --git a/buildSrc/src/test/java/ApiGeneratorTest.java b/buildSrc/src/test/java/ApiGeneratorTest.java new file mode 100644 index 000000000..e3ab0b0b3 --- /dev/null +++ b/buildSrc/src/test/java/ApiGeneratorTest.java @@ -0,0 +1,215 @@ +import org.gradle.api.Project; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.publish.maven.tasks.GenerateMavenPom; +import org.gradle.jvm.tasks.Jar; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.w3c.dom.Document; + +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.stream.Stream; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +public class ApiGeneratorTest { + @Rule + public final TemporaryFolder temporary = new TemporaryFolder(); + + @Test + public void publishesCanonicalCoordinatesAndTracksConfiguredJar() throws Exception { + Project project = project(); + GenerateApiTask api = project.getTasks().named("irisApi", GenerateApiTask.class).get(); + Jar jar = project.getTasks().named("jar", Jar.class).get(); + jar.getArchiveFileName().set("Iris v4.1.0-26.2 [CraftBukkit] 26.1.2-26.2.jar"); + + assertEquals(jar.getArchiveFile().get().getAsFile(), api.getInputFile().get().getAsFile()); + assertEquals(jar.getArchiveFileName().get(), api.getOutputFile().get().getAsFile().getName()); + assertEquals(ApiGenerator.targetDirectory(project), api.getOutputFile().get().getAsFile().getParentFile()); + + PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); + MavenPublication publication = (MavenPublication) publishing.getPublications().getByName("maven"); + assertEquals("art.arcane", publication.getGroupId()); + assertEquals("iris", publication.getArtifactId()); + assertEquals("4.1.0-26.2", publication.getVersion()); + assertEquals(1, publication.getArtifacts().size()); + assertEquals(api.getOutputFile().get().getAsFile(), publication.getArtifacts().iterator().next().getFile()); + + GenerateMavenPom pom = project.getTasks().named("generatePomFileForMavenPublication", GenerateMavenPom.class).get(); + File destination = temporary.newFile("iris.pom"); + pom.setDestination(destination); + pom.doGenerate(); + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(destination); + assertEquals("art.arcane", document.getElementsByTagName("groupId").item(0).getTextContent()); + assertEquals("iris", document.getElementsByTagName("artifactId").item(0).getTextContent()); + assertEquals("4.1.0-26.2", document.getElementsByTagName("version").item(0).getTextContent()); + assertEquals(0, document.getElementsByTagName("dependency").getLength()); + } + + @Test + public void generatesVerifiableStubsThatCompileConsumersWithSharedSignatures() throws Exception { + Project project = project(); + Path sources = temporary.newFolder("sources").toPath(); + Path classes = temporary.newFolder("classes").toPath(); + Path apiSource = sources.resolve("example/SampleApi.java"); + Path sharedSource = sources.resolve("example/SharedValue.java"); + Files.createDirectories(apiSource.getParent()); + Files.writeString(sharedSource, """ + package example; + public record SharedValue(int value) {} + """); + Files.writeString(apiSource, """ + package example; + import java.io.IOException; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.util.List; + public abstract class SampleApi { + public static final int CONSTANT = 42; + public SampleApi() {} + public abstract T value(); + public native int nativeValue(); + @Contract + public static List values(@Contract String input) throws IOException { + try { + if (input.isEmpty()) { + throw new IllegalArgumentException("implementation-only"); + } + return List.of(new SharedValue(input.length())); + } catch (IllegalArgumentException exception) { + throw new IOException(exception); + } + } + public interface Query { + int count(); + default int sample() { return count() + 1; } + } + public enum Mode { ONE, TWO } + @Retention(RetentionPolicy.RUNTIME) + public @interface Contract { String value() default "api"; } + } + """); + compile(List.of(apiSource, sharedSource), classes, List.of()); + + GenerateApiTask api = project.getTasks().named("irisApi", GenerateApiTask.class).get(); + Path input = project.getLayout().getBuildDirectory().file("plugin.jar").get().getAsFile().toPath(); + Files.createDirectories(input.getParent()); + createJar(classes, input); + api.getInputFile().set(input.toFile()); + api.getOutputFile().set(project.getLayout().getBuildDirectory().file("api.jar")); + api.generate(); + Path output = api.getOutputFile().get().getAsFile().toPath(); + + try (JarFile jar = new JarFile(output.toFile())) { + assertNotNull(jar.getEntry("example/SampleApi.class")); + assertNotNull(jar.getEntry("example/SharedValue.class")); + assertNull(jar.getEntry("plugin.yml")); + byte[] bytes = jar.getInputStream(jar.getEntry("example/SampleApi.class")).readAllBytes(); + assertFalse(ClassReferences.read(bytes).contains("java/lang/IllegalArgumentException")); + } + + try (URLClassLoader loader = new URLClassLoader(new URL[]{output.toUri().toURL()}, null)) { + Class sample = loader.loadClass("example.SampleApi"); + Method values = sample.getDeclaredMethod("values", String.class); + assertEquals("input", values.getParameters()[0].getName()); + assertEquals(1, values.getParameterAnnotations()[0].length); + assertEquals(1, values.getAnnotations().length); + assertEquals("java.util.List", values.getGenericReturnType().getTypeName()); + assertEquals(IOException.class, values.getExceptionTypes()[0]); + assertTrue(Modifier.isAbstract(sample.getDeclaredMethod("value").getModifiers())); + assertTrue(Modifier.isNative(sample.getDeclaredMethod("nativeValue").getModifiers())); + InvocationTargetException invocation = assertThrows(InvocationTargetException.class, + () -> values.invoke(null, "world")); + assertEquals(IllegalStateException.class, invocation.getCause().getClass()); + assertEquals("Only API", invocation.getCause().getMessage()); + + Class shared = loader.loadClass("example.SharedValue"); + assertTrue(shared.isRecord()); + InvocationTargetException constructor = assertThrows(InvocationTargetException.class, + () -> shared.getDeclaredConstructor(int.class).newInstance(3)); + assertEquals(IllegalStateException.class, constructor.getCause().getClass()); + assertTrue(loader.loadClass("example.SampleApi$Query").getDeclaredMethod("sample").isDefault()); + loader.loadClass("example.SampleApi$Mode").getDeclaredMethods(); + assertEquals("api", loader.loadClass("example.SampleApi$Contract").getDeclaredMethod("value").getDefaultValue()); + } + + Path consumer = sources.resolve("Consumer.java"); + Files.writeString(consumer, """ + import example.SampleApi; + import example.SharedValue; + import java.io.IOException; + import java.util.List; + public class Consumer extends SampleApi implements SampleApi.Query { + public Integer value() { return SampleApi.CONSTANT; } + public int count() { return 1; } + public List query() throws IOException { return SampleApi.values("world"); } + public int sampleDefault() { return sample(); } + public SampleApi.Mode mode() { return SampleApi.Mode.ONE; } + public SharedValue point() { return new SharedValue(3); } + } + """); + compile(List.of(consumer), temporary.newFolder("consumer").toPath(), List.of("-classpath", output.toString())); + } + + private Project project() throws IOException { + Project project = ProjectBuilder.builder().withName("Iris").withProjectDir(temporary.newFolder()).build(); + project.setGroup("art.arcane"); + project.setVersion("4.1.0-26.2"); + project.getPlugins().apply("java"); + project.getPlugins().apply(ApiGenerator.class); + return project; + } + + private static void compile(List sources, Path output, List extraOptions) throws IOException { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + assertNotNull("A JDK is required to verify API consumers", compiler); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + try (StandardJavaFileManager files = compiler.getStandardFileManager(diagnostics, null, null)) { + List options = Stream.concat( + List.of("--release", "25", "-parameters", "-proc:none", "-d", output.toString()).stream(), + extraOptions.stream()).toList(); + boolean success = compiler.getTask(null, files, diagnostics, options, null, + files.getJavaFileObjectsFromPaths(sources)).call(); + assertTrue(diagnostics.getDiagnostics().toString(), success); + } + } + + private static void createJar(Path classes, Path destination) throws IOException { + try (JarOutputStream output = new JarOutputStream(Files.newOutputStream(destination)); + Stream files = Files.walk(classes)) { + for (Path file : files.filter(Files::isRegularFile).sorted().toList()) { + output.putNextEntry(new JarEntry(classes.relativize(file).toString().replace(File.separatorChar, '/'))); + Files.copy(file, output); + output.closeEntry(); + } + output.putNextEntry(new JarEntry("plugin.yml")); + output.write("name: Iris".getBytes()); + output.closeEntry(); + } + } +} diff --git a/gradle.properties b/gradle.properties index 0138f76d6..656b7c9a4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,4 +30,4 @@ apiVersion=26.1 fabricLoaderVersion=0.19.3 forgeVersion=26.2-65.1.1 neoForgeVersion=26.2.0.59 -volmLibCoordinate=com.github.VolmitSoftware:VolmLib:master-SNAPSHOT +volmLibCoordinate=com.github.VolmitSoftware.VolmLib:volmlib-shared:6c54bbcd19282508364794df1eacb9720d94a896 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe1a63e05..79cfae060 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -36,7 +36,7 @@ jaxen = "2.0.6" # https://central.sonatype.com/artifact/jaxen/jaxen # Third Party Integrations nexo = "1.25.0" # https://repo.nexomc.com/#/releases/com/nexomc/nexo oraxen = "1.218.0" -itemsadder = "4.0.10" # https://github.com/LoneDev6/API-ItemsAdder +itemsadder = "4.0.17" # https://github.com/LoneDev6/API-ItemsAdder placeholderApi = "2.11.6" # https://repo.extendedclip.com/#/releases/me/clip/placeholderapi score = "5.25.3.9" # https://github.com/Ssomar-Developement/SCore mmoitems = "6.10.1-SNAPSHOT" # https://nexus.phoenixdevt.fr/repository/maven-public/net/Indyuce/MMOItems-API/maven-metadata.xml @@ -97,7 +97,7 @@ jaxen = { module = "jaxen:jaxen", version.ref = "jaxen" } # Third Party Integrations nexo = { module = "com.nexomc:nexo", version.ref = "nexo" } oraxen = { module = "io.th0rgal:oraxen", version.ref = "oraxen" } -itemsadder = { module = "dev.lone:api-itemsadder", version.ref = "itemsadder" } +itemsadder = { module = "beer.devs:itemsadder-api", version.ref = "itemsadder" } placeholderApi = { module = "me.clip:placeholderapi", version.ref = "placeholderApi" } score = { module = "com.github.Ssomar-Developement:SCore", version.ref = "score" } mmoitems = { module = "net.Indyuce:MMOItems-API", version.ref = "mmoitems" } diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 000000000..8b9002186 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,4 @@ +jdk: + - openjdk25 +install: + - ./gradlew --no-daemon build publishToMavenLocal -PuseLocalVolmLib=false -PuseMavenLocal=false