From e9657d63c47f982116e94134257a57d2d6166481 Mon Sep 17 00:00:00 2001 From: Juuxel <6596629+Juuxel@users.noreply.github.com> Date: Sat, 10 Jul 2021 23:52:38 +0300 Subject: [PATCH] Fix local file mod dependencies (#430) * ModProcessor: Add more descriptive error TR output error message * Fix flatDir/files/fileTree mod dependencies * Add clarifying comment * Use hash as a placeholder version * ProjectTestTrait: Copy instead of reading and writing text This allows having jars and other binary data in tests. * Add integration test for local file dependencies * Use File.bytes instead of Files.copy * Use truncated SHA256 instead of murmur3 --- .../loom/build/ModCompileRemapper.java | 69 ++++++++---- .../loom/configuration/mods/ModProcessor.java | 9 +- .../java/net/fabricmc/loom/util/Checksum.java | 10 ++ .../LocalFileDependencyTest.groovy | 50 +++++++++ .../loom/test/util/ProjectTestTrait.groovy | 2 +- .../projects/localFileDependency/build.gradle | 100 ++++++++++++++++++ .../localFileDependency/gradle.properties | 17 +++ .../myFileTree/test-data-c.jar | Bin 0 -> 4741 bytes .../myFileTree/test-data-d.jar | Bin 0 -> 4741 bytes .../myFlatDir/test-data-e.jar | Bin 0 -> 4742 bytes .../localFileDependency/settings.gradle | 2 + .../java/net/fabricmc/example/ExampleMod.java | 28 +++++ .../fabricmc/example/mixin/ExampleMixin.java | 15 +++ .../src/main/resources/fabric.mod.json | 36 +++++++ .../src/main/resources/modid.mixins.json | 14 +++ .../localFileDependency/test-data-a.jar | Bin 0 -> 4742 bytes .../localFileDependency/test-data-b.jar | Bin 0 -> 4742 bytes 17 files changed, 331 insertions(+), 21 deletions(-) create mode 100644 src/test/groovy/net/fabricmc/loom/test/integration/LocalFileDependencyTest.groovy create mode 100644 src/test/resources/projects/localFileDependency/build.gradle create mode 100644 src/test/resources/projects/localFileDependency/gradle.properties create mode 100644 src/test/resources/projects/localFileDependency/myFileTree/test-data-c.jar create mode 100644 src/test/resources/projects/localFileDependency/myFileTree/test-data-d.jar create mode 100644 src/test/resources/projects/localFileDependency/myFlatDir/test-data-e.jar create mode 100644 src/test/resources/projects/localFileDependency/settings.gradle create mode 100644 src/test/resources/projects/localFileDependency/src/main/java/net/fabricmc/example/ExampleMod.java create mode 100644 src/test/resources/projects/localFileDependency/src/main/java/net/fabricmc/example/mixin/ExampleMixin.java create mode 100644 src/test/resources/projects/localFileDependency/src/main/resources/fabric.mod.json create mode 100644 src/test/resources/projects/localFileDependency/src/main/resources/modid.mixins.json create mode 100644 src/test/resources/projects/localFileDependency/test-data-a.jar create mode 100644 src/test/resources/projects/localFileDependency/test-data-b.jar diff --git a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java index 308ccc2..4c60633 100644 --- a/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java +++ b/src/main/java/net/fabricmc/loom/build/ModCompileRemapper.java @@ -28,11 +28,14 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import java.util.zip.ZipFile; +import com.google.common.io.Files; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.FileCollectionDependency; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.dsl.DependencyHandler; @@ -40,9 +43,11 @@ import org.gradle.api.artifacts.query.ArtifactResolutionQuery; import org.gradle.api.artifacts.result.ArtifactResult; import org.gradle.api.artifacts.result.ComponentArtifactsResult; import org.gradle.api.artifacts.result.ResolvedArtifactResult; +import org.gradle.api.file.FileCollection; import org.gradle.api.logging.Logger; import org.gradle.jvm.JvmLibrary; import org.gradle.language.base.artifact.SourcesArtifact; +import org.jetbrains.annotations.Nullable; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; @@ -51,12 +56,21 @@ import net.fabricmc.loom.configuration.RemappedConfigurationEntry; import net.fabricmc.loom.configuration.mods.ModProcessor; import net.fabricmc.loom.configuration.processors.dependency.ModDependencyInfo; import net.fabricmc.loom.configuration.processors.dependency.RemapData; +import net.fabricmc.loom.util.Checksum; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.OperatingSystem; import net.fabricmc.loom.util.SourceRemapper; @SuppressWarnings("UnstableApiUsage") public class ModCompileRemapper { + // This is a placeholder that is used when the actual group is missing (null or empty). + // This can happen when the dependency is a FileCollectionDependency or from a flatDir repository. + private static final String MISSING_GROUP = "unspecified"; + + private static String replaceIfNullOrEmpty(@Nullable String s, Supplier fallback) { + return s == null || s.isEmpty() ? fallback.get() : s; + } + public static void remapDependencies(Project project, String mappingsSuffix, LoomGradleExtension extension, SourceRemapper sourceRemapper) { Logger logger = project.getLogger(); DependencyHandler dependencies = project.getDependencies(); @@ -75,29 +89,18 @@ public class ModCompileRemapper { List modDependencies = new ArrayList<>(); for (ResolvedArtifact artifact : sourceConfig.getResolvedConfiguration().getResolvedArtifacts()) { - // TODO: This collection doesn't appear to include FileCollection dependencies - // Might have to go based on the dependencies, rather than their resolved form? - // File dependencies use SelfResolvingDependency, which appears to be handled differently - String group = artifact.getModuleVersion().getId().getGroup(); + String group = replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getGroup(), () -> MISSING_GROUP); String name = artifact.getModuleVersion().getId().getName(); - String version = artifact.getModuleVersion().getId().getVersion(); + String version = replaceIfNullOrEmpty(artifact.getModuleVersion().getId().getVersion(), () -> Checksum.truncatedSha256(artifact.getFile())); - if (!isFabricMod(logger, artifact)) { + if (!isFabricMod(logger, artifact.getFile(), artifact.getId())) { addToRegularCompile(project, regularConfig, artifact); continue; } ModDependencyInfo info = new ModDependencyInfo(group, name, version, artifact.getClassifier(), artifact.getFile(), remappedConfig, remapData); - - if (refreshDeps) { - info.forceRemap(); - } - modDependencies.add(info); - String remappedLog = group + ":" + name + ":" + version + (artifact.getClassifier() == null ? "" : ":" + artifact.getClassifier()) + " (" + mappingsSuffix + ")"; - project.getLogger().info(":providing " + remappedLog); - File remappedSources = info.getRemappedOutput("sources"); if ((!remappedSources.exists() || refreshDeps) && !OperatingSystem.isCIBuild()) { @@ -109,6 +112,36 @@ public class ModCompileRemapper { } } + // FileCollectionDependency (files/fileTree) doesn't resolve properly, + // so we have to "resolve" it on our own. The naming is "abc.jar" => "unspecified:abc:unspecified". + for (FileCollectionDependency dependency : sourceConfig.getAllDependencies().withType(FileCollectionDependency.class)) { + String group = replaceIfNullOrEmpty(dependency.getGroup(), () -> MISSING_GROUP); + FileCollection files = dependency.getFiles(); + + // Create a mod dependency for each file in the file collection + for (File artifact : files) { + if (!isFabricMod(logger, artifact, artifact.getName())) { + dependencies.add(regularConfig.getName(), project.files(artifact)); + continue; + } + + String name = Files.getNameWithoutExtension(artifact.getAbsolutePath()); + String version = replaceIfNullOrEmpty(dependency.getVersion(), () -> Checksum.truncatedSha256(artifact)); + + ModDependencyInfo info = new ModDependencyInfo(group, name, version, null, artifact, remappedConfig, remapData); + modDependencies.add(info); + } + } + + for (ModDependencyInfo info : modDependencies) { + if (refreshDeps) { + info.forceRemap(); + } + + String remappedLog = info.getRemappedNotation() + " (" + mappingsSuffix + ")"; + project.getLogger().info(":providing " + remappedLog); + } + try { ModProcessor.processMods(project, modDependencies); } catch (IOException e) { @@ -128,12 +161,10 @@ public class ModCompileRemapper { /** * Checks if an artifact is a fabric mod, according to the presence of a fabric.mod.json. */ - private static boolean isFabricMod(Logger logger, ResolvedArtifact artifact) { - File input = artifact.getFile(); - - try (ZipFile zipFile = new ZipFile(input)) { + private static boolean isFabricMod(Logger logger, File artifact, Object id) { + try (ZipFile zipFile = new ZipFile(artifact)) { if (zipFile.getEntry("fabric.mod.json") != null) { - logger.info("Found Fabric mod in modCompile: {}", artifact.getId()); + logger.info("Found Fabric mod in modCompile: {}", id); return true; } diff --git a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java index 1f7e639..c7088a0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/mods/ModProcessor.java @@ -175,7 +175,14 @@ public class ModProcessor { // Apply this in a second loop as we need to ensure all the inputs are on the classpath before remapping. for (ModDependencyInfo info : remapList) { - OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(info.getRemappedOutput().toPath()).build(); + OutputConsumerPath outputConsumer; + + try { + outputConsumer = new OutputConsumerPath.Builder(info.getRemappedOutput().toPath()).build(); + } catch (Exception e) { + throw new IOException("Could not create output consumer for " + info.getRemappedOutput().getAbsolutePath()); + } + outputConsumer.addNonClassFiles(info.getInputFile().toPath()); outputConsumerMap.put(info, outputConsumer); String accessWidener = info.getAccessWidener(); diff --git a/src/main/java/net/fabricmc/loom/util/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java index c9723f3..e3607d4 100644 --- a/src/main/java/net/fabricmc/loom/util/Checksum.java +++ b/src/main/java/net/fabricmc/loom/util/Checksum.java @@ -26,6 +26,7 @@ package net.fabricmc.loom.util; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; @@ -60,4 +61,13 @@ public class Checksum { throw new RuntimeException("Failed to get file hash"); } } + + public static String truncatedSha256(File file) { + try { + HashCode hash = Files.asByteSource(file).hash(Hashing.sha256()); + return hash.toString().substring(0, 12); + } catch (IOException e) { + throw new UncheckedIOException("Failed to get file hash of " + file, e); + } + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/integration/LocalFileDependencyTest.groovy b/src/test/groovy/net/fabricmc/loom/test/integration/LocalFileDependencyTest.groovy new file mode 100644 index 0000000..6946198 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/integration/LocalFileDependencyTest.groovy @@ -0,0 +1,50 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2016, 2017, 2018 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.integration + +import net.fabricmc.loom.test.util.ProjectTestTrait +import spock.lang.Specification +import spock.lang.Unroll + +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +class LocalFileDependencyTest extends Specification implements ProjectTestTrait { + @Override + String name() { + "localFileDependency" + } + + @Unroll + def "build (gradle #gradle)"() { + when: + def result = create("build", gradle) + then: + result.task(":build").outcome == SUCCESS + where: + gradle | _ + DEFAULT_GRADLE | _ + PRE_RELEASE_GRADLE | _ + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/ProjectTestTrait.groovy b/src/test/groovy/net/fabricmc/loom/test/util/ProjectTestTrait.groovy index 28901dd..7097681 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/ProjectTestTrait.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/ProjectTestTrait.groovy @@ -59,7 +59,7 @@ trait ProjectTestTrait { } tempFile.parentFile.mkdirs() - tempFile << file.text + tempFile.bytes = file.bytes } // Disable the CI checks to ensure nothing is skipped diff --git a/src/test/resources/projects/localFileDependency/build.gradle b/src/test/resources/projects/localFileDependency/build.gradle new file mode 100644 index 0000000..c56a5a8 --- /dev/null +++ b/src/test/resources/projects/localFileDependency/build.gradle @@ -0,0 +1,100 @@ +plugins { + id 'fabric-loom' + id 'maven-publish' +} + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +archivesBaseName = project.archives_base_name +version = project.mod_version +group = project.maven_group + +repositories { + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. + flatDir { + dirs "myFlatDir" + } +} + +dependencies { + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // Fabric API. This is technically optional, but you probably want it anyway. + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" + + // Local files + modImplementation files("test-data-a.jar", "test-data-b.jar") // multiple files in a bare FileCollection + modImplementation fileTree("myFileTree") // an entire file tree + modImplementation name: "test-data-e" // a flatDir dependency + + // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. + // You may need to force-disable transitiveness on them. +} + +processResources { + inputs.property "version", project.version + + filesMatching("fabric.mod.json") { + expand "version": project.version + } +} + +tasks.withType(JavaCompile).configureEach { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + it.options.encoding = "UTF-8" + + // The Minecraft launcher currently installs Java 8 for users, so your mod probably wants to target Java 8 too + // JDK 9 introduced a new way of specifying this that will make sure no newer classes or methods are used. + // We'll use that if it's available, but otherwise we'll use the older option. + def targetVersion = 8 + if (JavaVersion.current().isJava9Compatible()) { + it.options.release = targetVersion + } +} + +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() +} + +jar { + from("LICENSE") { + rename { "${it}_${project.archivesBaseName}"} + } +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + // add all the jars that should be included when publishing to maven + artifact(remapJar) { + builtBy remapJar + } + artifact(sourcesJar) { + builtBy remapSourcesJar + } + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + } +} diff --git a/src/test/resources/projects/localFileDependency/gradle.properties b/src/test/resources/projects/localFileDependency/gradle.properties new file mode 100644 index 0000000..2e85dfd --- /dev/null +++ b/src/test/resources/projects/localFileDependency/gradle.properties @@ -0,0 +1,17 @@ +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G + +# Fabric Properties + # check these on https://fabricmc.net/use + minecraft_version=1.16.5 + yarn_mappings=1.16.5+build.5 + loader_version=0.11.2 + +# Mod Properties + mod_version = 1.0.0 + maven_group = com.example + archives_base_name = fabric-example-mod + +# Dependencies + # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api + fabric_version=0.31.0+1.16 diff --git a/src/test/resources/projects/localFileDependency/myFileTree/test-data-c.jar b/src/test/resources/projects/localFileDependency/myFileTree/test-data-c.jar new file mode 100644 index 0000000000000000000000000000000000000000..bd64ce5f7a2ceec0f54b60eb6f94e40e7974773a GIT binary patch literal 4741 zcmbtY2T&918cl#efCLbbrt~6$gkGcy7-|fWUX&hsh|;7eC_O?1gGi6kEf7F@1Qire z5fLOP#ey{HC{oRp7xaz#9Pf5~lkQcyT*Oenrx)J7bus*1{ zoXpcFy|tDK1IlkF-%Zj1Q07eFTQPKtduSy5v;##GzyhEt_%49%D?I?fOk2MTp#OT| z@0D`Vlp;(G;YcgEO9(nRM9$MK#7)lQPlT2_9LEYg;yh#gPOpv{gu$ivwBF1NTF1HO zU#%CV%l8q^%VE?cRxJ>LD@*IvHtPxx!Umg9)e~~6X16p#XG6T_Q%MW2ubs@9MvqY{ zhh`ym*JswZT0d;?t_E(TkmZ*ztY{YJv25dcGy43t&_$gqw{LF-;O?gMu7DPMy#2`5 zwVP)6fra-s$vLpa0iUo+(ll4|c7)=#QY$gJaO6=}U!^0G$E9riQP4F~c{jytaeJ%6R`N=LxQ>AVjMmtH#|0*5ju%V(y?ti0ox%o|`l zWwm*m?~v54?cy9VpWGWn7VqqRpM%GBJthJ_F=-Xwxxkas7ob|eV8t3=*F=1(+fF_F zN`9Zkc((k;&`=TUYUrH}@t0sB=bIK89TU-EIt~yC-1EteFqKxP8zVFC?|%v-Z&|e^ zl7(L3b=wO%JR@Rm7u92VRT2oe4Z<6KT}#=WSia zP7Itbt)et`I$RkpX<(>~oPO+eTQ*NX53hL%W}X>1tHi{>Re(0mywuUz6y2*WA%yBGDJ0rkc54wcbIJBPY1J`Ge9hJP z`00+%ymtBJH7DyE5-MgXZCeF5%x(Cy=I9nhT)mZ?sFdXl!TxLchZA4aiCOs>x}gx6 z61h~vTtDv@M=&XsExGY)rAdXQOftT=$S0G5U)G{@YwVu~2DB@RM)4NT>Yd%HR^^0xoI$vKka=}bnV$h)(``vEg;B4x zMJu)H{LolQr%zFtVW9!=HEF5W0n1@M^%KM4oy&Op^i$wZrA@+w4i~Y=+up|1w~zzU z468wC!ux%k8D*EF#SeQ@#N9}EAmtijY%@~iM7*$M%i!y*Yn=4NM9xYMtqFp23m-;p zaB`q6&R%@Qo{Nuvr0D@&bP~b77mB1vCPhC~u-%9s8s0!kEU zeXx3hygcCySXc`0bFXYEac1A8WjJo2WTWhQA42Fe43<@Kv(NJ8M>r@+y4Kb&hfY2t zJW|Xhd2-jU7;MeDJhUV+`@uAbLrhkDOE!Ko-8SO4beda)8+b}d5QQS~)X!xvefE%> zrljl;R!$53Pfb!x+mC%Ql7<`)c1L$AhnrywNMW-t%<;sjT^736yw8CDS_~1JI2Rk9 zAs!VJkqg78qH;&u(rUr$4?VRg;+$s;-P@92Kw!9|Wpy-0uo-au z0WabEXtU4js56whH2xczhmc-U>3gI=`<0zo?U=D=mBGT=+pEMxMKs~#sYQW-h-|&X zgN)E)?xNByK&fb>7|k2(P}hs;XL9O3vPfT@H#yLBO2qhjoxe6_`UZBHfA?@k0NO?% z3og5z4n64Nmi#X8874%%&xg4;7pB(h=G~Of2iHyu*HBNOwrGoXX~(ki8=x8)g?wKQ zsT>F=R$Lthkr-b!v&7kr#%&12J#zq`GG|ldVY@6%?SEGw;-%mqgn2z7gNY3~6b{LY4MQ8hnoKrv2$%Ai>R&WmLVIMg_YFpXUEpT=X z#_a?tsbxPKWT1zQi_RpOLZ@6tzl1IuYd%kEHJacMUKVCZR!~h8#~xweHu$1OaO1d`{~&!o4`RdO5Q4hn?~O;YP1Sw(5~WiGIv{ zo(BFE{l&cH?S0}`b0ifBtCi=FklYKwkjM3icj>zheDHxK^eqdzv;}eFUn$*_fxC{r zt^=h(*W)Gq|2niAjyWnaG4N9kj?T<~$R{Tm>9UH5Ia!?-#MScx9}u{Pv1SUrZtsg&(>``#q^o z2G1a5!W>hU2LSl%n)S(RHA9yD?_2JdICMH_V?QoREp=SR%O*a=J-+5k*&eDbYWES< ztOj2pywlsx1yOM}_oFlebPFDdzIcwRxX>6^ZN(jg3}Nc3d8Oj=d7sm49MpeT;t0%D zN_XVJ?4zU5Giyk#>z51odHb%sAq5^48R39kO)*68fJ@x>4M3|h&FIpPC z=&ZR4yKboJXG*WiH4}?-Muf1-Po_ej&Z)jbhBs!LwhoV14WJTn z#-%FO!c3YMm~fVB6Yjio#_e zanH{hRe3Tc${*Cp&y-LmM@?|1eQE!2HZAb#(2RyL8=0JR?A%LVi@%<9-a>~Qbnx+i z7{Jdi;v7X)<5pG=iq;HA$4_bsMJ=Z*m6-KSyQe%AzG$WAQ!4{zH}$bVUuEmVEby`J z122@RMrMWeKP?5`OHm1EC9EBjub$rc91~GX9OFPrf=Qw;3K+edw0a71)C{PSb=|gl zU8ibJE8OckO&X8Wq=&BgPWVpWXbCtlH~n3NgVl+nQOCBlcS`e z4!sV&B*_u>HAhVJYbff`QiA!996>wF_$A0N(DVhOL*#!Jm5x`<*$FPcuK!IVzhQy? zm*v||F&_UX9p|rf0az^Nk2$|Rh+j7<`CCo|jo6x&iVo94ks-_@z%4lVPwqqlsvAkC z&rvZ|(`~MQSTga!33@gdhX+E!S%HXR=OlTCWm7~yn}qS+HE+!hU#6!@dn}#D2_*+# zGYhp_Mf7$}k7%9Ul^ZYa(s5aQ9%XLf7|0Pm81HtYXv6Y>dq6C>zBHWsvMe=fZqQkV zKkp&uY)onwF3B~uu4EAs$jykPngG1a&gXrsQO=wAyiB zdA=r8e31t_uWWk;*f4`v-2&PXA^6{JH_%|lAQ-B3P`+tFkU)yEJ7k=Oo zt-znV#NV^-_{0yeL%*^9L)Z9w`W@f+0q0-o|ACAAct3Y;%+K%cj*tAnt>4!8m#%^` U2QltYgJ~aLdH}$ledpVM0O(D3V*mgE literal 0 HcmV?d00001 diff --git a/src/test/resources/projects/localFileDependency/myFileTree/test-data-d.jar b/src/test/resources/projects/localFileDependency/myFileTree/test-data-d.jar new file mode 100644 index 0000000000000000000000000000000000000000..cd5ae86bb244bbad4e84ff6e397b14d869b11187 GIT binary patch literal 4741 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 TAk98Ci2UKA0s!op_rLuIsMvlu literal 0 HcmV?d00001 diff --git a/src/test/resources/projects/localFileDependency/myFlatDir/test-data-e.jar b/src/test/resources/projects/localFileDependency/myFlatDir/test-data-e.jar new file mode 100644 index 0000000000000000000000000000000000000000..d7a42d67a49f925d9c94e5433e91e54bb136a3b8 GIT binary patch literal 4742 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.7.4", + "fabric": "*", + "minecraft": "1.16.x" + }, + "suggests": { + "another-mod": "*" + } +} diff --git a/src/test/resources/projects/localFileDependency/src/main/resources/modid.mixins.json b/src/test/resources/projects/localFileDependency/src/main/resources/modid.mixins.json new file mode 100644 index 0000000..21fe73a --- /dev/null +++ b/src/test/resources/projects/localFileDependency/src/main/resources/modid.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.fabricmc.example.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "ExampleMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/test/resources/projects/localFileDependency/test-data-a.jar b/src/test/resources/projects/localFileDependency/test-data-a.jar new file mode 100644 index 0000000000000000000000000000000000000000..d0d6345826d4fac37684f3bfd768c217bb64bed0 GIT binary patch literal 4742 zcmbtY2{e>@AD+Q5cGu&Mi^_tjy@Zw_>Q)_$eT~l*5W5fB`^J0I>Z-0M%D`0Dzv-{}e#| z^~PT-<)A1vH!*<1Y@u$!@bkg4-X6govK~JQjWzxz^ygmTbQ;Mwz#e5Zr`x=XLZGvr zbluEBZKK_bOIk#ziv5I&3TQQO^{Y7G#`?D1$!&!Pq2ujgEtrD(l^ykvm0;huY53Kd z>T?B4@L6IVX@$w*#`5+~*Sn7gHv>PW66DDl8yaOr414JZvq$`Q;H7;V9UY$n(05aZ zH$ZDoeEkV_O`lBD$5vn8BoxT4jroPv;g>kt_bw>zDRtqJuRkAnI8x^f<94f@A3#;( zs|JalJF__EC&;lCj@r}154XdzNh{v*6W8wH9eL~QH+lo!FBQF8y*lG!9!SbrtXf{0 zwe^jsGrKLvUD;IhX%4pgaIdU@z$5#cdEUX5`+mnx>v$~$exTDVyOY74Iuf9QrLkp9 zXl}y|>GbTLcp-n-az0=FBZ*YXxEXTiqu3-^(DkNecJD%TsJ0W6IQrS*$57=imzw9x zukU{dCG6OCClUl-r0euxd%Z8jbdT$H}8gzXb4L<%O}#r!t2gv7nRyC8b$@4 zIvHU&=Wzo#>5_PN4}Wl42QfMq)DoYv)MUG(nLFW_ic{25PE|TSCHc~ zyTpdONj+_5F%@#@u+RYZH!Ng>)8wX9X_R0dgu4ag3=GtHDQK>;i4TtUmEKuHEMK+1 zf!}nF6x(t)I(@PC^FfE=s>X9Iw=dVO5W9D4Lq#ObHodG4GR5zCoB-Db)AI?TNO#yE9j)o+#;My27cL!)M<2l78uZ@C=z+_ z`tpI`u4gtmHBP?aVtNeyYF&xhS1aS|+EM&vv#hz-_0@B{r(Si|%Br%gf#t^2p@{u(ET1!B_;EMXhs6-K(Fjo~^6(P1@b@-YZJk zH>nS8SCERV-ZB$GtOmEW>Wfs4z@_@KNYhk30Laplwg;jn?@~pm3)p4hgPZTJ1{o|@ zYC-RmM-Y_cm+1qakKNuNZ22m}+VU{>oJRI#nXw4n9F9Z_Zts7il* zp0*AMhr+Z^=9;Z)LvctP#N|U|PH0E~bW2iVCV(9Jq-9|$ypNpjm=y;8P|+rY>2(v0 z=|5WCl5k(d~vKh-cfABk%Nc#dD{c3=p>BeFf)t@Ns8`Qu>Y7a&RZQ-paQMKK9Q_t zvNkER=K`3zUJfBiD@i;_ND?q^l~)GoJKWy#Ln=_&Jk-_o?)(i!j#_o&NAUW#eRAS<-X!!o%-p33vIw1oetkYRc!n;e0xIw&;w% z1$|1I&GXt>_E=XhS$;-0v#6b zQ%ZHpphUi|2b)0z@`5X1bv=B&k&o>N~8C7Dj2_k{Ny4L3z%@u4ddX6d+B2P}1}eWSp? zuLX-PT#5_N7K=n(D3nW2vnrhK&TIm2_j_v+#n__^JiC+RfWUBPn}%qFKs(^Z`!B7n z>%YPJys<@B(k=NM?EOw`I%%fa;xZPJJ85Xtpq>{zJ0q*sn(ync&)nU!0EygROO$;xX(YX00~wYUSGNZUYy*A*Snhc;)RG(`a5M<_J<3BS zu8-zL>R(F(W%Bt`+4Cg(N25ms0hcr z4_o6KyO6JYVw{%wl&6SfCr~2VFh-+>mD&ADR#ZXrdj`ppx5hGUVZuf?n$Kz>mTHh> zUaqO^0QgD1JgD?u7V|MTkL0<)r-)#+5kLCjLOIo858t+89;jAkxVqZq-A*l$hgxxr zy!uwHw1Q_QNy;+exZ08_5T5o$J43v~bo@ub_@_?bFf$fKZkAlh-O)L$u#bY1AYx`g z3K54o?)4TS)%l4=F(1!Vfd8$#cj}d%INRgjx#~HJfz9kgs??4;ueVXjwzbn-h0yGJ zsIhw+X)+@(G-k7ld)4ox{=GNeN74oKF;=kUxGAQ5Bs||w_>!7MA9s3tw1O+@3F~ti zeD?9o*Cj;c@L_x*BP)0Yt-;^#vNw>arCI< zyp14F#)49ii;A@4Jx7_mOu$F1eW|e?QIzMOe#X#d&--Z#G@8gm&;IW3Zu&dQGk845&UUCGKbs z>Wgre_P`B>_4tLYXjo`Fq%$>cW+MGGRRNC}Mm+fgG_x4n=i5b_H~f$-huM6+5jafC zBYlB^BqM+4-Dq^q-GhzwFZDL5m`gokQs{_g%HX(N6G1!qQDMs~*Jit`IyQtyViM_y zyv$pa-;-i_q3rhgu**t3 z^VtK5&*j`Dbe=zWG}VcYfz_61<8Fg=;L`?OC0|PIV}!jIYGIBzwXt$7&F4&{M_xd$ z3&ugkrR&pITs1c3uASb^ABqtWjjW1dY(Iz4*5tCZx++-XWKx?g8bMz@`WU50ziuAN zqBvD|i^|*ulzdI}oC~utEA_U*#qd`G>g$(4@L-Y8X9|=Tw0EuFWT;ENE8V~zW!&pa z6cfm0zWg>uk;Pe0AS{J$H8}$DG(?iYP|p-?o+dq)VU7{FrS2XQXnEzH4>d*;cVUl} zF4Xr;ia>x0x&^RG|I%%i_RTlgrg0 zmYqW;R_JUP`dO@Dy*FK={4wp~9Py)s$OZPyFFo&KGXqOV%j!lf1VYlOOOt0Te}CL} z3m$yT$?t4`057YsYvisf=TS9Ov_?2QVNpXclANVfZaT8$nK~qN#a7p^Neawr;%5mj zVHrWJ@-Q9-uU4u=Y z`@!Ys{zn@51q$;YDBqqG;q`yQvHuJgfJ7qxHs-Hi1mu^P<`iH%N-8=*2}OExUI8BG z&;Olo;|+?ydE$+sMb! z>E6p^kDLHUfQPQ9tl(D)aGArcVE_YV`p7#evPP5Fb8*en!sOZm_Z^|{Dw2EK=|#t0 z%~?C*$?J74k$CR_QM2kjML}`FIK{C$8N*-_EX?K-uqojXL&B-u5;6VA55t$M5)7*k zaY?z=oC~Upf*8y`NU*OeO6H3_H!ibJzyP59 z!9b~B8N+vi0Sdw2w~4<--8YKw#mf8*^&i^CU&HSk$M-zzER()zE1Bs@B4ngd+zz&Ip?0|dG6f*eeUnMC|gD*egGH@1`yb1?E$|O zKR^z!5AJBHY>l)~*=LHhwtzc0Libs)0i?d21q28K%3@52R^d;8zxSnmcZU$R%Ftm> zTV=)jcONv{tBz>Anx33y0HADHz)cAZ3u1H-5&Djz3t$7#75osu@C_aSV5RpT0vNxY z_-CbjbfpMuGdR)#?ir2_3s?5@4)<2}`BiAP!*`*-_bS&zSkXb=ILk?smQ5T6pM9_U zN*-<%?{%)MRhr>kpk#3&vwms)d?|2gan zqlGi*2}&Jxj>GxV?CN^=hc%()(6w~33N3RBC{0)+eNS)JCU&zOq;t%s@UbrJdsd>{EXJ;lH z0+Ly5uR;Z@n~OhBA~y%NDhkQM$}bW5LUT6*^$r>PPKADAF{rqfDVRPSqE*7=z<#o& zz4X3u@22^4m0fnP3slyq)N=Oah-+)IPr(u%SL|~8rs5-wTsh?MkEhonHM`v}kI%ln z`6-gT?$DD$mUvDu?k(x_JC@K{-ij4mGH6`B9zCotCGVz^!jy=vJDgiw?lf%?7kcnO zjKxrJfMYdxY3NK@brvSR60~LBnZEw^ z_y>mauCGD)lws;T$%$-5>Gg9FO_spMKe1@Yfe|fN#sKWTS0EXP~%q;2(4vOczl4;`T}P5jMF93 zvRka|ikIb~<9!=K&gU*R9&NpPs&O?`@WPp6<4H8P`Yqa@2FL zFz#Hfq86jxb5cOuW>*dCi?w#675lTP&lq|EkePe!w`8n8q)Rgv@+zZ;mOq?1Vm4cC z2)|YtLsnOrWepu4xxsImCcd~@$;>O--4L`Bgp{n})af76a=1ur#lxaqaJCJ&>R^~~ zdnbfX^_hRxf`fL$xrii)`={8v$cPa5ih}%$5L)ED)~T`Rej33gI|}@%s$G)U=P47@ z8DP2jierxg(=rZCe7lP`xB6_nthpaW)|*5CQfd$rpJSwsoRoxgjlRgQ;bkmM;jQB_ zm?C;~31hTJr$>5{U1XPB_=H8q+ix+%rxINra3U#?)cD(~PHQJeMQY*-wcvFn_Y`V5 z_FGpt2>@(7PDN0a)aCD;Ocl3kQ_%#$oUg6~VpSRJ2TYClr=>M!&x*ydcur`eHw8jh zV(0I)*o20gel9=kE+w$r_;&QQicwl=Md~=BXxGOH@^!JVNBX3z4OGs)KrzsMp`2MY<^KzNP5U?lLB@OFTXw2nz zP`yqQlp@-FYxxM7Hst}BUyL61sq3or;ND?i_8O*sK*Q@M2XWXeOi9z*B|ks};i?Yt zYHp=POg|(Jsuhv^_*<7?>vNvB?MbY`P~B20i-Yjufndd<1F}s<9bJ-F1*=FLs^zj7 z8`Vz(#mMb%iCTcum~9{g&dz;P(d2i>m`ZG$Z#Mb163C6vC|VP{+u5C{IU z5H2%yA}KmoHWqiR2ujF66+P?8Y6h>~_A{W!^2V9@^rS+8z-Tx7hIov42jJ4juWfCM zFOmIzCB-=eJ5gt+K=Wl+gA9k#%&D|4CMF%Y&oQ5_*ozA-H(`q}u5axFN#C3oMf3%p zvaHBGf$I|~I?k((@?BDVee|wQ(heW2Lnzrd$5>{-J_ycK3;?ddi6*rcB4EKs!Zga_ zqx@Ld`3z8&sQ(xz|0h9Oe&ZV%bKL;Lk-cNHi(z`g8R-g@?e;dn{YvDt0#Gk8Rg%zA zaqAgDd-UTTf61F@n~hae9Hk{ohLBENQs4T$-YWCVq@s z5FI&IU}8SX%z4m9TA>RlA8(PMf0>)pDk!h2tv(Z%@fDlW~T_L)EtJhvWh z<<(AonwcM&u-q*;A9w(EF5ay^o)3MC1?VMG9Czr6ipi&Ab8A#L6|Gaf1Z%OdFDugcf4$GDmLez-MbtjM3jA27PW!-29 zCe2^oi#B^;Dkg&2HasA_7Mv)$Y$?&-67Mm6o_rz#S`F%DJ8bv zfr$^MS_Zpq#^dv@3pLiiGF@iiEcZ@0%|fx&1Sf4;OE{`LlCsM=Khblsb4hAAA%%q^ z!ns0!pR}fdXAz2#Zt1iU0O68;YuZX9)&9}juA7yv{jP@Cj|=jPeP;S0`wPi-7S3$<;XJ*}HU(1~mr2M98kuIe=Bb9~*;~dmGzChn& zR!7d2FFwoh&|ik0KeSnJKS5k3_F^1+$5D)tfq)(AjKpPE>)KqI7}lCccX6hyi-<@r zwXwP;2825(?YzuUcTOvA##Pnh(Qm|c7f*oD;nEvHg_=`FoBQ8o>MDFFUn)>V1M5mz$}0)M=Lav>4382n99^QyV-YLuoP-K@@M&^@<3!%emptNHi!tw#Cg7 z8Sw73c!(Ch6|WE(UHL@2xdom`o^}dPe8+oZc2k>QLkAbHAB{datuGNv%T}+n8J_V;zb~2NU=rA@2{2GtF>bCBOl$b0^Ug14CyAW98_tTS=&fB)?7NlgM@%d(oag5{oM`jl@w~j zHX$uTP9_6UjmK4Q3>+uDj`_5X-Ja8DLM1sw*#Q9H&hK;jpT}=*`uOed?Sl*Qg<`ON z(BLp^=-(N=ue`BGp%jt#t?upP_?{9Rn&>eBi1)7s6x!&0{@g)^T_CiIkG`!h$-kA4p_Omuyr=x~)^MeTiZ>sxU7z5h3j z`~ijYzbN0Y6yy7U!twqN7lOrN{x;^HZv^CzmKo!67WQ}kiBqT{ zB!ekW?VH9STT^pL%B>@eTu>fggq*}kG|b4Vok;a|N_{U&J+uj3_L=PArd4EgE6K-5 z-M^qnIYiMgl01)H1Xu*}avc|N&ulPLHVW!6edX$#f@xCf~|9@$);HN)}oe82wrG_`w}(rj{pH*3Prru@X>vijLPDb6>e_ z*rfF0S?iEGoUtYlTe8e)|C`Uj>v>KS6#84K*DSIx)skZB%u{3d65ERw`pU8gNZ~>uaJhO0=(YK zSojA$%%ZAj(C`hY@AP;g@148wQL!sWl;!~)LLnw)M{FnLrcamS9=xty5S-Uv^|Hfsu3D^Mi4-EABlQH}x z7^D;YeVh1e)NP~qS=^l8QU9TB{5AZxar`Wg-{Jp(jr{NW{B`Ya8_CaV`Xk4`v=x*s Th-O literal 0 HcmV?d00001