Support client only jar, add tests for ancient minecraft versions. (#589)

* Support client only jar, add tests for ancient minecraft versions.

* Misc fixes.
This commit is contained in:
modmuss50
2022-01-31 16:30:12 +00:00
committed by GitHub
parent 4b52176499
commit 582b6826bd
12 changed files with 281 additions and 66 deletions

View File

@@ -145,6 +145,11 @@ public interface LoomGradleExtensionAPI {
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SERVER_ONLY);
}
@ApiStatus.Experimental
default void clientOnlyMinecraftJar() {
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.CLIENT_ONLY);
}
@ApiStatus.Experimental
default void splitMinecraftJar() {
getMinecraftJarConfiguration().set(MinecraftJarConfiguration.SPLIT);

View File

@@ -58,6 +58,10 @@ public final class MergedMinecraftProvider extends MinecraftProvider {
public void provide() throws Exception {
super.provide();
if (!getVersionInfo().isVersionOrNewer("2012-07-25T22:00:00+00:00" /* 1.3 release date */)) {
throw new UnsupportedOperationException("Minecraft versions 1.2.5 and older cannot be merged. Please use `loom { server/clientOnlyMinecraftJar() }`");
}
if (!Files.exists(minecraftMergedJar) || isRefreshDeps()) {
try {
mergeJars();

View File

@@ -49,13 +49,21 @@ public enum MinecraftJarConfiguration {
List.of("client", "server")
),
SERVER_ONLY(
ServerOnlyMinecraftProvider::new,
IntermediaryMinecraftProvider.ServerOnlyImpl::new,
NamedMinecraftProvider.ServerOnlyImpl::new,
ProcessedNamedMinecraftProvider.ServerOnlyImpl::new,
SingleJarMinecraftProvider::server,
IntermediaryMinecraftProvider.SingleJarImpl::server,
NamedMinecraftProvider.SingleJarImpl::server,
ProcessedNamedMinecraftProvider.SingleJarImpl::server,
SingleJarDecompileConfiguration::new,
List.of("server")
),
CLIENT_ONLY(
SingleJarMinecraftProvider::client,
IntermediaryMinecraftProvider.SingleJarImpl::client,
NamedMinecraftProvider.SingleJarImpl::client,
ProcessedNamedMinecraftProvider.SingleJarImpl::client,
SingleJarDecompileConfiguration::new,
List.of("client")
),
SPLIT(
SplitMinecraftProvider::new,
IntermediaryMinecraftProvider.SplitImpl::new,

View File

@@ -34,6 +34,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import com.google.common.base.Preconditions;
import com.google.common.io.Files;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
@@ -73,6 +74,14 @@ public abstract class MinecraftProvider {
this.project = project;
}
protected boolean provideClient() {
return true;
}
protected boolean provideServer() {
return true;
}
public void provide() throws Exception {
final DependencyInfo dependency = DependencyInfo.create(getProject(), Constants.Configurations.MINECRAFT);
minecraftVersion = dependency.getDependency().getVersion();
@@ -88,7 +97,17 @@ public abstract class MinecraftProvider {
}
if (offline) {
if (minecraftClientJar.exists() && minecraftServerJar.exists()) {
boolean exists = true;
if (provideServer() && !minecraftServerJar.exists()) {
exists = false;
}
if (provideClient() && !minecraftClientJar.exists()) {
exists = false;
}
if (exists) {
getProject().getLogger().debug("Found client and server jars, presuming up-to-date");
} else {
throw new GradleException("Missing jar(s); Client: " + minecraftClientJar.exists() + ", Server: " + minecraftServerJar.exists());
@@ -97,7 +116,9 @@ public abstract class MinecraftProvider {
downloadJars(getProject().getLogger());
}
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
if (provideServer()) {
serverBundleMetadata = BundleMetadata.fromJar(minecraftServerJar.toPath());
}
libraryProvider = new MinecraftLibraryProvider();
libraryProvider.provide(this, getProject());
@@ -107,11 +128,17 @@ public abstract class MinecraftProvider {
workingDir = new File(getExtension().getFiles().getUserCache(), minecraftVersion);
workingDir.mkdirs();
minecraftJson = file("minecraft-info.json");
minecraftClientJar = file("minecraft-client.jar");
minecraftServerJar = file("minecraft-server.jar");
minecraftExtractedServerJar = file("minecraft-extracted_server.jar");
versionManifestJson = new File(getExtension().getFiles().getUserCache(), "version_manifest.json");
experimentalVersionsJson = new File(getExtension().getFiles().getUserCache(), "experimental_version_manifest.json");
if (provideClient()) {
minecraftClientJar = file("minecraft-client.jar");
}
if (provideServer()) {
minecraftServerJar = file("minecraft-server.jar");
minecraftExtractedServerJar = file("minecraft-extracted_server.jar");
}
}
private void downloadMcJson(boolean offline) throws IOException {
@@ -231,14 +258,19 @@ public abstract class MinecraftProvider {
return;
}
MinecraftVersionMeta.Download client = versionInfo.download("client");
MinecraftVersionMeta.Download server = versionInfo.download("server");
if (provideClient()) {
MinecraftVersionMeta.Download client = versionInfo.download("client");
HashedDownloadUtil.downloadIfInvalid(new URL(client.url()), minecraftClientJar, client.sha1(), logger, false);
}
HashedDownloadUtil.downloadIfInvalid(new URL(client.url()), minecraftClientJar, client.sha1(), logger, false);
HashedDownloadUtil.downloadIfInvalid(new URL(server.url()), minecraftServerJar, server.sha1(), logger, false);
if (provideServer()) {
MinecraftVersionMeta.Download server = versionInfo.download("server");
HashedDownloadUtil.downloadIfInvalid(new URL(server.url()), minecraftServerJar, server.sha1(), logger, false);
}
}
protected final void extractBundledServerJar() throws IOException {
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
Objects.requireNonNull(getServerBundleMetadata(), "Cannot bundled mc jar from none bundled server jar");
getLogger().info(":Extracting server jar from bootstrap");
@@ -269,17 +301,20 @@ public abstract class MinecraftProvider {
}
public File getMinecraftClientJar() {
Preconditions.checkArgument(provideClient(), "Not configured to provide client jar");
return minecraftClientJar;
}
// May be null on older versions
@Nullable
public File getMinecraftExtractedServerJar() {
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
return minecraftExtractedServerJar;
}
// This may be the server bundler jar on newer versions prob not what you want.
public File getMinecraftServerJar() {
Preconditions.checkArgument(provideServer(), "Not configured to provide server jar");
return minecraftServerJar;
}
@@ -291,14 +326,6 @@ public abstract class MinecraftProvider {
return versionInfo;
}
public MinecraftLibraryProvider getLibraryProvider() {
return libraryProvider;
}
public String getTargetConfig() {
return Constants.Configurations.MINECRAFT;
}
@Nullable
public BundleMetadata getServerBundleMetadata() {
return serverBundleMetadata;

View File

@@ -35,60 +35,64 @@ import net.fabricmc.tinyremapper.NonClassCopyMode;
import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper;
public final class ServerOnlyMinecraftProvider extends MinecraftProvider {
private Path minecraftServerOnlyJar;
public final class SingleJarMinecraftProvider extends MinecraftProvider {
private final Environment environment;
public ServerOnlyMinecraftProvider(Project project) {
private Path minecraftEnvOnlyJar;
private SingleJarMinecraftProvider(Project project, Environment environment) {
super(project);
this.environment = environment;
}
public static SingleJarMinecraftProvider server(Project project) {
return new SingleJarMinecraftProvider(project, new Server());
}
public static SingleJarMinecraftProvider client(Project project) {
return new SingleJarMinecraftProvider(project, new Client());
}
@Override
protected void initFiles() {
super.initFiles();
minecraftServerOnlyJar = path("minecraft-server-only.jar");
minecraftEnvOnlyJar = path("minecraft-%s-only.jar".formatted(environment.name()));
}
@Override
public List<Path> getMinecraftJars() {
return List.of(minecraftServerOnlyJar);
return List.of(minecraftEnvOnlyJar);
}
@Override
public void provide() throws Exception {
super.provide();
boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftServerOnlyJar);
boolean requiresRefresh = isRefreshDeps() || Files.notExists(minecraftEnvOnlyJar);
if (!requiresRefresh) {
return;
}
BundleMetadata serverBundleMetadata = getServerBundleMetadata();
if (serverBundleMetadata == null) {
throw new UnsupportedOperationException("Only Minecraft versions using a bundled server jar support server only configuration, please use a merged jar setup for this version of minecraft");
}
extractBundledServerJar();
final Path serverJar = getMinecraftExtractedServerJar().toPath();
final Path inputJar = environment.getInputJar(this);
TinyRemapper remapper = null;
try {
remapper = TinyRemapper.newRemapper().build();
Files.deleteIfExists(minecraftServerOnlyJar);
Files.deleteIfExists(minecraftEnvOnlyJar);
// Pass through tiny remapper to fix the meta-inf
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(minecraftServerOnlyJar).build()) {
outputConsumer.addNonClassFiles(serverJar, NonClassCopyMode.FIX_META_INF, remapper);
remapper.readInputs(serverJar);
try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(minecraftEnvOnlyJar).build()) {
outputConsumer.addNonClassFiles(inputJar, NonClassCopyMode.FIX_META_INF, remapper);
remapper.readInputs(inputJar);
remapper.apply(outputConsumer);
}
} catch (Exception e) {
Files.deleteIfExists(minecraftServerOnlyJar);
throw new RuntimeException("Failed to process server only jar", e);
Files.deleteIfExists(minecraftEnvOnlyJar);
throw new RuntimeException("Failed to process %s only jar".formatted(environment.name()), e);
} finally {
if (remapper != null) {
remapper.finish();
@@ -96,7 +100,54 @@ public final class ServerOnlyMinecraftProvider extends MinecraftProvider {
}
}
public Path getMinecraftServerOnlyJar() {
return minecraftServerOnlyJar;
@Override
protected boolean provideClient() {
return environment instanceof Client;
}
@Override
protected boolean provideServer() {
return environment instanceof Server;
}
public Path getMinecraftEnvOnlyJar() {
return minecraftEnvOnlyJar;
}
private interface Environment {
String name();
Path getInputJar(SingleJarMinecraftProvider provider) throws Exception;
}
private static final class Server implements Environment {
@Override
public String name() {
return "server";
}
@Override
public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception {
BundleMetadata serverBundleMetadata = provider.getServerBundleMetadata();
if (serverBundleMetadata == null) {
return provider.getMinecraftServerJar().toPath();
}
provider.extractBundledServerJar();
return provider.getMinecraftExtractedServerJar().toPath();
}
}
private static final class Client implements Environment {
@Override
public String name() {
return "client";
}
@Override
public Path getInputJar(SingleJarMinecraftProvider provider) throws Exception {
return provider.getMinecraftClientJar().toPath();
}
}
}

View File

@@ -32,12 +32,12 @@ import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.SidedClassVisitor;
import net.fabricmc.tinyremapper.TinyRemapper;
public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.ServerOnlyImpl, IntermediaryMinecraftProvider.SplitImpl {
public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftProvider> extends AbstractMappedMinecraftProvider<M> permits IntermediaryMinecraftProvider.MergedImpl, IntermediaryMinecraftProvider.SingleJarImpl, IntermediaryMinecraftProvider.SplitImpl {
public IntermediaryMinecraftProvider(Project project, M minecraftProvider) {
super(project, minecraftProvider);
}
@@ -86,16 +86,32 @@ public abstract sealed class IntermediaryMinecraftProvider<M extends MinecraftPr
}
}
public static final class ServerOnlyImpl extends IntermediaryMinecraftProvider<ServerOnlyMinecraftProvider> implements ServerOnly {
public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) {
public static final class SingleJarImpl extends IntermediaryMinecraftProvider<SingleJarMinecraftProvider> implements SingleJar {
private final String env;
private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, String env) {
super(project, minecraftProvider);
this.env = env;
}
public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "server");
}
public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "client");
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL)
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL)
);
}
@Override
public String env() {
return env;
}
}
}

View File

@@ -65,16 +65,20 @@ public interface MappedMinecraftProvider {
}
}
interface ServerOnly extends ProviderImpl {
String SERVER_ONLY = "serverOnly";
interface SingleJar extends ProviderImpl {
String env();
default Path getServerOnlyJar() {
return getJar(SERVER_ONLY);
default String envName() {
return "%sOnly".formatted(env());
}
default Path getEnvOnlyJar() {
return getJar(envName());
}
@Override
default List<Path> getMinecraftJars() {
return List.of(getServerOnlyJar());
return List.of(getEnvOnlyJar());
}
}
}

View File

@@ -33,7 +33,7 @@ import org.gradle.api.Project;
import net.fabricmc.loom.api.mappings.layered.MappingsNamespace;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
import net.fabricmc.loom.util.Constants;
import net.fabricmc.loom.util.SidedClassVisitor;
@@ -99,21 +99,37 @@ public abstract class NamedMinecraftProvider<M extends MinecraftProvider> extend
}
}
public static final class ServerOnlyImpl extends NamedMinecraftProvider<ServerOnlyMinecraftProvider> implements ServerOnly {
public ServerOnlyImpl(Project project, ServerOnlyMinecraftProvider minecraftProvider) {
public static final class SingleJarImpl extends NamedMinecraftProvider<SingleJarMinecraftProvider> implements SingleJar {
private final String env;
private SingleJarImpl(Project project, SingleJarMinecraftProvider minecraftProvider, String env) {
super(project, minecraftProvider);
this.env = env;
}
public static SingleJarImpl server(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "server");
}
public static SingleJarImpl client(Project project, SingleJarMinecraftProvider minecraftProvider) {
return new SingleJarImpl(project, minecraftProvider, "client");
}
@Override
public List<RemappedJars> getRemappedJars() {
return List.of(
new RemappedJars(minecraftProvider.getMinecraftServerOnlyJar(), getServerOnlyJar(), MappingsNamespace.OFFICIAL)
new RemappedJars(minecraftProvider.getMinecraftEnvOnlyJar(), getEnvOnlyJar(), MappingsNamespace.OFFICIAL)
);
}
@Override
protected void applyDependencies(BiConsumer<String, String> consumer) {
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, SERVER_ONLY);
consumer.accept(Constants.Configurations.MINECRAFT_NAMED, envName());
}
@Override
public String env() {
return env;
}
}
}

View File

@@ -36,7 +36,7 @@ import net.fabricmc.loom.LoomGradlePlugin;
import net.fabricmc.loom.configuration.processors.JarProcessorManager;
import net.fabricmc.loom.configuration.providers.minecraft.MergedMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.MinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.ServerOnlyMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SingleJarMinecraftProvider;
import net.fabricmc.loom.configuration.providers.minecraft.SplitMinecraftProvider;
public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvider, P extends NamedMinecraftProvider<M>> extends NamedMinecraftProvider<M> {
@@ -156,14 +156,30 @@ public abstract class ProcessedNamedMinecraftProvider<M extends MinecraftProvide
}
}
public static final class ServerOnlyImpl extends ProcessedNamedMinecraftProvider<ServerOnlyMinecraftProvider, NamedMinecraftProvider.ServerOnlyImpl> implements ServerOnly {
public ServerOnlyImpl(NamedMinecraftProvider.ServerOnlyImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
public static final class SingleJarImpl extends ProcessedNamedMinecraftProvider<SingleJarMinecraftProvider, NamedMinecraftProvider.SingleJarImpl> implements SingleJar {
private final String env;
private SingleJarImpl(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager, String env) {
super(parentMinecraftProvide, jarProcessorManager);
this.env = env;
}
public static ProcessedNamedMinecraftProvider.SingleJarImpl server(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, "server");
}
public static ProcessedNamedMinecraftProvider.SingleJarImpl client(NamedMinecraftProvider.SingleJarImpl parentMinecraftProvide, JarProcessorManager jarProcessorManager) {
return new ProcessedNamedMinecraftProvider.SingleJarImpl(parentMinecraftProvide, jarProcessorManager, "client");
}
@Override
public Path getServerOnlyJar() {
return getProcessedPath(getParentMinecraftProvider().getServerOnlyJar());
public Path getEnvOnlyJar() {
return getProcessedPath(getParentMinecraftProvider().getEnvOnlyJar());
}
@Override
public String env() {
return env;
}
}
}

View File

@@ -141,11 +141,19 @@ public final class LoomTasks {
extension.getRunConfigs().create("client", RunConfigSettings::client);
extension.getRunConfigs().create("server", RunConfigSettings::server);
// Remove the client run config when server only. Done by name to not remove any possible custom run configs
// Remove the client or server run config when not required. Done by name to not remove any possible custom run configs
project.afterEvaluate(p -> {
if (extension.getMinecraftJarConfiguration().get() == MinecraftJarConfiguration.SERVER_ONLY) {
extension.getRunConfigs().removeIf(settings -> settings.getName().equals("client"));
String taskName = switch (extension.getMinecraftJarConfiguration().get()) {
case SERVER_ONLY -> "client";
case CLIENT_ONLY -> "server";
default -> null;
};
if (taskName == null) {
return;
}
extension.getRunConfigs().removeIf(settings -> settings.getName().equals(taskName));
});
}

View File

@@ -84,4 +84,37 @@ class LegacyProjectTest extends Specification implements GradleProjectTestTrait
'1.4.7' | _
'1.3.2' | _
}
@Unroll
def "Ancient minecraft (minecraft #version)"() {
setup:
def gradle = gradleProject(project: "minimalBase", version: PRE_RELEASE_GRADLE)
gradle.buildGradle << """
loom {
intermediaryUrl = 'https://s.modm.us/intermediary-empty-v2.jar'
clientOnlyMinecraftJar()
}
dependencies {
minecraft "com.mojang:minecraft:${version}"
mappings loom.layered() {
// No names
}
modImplementation "net.fabricmc:fabric-loader:0.12.12"
}
"""
when:
def result = gradle.run(task: "configureClientLaunch")
then:
result.task(":configureClientLaunch").outcome == SUCCESS
where:
version | _
'1.2.5' | _
'b1.8.1' | _
'a1.2.5' | _
}
}

View File

@@ -59,6 +59,33 @@ class MCJarConfigTest extends Specification implements GradleProjectTestTrait {
version << STANDARD_TEST_VERSIONS
}
@Unroll
def "client only (gradle #version)"() {
setup:
def gradle = gradleProject(project: "minimalBase", version: version)
gradle.buildGradle << '''
loom {
clientOnlyMinecraftJar()
}
dependencies {
minecraft "com.mojang:minecraft:1.18.1"
mappings "net.fabricmc:yarn:1.18.1+build.18:v2"
modImplementation "net.fabricmc:fabric-loader:0.12.12"
}
'''
when:
def result = gradle.run(task: "build")
then:
result.task(":build").outcome == SUCCESS
where:
version << STANDARD_TEST_VERSIONS
}
@Unroll
def "split (gradle #version)"() {
setup: