mirror of
https://github.com/architectury/architectury-loom.git
synced 2026-03-28 04:07:01 -05:00
fabric.mod.json generation task (#1345)
* fabric.mod.json DSL. * A start on generating FMJs * More work * The rest * Task works * Javadoc and cleanup * Fixes
This commit is contained in:
@@ -161,6 +161,7 @@ dependencies {
|
||||
testImplementation testLibs.bcprov
|
||||
testImplementation testLibs.bcutil
|
||||
testImplementation testLibs.bcpkix
|
||||
testImplementation testLibs.fabric.loader
|
||||
|
||||
compileOnly runtimeLibs.jetbrains.annotations
|
||||
testCompileOnly runtimeLibs.jetbrains.annotations
|
||||
|
||||
785
src/main/java/net/fabricmc/loom/api/fmj/FabricModJsonV1Spec.java
Normal file
785
src/main/java/net/fabricmc/loom/api/fmj/FabricModJsonV1Spec.java
Normal file
@@ -0,0 +1,785 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2025 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.api.fmj;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.MapProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
/**
|
||||
* Represents the Fabric mod JSON v1 specification.
|
||||
*
|
||||
* <p>This class defines properties of a Fabric mod JSON file via a type-safe DSL.
|
||||
*/
|
||||
public abstract class FabricModJsonV1Spec {
|
||||
/**
|
||||
* The ID of the mod.
|
||||
* @return A {@link Property} containing a {@link String} representing the mod ID
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getModId();
|
||||
|
||||
/**
|
||||
* The version of the mod.
|
||||
* @return A {@link Property} containing a {@link String} representing the mod version
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getVersion();
|
||||
|
||||
/**
|
||||
* The display name of the mod.
|
||||
* @return A {@link Property} containing a {@link String} representing the mod name
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getName();
|
||||
|
||||
/**
|
||||
* The description of the mod.
|
||||
* @return A {@link Property} containing a {@link String} representing the mod description
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getDescription();
|
||||
|
||||
/**
|
||||
* A list of other mod IDs that this mod uses as aliases.
|
||||
* @return A {@link ListProperty} containing a list of {@link String} representing the mod aliases
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<String> getProvides();
|
||||
|
||||
/**
|
||||
* The environment the mod runs in.
|
||||
*
|
||||
* <p>One of `client`, `server`, or `*`.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the mod environment
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getEnvironment();
|
||||
|
||||
/**
|
||||
* Sets the environment to 'client', indicating the mod only runs on the client side.
|
||||
*/
|
||||
public void client() {
|
||||
getEnvironment().set("client");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the environment to 'server', indicating the mod only runs on the server side.
|
||||
*/
|
||||
public void server() {
|
||||
getEnvironment().set("server");
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of entrypoints for the mod.
|
||||
* @return A {@link ListProperty} containing a list of {@link Entrypoint} representing the mod entrypoints
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Entrypoint> getEntrypoints();
|
||||
|
||||
/**
|
||||
* Add a new entrypoint with the given name and value.
|
||||
*
|
||||
* @param entrypoint The name of the entrypoint, such as "main" or "client"
|
||||
* @param value The value of the entrypoint, typically a fully qualified class name
|
||||
*/
|
||||
public void entrypoint(String entrypoint, String value) {
|
||||
entrypoint(entrypoint, value, metadata -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new entrypoint with the given name and value, and configure it with the given action.
|
||||
*
|
||||
* @param entrypoint The name of the entrypoint, such as "main" or "client"
|
||||
* @param value The value of the entrypoint, typically a fully qualified class name
|
||||
* @param action An action to configure the entrypoint further
|
||||
*/
|
||||
public void entrypoint(String entrypoint, String value, Action<Entrypoint> action) {
|
||||
entrypoint(entrypoint, metadata -> {
|
||||
metadata.getValue().set(value);
|
||||
action.execute(metadata);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new entrypoint with the given name, and configure it with the given action.
|
||||
*
|
||||
* @param entrypoint The name of the entrypoint, such as "main" or "client"
|
||||
* @param action An action to configure the entrypoint
|
||||
*/
|
||||
public void entrypoint(String entrypoint, Action<Entrypoint> action) {
|
||||
create(Entrypoint.class, getEntrypoints(), e -> {
|
||||
e.getEntrypoint().set(entrypoint);
|
||||
action.execute(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of additional JARs to load with the mod.
|
||||
*
|
||||
* <p>A path relative to the root of the mod JAR.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link String} representing the additional JARs
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<String> getJars();
|
||||
|
||||
/**
|
||||
* A list of Mixin configurations for the mod.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link Mixin} representing the mod Mixins
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Mixin> getMixins();
|
||||
|
||||
/**
|
||||
* Add a new Mixin configuration with the given value.
|
||||
*
|
||||
* @param value The value of the Mixin configuration, typically a path to a JSON file
|
||||
*/
|
||||
public void mixin(String value) {
|
||||
mixin(value, mixin -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Mixin configuration with the given value, and configure it with the given action.
|
||||
*
|
||||
* @param value The value of the Mixin configuration, typically a path to a JSON file
|
||||
* @param action An action to configure the Mixin further
|
||||
*/
|
||||
public void mixin(String value, Action<Mixin> action) {
|
||||
mixin(mixin -> {
|
||||
mixin.getValue().set(value);
|
||||
action.execute(mixin);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new Mixin configuration, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the Mixin
|
||||
*/
|
||||
public void mixin(Action<Mixin> action) {
|
||||
create(Mixin.class, getMixins(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to the access widener file for the mod.
|
||||
*
|
||||
* <p>A path relative to the root of the mod JAR.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the access widener path
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getAccessWidener();
|
||||
|
||||
/**
|
||||
* A list of depedencies that this mod depends on (required).
|
||||
* @return A {@link ListProperty} containing a list of {@link Dependency} representing the mod dependencies
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Dependency> getDepends();
|
||||
|
||||
/**
|
||||
* Add a required dependency on another mod with the given mod ID and version requirements.
|
||||
*
|
||||
* @param modId The mod ID of the dependency
|
||||
* @param versionRequirements A collection of version requirement strings
|
||||
*/
|
||||
public void depends(String modId, Iterable<String> versionRequirements) {
|
||||
depends(modId, dependency -> {
|
||||
dependency.getVersionRequirements().addAll(versionRequirements);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a required dependency on another mod with the given mod ID and a single version requirement.
|
||||
*
|
||||
* @param modId The mod ID of the dependency
|
||||
* @param versionRequirement A version requirement string
|
||||
*/
|
||||
public void depends(String modId, String versionRequirement) {
|
||||
depends(modId, dependency -> {
|
||||
dependency.getVersionRequirements().add(versionRequirement);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a required dependency on another mod with the given mod ID, and configure it with the given action.
|
||||
*
|
||||
* @param modId The mod ID of the dependency
|
||||
* @param action An action to configure the dependency further
|
||||
*/
|
||||
public void depends(String modId, Action<Dependency> action) {
|
||||
depends(dependency -> {
|
||||
dependency.getModId().set(modId);
|
||||
action.execute(dependency);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a required dependency, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the dependency
|
||||
*/
|
||||
public void depends(Action<Dependency> action) {
|
||||
create(Dependency.class, getDepends(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of recommended dependencies.
|
||||
* @return A {@link ListProperty} containing a list of {@link Dependency} representing the mod recommended dependencies
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Dependency> getRecommends();
|
||||
|
||||
/**
|
||||
* Add a recommended dependency on another mod with the given mod ID and version requirements.
|
||||
*
|
||||
* @param modId The mod ID of the recommended dependency
|
||||
* @param versionRequirements A collection of version requirement strings
|
||||
*/
|
||||
public void recommends(String modId, Iterable<String> versionRequirements) {
|
||||
recommends(modId, dependency -> {
|
||||
dependency.getVersionRequirements().addAll(versionRequirements);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a recommended dependency on another mod with the given mod ID and a single version requirement.
|
||||
*
|
||||
* @param modId The mod ID of the recommended dependency
|
||||
* @param versionRequirement A version requirement string
|
||||
*/
|
||||
public void recommends(String modId, String versionRequirement) {
|
||||
recommends(modId, dependency -> {
|
||||
dependency.getVersionRequirements().add(versionRequirement);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a recommended dependency on another mod with the given mod ID, and configure it with the given action.
|
||||
*
|
||||
* @param modId The mod ID of the recommended dependency
|
||||
* @param action An action to configure the recommended dependency further
|
||||
*/
|
||||
public void recommends(String modId, Action<Dependency> action) {
|
||||
recommends(dependency -> {
|
||||
dependency.getModId().set(modId);
|
||||
action.execute(dependency);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a recommended dependency, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the recommended dependency
|
||||
*/
|
||||
public void recommends(Action<Dependency> action) {
|
||||
create(Dependency.class, getRecommends(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of suggested dependencies.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link Dependency} representing the mod suggested dependencies
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Dependency> getSuggests();
|
||||
|
||||
/**
|
||||
* Add a suggested dependency on another mod with the given mod ID and version requirements.
|
||||
*
|
||||
* @param modId The mod ID of the suggested dependency
|
||||
* @param versionRequirements A collection of version requirement strings
|
||||
*/
|
||||
public void suggests(String modId, Iterable<String> versionRequirements) {
|
||||
suggests(modId, dependency -> {
|
||||
dependency.getVersionRequirements().addAll(versionRequirements);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a suggested dependency on another mod with the given mod ID and a single version requirement.
|
||||
*
|
||||
* @param modId The mod ID of the suggested dependency
|
||||
* @param versionRequirement A version requirement string
|
||||
*/
|
||||
public void suggests(String modId, String versionRequirement) {
|
||||
suggests(modId, dependency -> {
|
||||
dependency.getVersionRequirements().add(versionRequirement);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a suggested dependency on another mod with the given mod ID, and configure it with the given action.
|
||||
*
|
||||
* @param modId The mod ID of the suggested dependency
|
||||
* @param action An action to configure the suggested dependency further
|
||||
*/
|
||||
public void suggests(String modId, Action<Dependency> action) {
|
||||
suggests(dependency -> {
|
||||
dependency.getModId().set(modId);
|
||||
action.execute(dependency);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a suggested dependency, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the suggested dependency
|
||||
*/
|
||||
public void suggests(Action<Dependency> action) {
|
||||
create(Dependency.class, getSuggests(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of conflicting dependencies.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link Dependency} representing the mod conflicting dependencies
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Dependency> getConflicts();
|
||||
|
||||
/**
|
||||
* Add a conflicting dependency on another mod with the given mod ID and version requirements.
|
||||
*
|
||||
* @param modId The mod ID of the conflicting dependency
|
||||
* @param versionRequirements A collection of version requirement strings
|
||||
*/
|
||||
public void conflicts(String modId, Iterable<String> versionRequirements) {
|
||||
conflicts(modId, dependency -> {
|
||||
dependency.getVersionRequirements().addAll(versionRequirements);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a conflicting dependency on another mod with the given mod ID and a single version requirement.
|
||||
*
|
||||
* @param modId The mod ID of the conflicting dependency
|
||||
* @param versionRequirement A version requirement string
|
||||
*/
|
||||
public void conflicts(String modId, String versionRequirement) {
|
||||
conflicts(modId, dependency -> {
|
||||
dependency.getVersionRequirements().add(versionRequirement);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a conflicting dependency on another mod with the given mod ID, and configure it with the given action.
|
||||
*
|
||||
* @param modId The mod ID of the conflicting dependency
|
||||
* @param action An action to configure the conflicting dependency further
|
||||
*/
|
||||
public void conflicts(String modId, Action<Dependency> action) {
|
||||
conflicts(dependency -> {
|
||||
dependency.getModId().set(modId);
|
||||
action.execute(dependency);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a conflicting dependency, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the conflicting dependency
|
||||
*/
|
||||
public void conflicts(Action<Dependency> action) {
|
||||
create(Dependency.class, getConflicts(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of dependencies that this mod breaks.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link Dependency} representing the mod broken dependencies
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Dependency> getBreaks();
|
||||
|
||||
/**
|
||||
* Add a broken dependency on another mod with the given mod ID and version requirements.
|
||||
*
|
||||
* @param modId The mod ID of the broken dependency
|
||||
* @param versionRequirements A collection of version requirement strings
|
||||
*/
|
||||
public void breaks(String modId, Iterable<String> versionRequirements) {
|
||||
breaks(modId, dependency -> {
|
||||
dependency.getVersionRequirements().addAll(versionRequirements);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a broken dependency on another mod with the given mod ID and a single version requirement.
|
||||
*
|
||||
* @param modId The mod ID of the broken dependency
|
||||
* @param versionRequirement A version requirement string
|
||||
*/
|
||||
public void breaks(String modId, String versionRequirement) {
|
||||
breaks(modId, dependency -> {
|
||||
dependency.getVersionRequirements().add(versionRequirement);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a broken dependency on another mod with the given mod ID, and configure it with the given action.
|
||||
*
|
||||
* @param modId The mod ID of the broken dependency
|
||||
* @param action An action to configure the broken dependency further
|
||||
*/
|
||||
public void breaks(String modId, Action<Dependency> action) {
|
||||
breaks(dependency -> {
|
||||
dependency.getModId().set(modId);
|
||||
action.execute(dependency);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a broken dependency, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the broken dependency
|
||||
*/
|
||||
public void breaks(Action<Dependency> action) {
|
||||
create(Dependency.class, getBreaks(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of licenses for the mod.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link String} representing the mod licenses
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<String> getLicenses();
|
||||
|
||||
/**
|
||||
* A list of authors of the mod.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link Person} representing the mod authors
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Person> getAuthors();
|
||||
|
||||
/**
|
||||
* Add a new author with the given name.
|
||||
*
|
||||
* @param name The name of the author
|
||||
*/
|
||||
public void author(String name) {
|
||||
author(name, person -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new author with the given name, and configure it with the given action.
|
||||
*
|
||||
* @param name The name of the author
|
||||
* @param action An action to configure the author further
|
||||
*/
|
||||
public void author(String name, Action<Person> action) {
|
||||
author(person -> {
|
||||
person.getName().set(name);
|
||||
action.execute(person);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new author, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the author
|
||||
*/
|
||||
public void author(Action<Person> action) {
|
||||
create(Person.class, getAuthors(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of contributors to the mod.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link Person} representing the mod contributors
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Person> getContributors();
|
||||
|
||||
/**
|
||||
* Add a new contributor with the given name.
|
||||
*
|
||||
* @param name The name of the contributor
|
||||
*/
|
||||
public void contributor(String name) {
|
||||
contributor(name, person -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new contributor with the given name, and configure it with the given action.
|
||||
*
|
||||
* @param name The name of the contributor
|
||||
* @param action An action to configure the contributor further
|
||||
*/
|
||||
public void contributor(String name, Action<Person> action) {
|
||||
contributor(person -> {
|
||||
person.getName().set(name);
|
||||
action.execute(person);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new contributor, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the contributor
|
||||
*/
|
||||
public void contributor(Action<Person> action) {
|
||||
create(Person.class, getContributors(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of contact information for the mod.
|
||||
*
|
||||
* <p>The key is the platform (e.g. "email", "github", "discord") and the value is the contact detail for that platform.
|
||||
*
|
||||
* @return A {@link MapProperty} containing a map of {@link String} keys and {@link String} values representing the mod contact information
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract MapProperty<String, String> getContactInformation();
|
||||
|
||||
/**
|
||||
* A list of icons for the mod.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link Icon} representing the mod icons
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<Icon> getIcons();
|
||||
|
||||
/**
|
||||
* Add a new icon with the given path.
|
||||
*
|
||||
* <p>Note: Only 1 unsized icon is allowed. If you need to specify multiple icons or sizes, use {@link #icon(int, String)}
|
||||
*
|
||||
* @param path The path to the icon file, relative to the root of the mod JAR
|
||||
*/
|
||||
public void icon(String path) {
|
||||
icon(path, icon -> { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new icon with the given size and path.
|
||||
*
|
||||
* @param size The size of the icon in pixels (e.g. 16, 32, 64)
|
||||
* @param path The path to the icon file, relative to the root of the mod JAR
|
||||
*/
|
||||
public void icon(int size, String path) {
|
||||
icon(path, icon -> icon.getSize().set(size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new icon with the given path, and configure it with the given action.
|
||||
*
|
||||
* @param path The path to the icon file, relative to the root of the mod JAR
|
||||
* @param action An action to configure the icon further
|
||||
*/
|
||||
public void icon(String path, Action<Icon> action) {
|
||||
icon(icon -> {
|
||||
icon.getPath().set(path);
|
||||
action.execute(icon);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new icon, and configure it with the given action.
|
||||
*
|
||||
* @param action An action to configure the icon
|
||||
*/
|
||||
public void icon(Action<Icon> action) {
|
||||
create(Icon.class, getIcons(), action);
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of language adapters for the mod.
|
||||
*
|
||||
* <p>The key is the adapter name and the value is the fully qualified class name of the adapter.
|
||||
*
|
||||
* @return A {@link MapProperty} containing a map of {@link String} keys and {@link String} values representing the mod language adapters
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract MapProperty<String, String> getLanguageAdapters();
|
||||
|
||||
/**
|
||||
* A map of custom data for the mod.
|
||||
*
|
||||
* <p>This can be used by other tools to store additional information about the mod.
|
||||
*
|
||||
* <p>The object is encoded to JSON using Gson, so it can be any type that Gson supports.
|
||||
*
|
||||
* @return A {@link MapProperty} containing a map of {@link String} keys and {@link Object} values representing the mod custom data
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract MapProperty<String, Object> getCustomData();
|
||||
|
||||
public abstract static class Entrypoint {
|
||||
/**
|
||||
* The name of the entrypoint, such as "main" or "client".
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the entrypoint name
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getEntrypoint();
|
||||
|
||||
/**
|
||||
* The value of the entrypoint, typically a fully qualified class name.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the entrypoint value
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getValue();
|
||||
|
||||
/**
|
||||
* The language adapter to use for this entrypoint, if any.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the entrypoint language adapter
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getAdapter();
|
||||
}
|
||||
|
||||
public abstract static class Mixin {
|
||||
/**
|
||||
* The value of the Mixin configuration, typically a path to a JSON file.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the Mixin configuration value
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getValue();
|
||||
|
||||
/**
|
||||
* The environment the Mixin configuration applies to.
|
||||
*
|
||||
* <p>One of `client`, `server`, or `*`.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the Mixin configuration environment
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getEnvironment();
|
||||
}
|
||||
|
||||
public abstract static class Dependency {
|
||||
/**
|
||||
* The mod ID of the dependency.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the dependency mod ID
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getModId();
|
||||
|
||||
/**
|
||||
* A list of version requirements for the dependency.
|
||||
*
|
||||
* <p>Each version requirement is a string that specifies a version or range of versions.
|
||||
*
|
||||
* @return A {@link ListProperty} containing a list of {@link String} representing the dependency version requirements
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract ListProperty<String> getVersionRequirements();
|
||||
}
|
||||
|
||||
public abstract static class Person {
|
||||
/**
|
||||
* The name of the person.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the person's name
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getName();
|
||||
|
||||
/**
|
||||
* A map of contact information for the person.
|
||||
*
|
||||
* <p>The key is the platform (e.g. "email", "github", "discord") and the value is the contact detail for that platform.
|
||||
*
|
||||
* @return A {@link MapProperty} containing a map of {@link String} keys and {@link String} values representing the person's contact information
|
||||
*/
|
||||
@Input
|
||||
@Optional
|
||||
public abstract MapProperty<String, String> getContactInformation();
|
||||
}
|
||||
|
||||
public abstract static class Icon {
|
||||
/**
|
||||
* The path to the icon file, relative to the root of the mod JAR.
|
||||
*
|
||||
* @return A {@link Property} containing a {@link String} representing the icon path
|
||||
*/
|
||||
@Input
|
||||
public abstract Property<String> getPath();
|
||||
|
||||
/**
|
||||
* The size of the icon in pixels (e.g. 16, 32, 64).
|
||||
*
|
||||
* <p>If not specified, the icon is considered to be "unsized". Only one unsized icon is allowed.
|
||||
*
|
||||
* @return A {@link Property} containing an {@link Integer} representing the icon size
|
||||
*/
|
||||
@Input
|
||||
@Optional // Icon is required if there is more than 1 icon specified
|
||||
public abstract Property<Integer> getSize();
|
||||
}
|
||||
|
||||
// Internal stuff:
|
||||
|
||||
@Inject
|
||||
@ApiStatus.Internal
|
||||
protected abstract ObjectFactory getObjectFactory();
|
||||
|
||||
private <T> void create(Class<T> type, ListProperty<T> list, Action<T> action) {
|
||||
T item = getObjectFactory().newInstance(type);
|
||||
action.execute(item);
|
||||
list.add(item);
|
||||
}
|
||||
}
|
||||
115
src/main/java/net/fabricmc/loom/task/FabricModJsonV1Task.java
Normal file
115
src/main/java/net/fabricmc/loom/task/FabricModJsonV1Task.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2025 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.task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.model.ObjectFactory;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Nested;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.workers.WorkAction;
|
||||
import org.gradle.workers.WorkParameters;
|
||||
import org.gradle.workers.WorkQueue;
|
||||
import org.gradle.workers.WorkerExecutor;
|
||||
|
||||
import net.fabricmc.loom.api.fmj.FabricModJsonV1Spec;
|
||||
import net.fabricmc.loom.util.fmj.gen.FabricModJsonV1Generator;
|
||||
|
||||
/**
|
||||
* A task that generates a {@code fabric.mod.json} file using the configured {@link FabricModJsonV1Spec} specification.
|
||||
*/
|
||||
public abstract class FabricModJsonV1Task extends AbstractLoomTask {
|
||||
/**
|
||||
* The fabric.mod.json spec.
|
||||
*/
|
||||
@Nested
|
||||
public abstract Property<FabricModJsonV1Spec> getJson();
|
||||
|
||||
/**
|
||||
* The output file to write the generated fabric.mod.json to.
|
||||
*/
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getOutputFile();
|
||||
|
||||
@Inject
|
||||
protected abstract WorkerExecutor getWorkerExecutor();
|
||||
|
||||
@Inject
|
||||
protected abstract ObjectFactory getObjectFactory();
|
||||
|
||||
public FabricModJsonV1Task() {
|
||||
getJson().set(getObjectFactory().newInstance(FabricModJsonV1Spec.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the fabric.mod.json spec.
|
||||
*
|
||||
* @param action A {@link Action} that configures the spec.
|
||||
*/
|
||||
public void json(Action<FabricModJsonV1Spec> action) {
|
||||
action.execute(getJson().get());
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void run() {
|
||||
final WorkQueue workQueue = getWorkerExecutor().noIsolation();
|
||||
|
||||
workQueue.submit(FabricModJsonV1WorkAction.class, params -> {
|
||||
params.getSpec().set(getJson());
|
||||
params.getOutputFile().set(getOutputFile());
|
||||
});
|
||||
}
|
||||
|
||||
public interface FabricModJsonV1WorkParameters extends WorkParameters {
|
||||
Property<FabricModJsonV1Spec> getSpec();
|
||||
|
||||
RegularFileProperty getOutputFile();
|
||||
}
|
||||
|
||||
public abstract static class FabricModJsonV1WorkAction implements WorkAction<FabricModJsonV1WorkParameters> {
|
||||
@Override
|
||||
public void execute() {
|
||||
FabricModJsonV1Spec spec = getParameters().getSpec().get();
|
||||
Path outputPath = getParameters().getOutputFile().get().getAsFile().toPath();
|
||||
|
||||
String json = FabricModJsonV1Generator.INSTANCE.generate(spec);
|
||||
|
||||
try {
|
||||
Files.writeString(outputPath, json);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to write fabric.mod.json", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2025 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.util.fmj.gen;
|
||||
|
||||
public interface FabricModJsonGenerator<Spec> {
|
||||
String generate(Spec spec);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2025 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.util.fmj.gen;
|
||||
|
||||
import static net.fabricmc.loom.util.fmj.gen.GeneratorUtils.add;
|
||||
import static net.fabricmc.loom.util.fmj.gen.GeneratorUtils.addArray;
|
||||
import static net.fabricmc.loom.util.fmj.gen.GeneratorUtils.addRequired;
|
||||
import static net.fabricmc.loom.util.fmj.gen.GeneratorUtils.addStringOrArray;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
import net.fabricmc.loom.LoomGradlePlugin;
|
||||
import net.fabricmc.loom.api.fmj.FabricModJsonV1Spec;
|
||||
|
||||
// Opposite of https://github.com/FabricMC/fabric-loader/blob/master/src/main/java/net/fabricmc/loader/impl/metadata/V1ModMetadataParser.java
|
||||
public final class FabricModJsonV1Generator implements FabricModJsonGenerator<FabricModJsonV1Spec> {
|
||||
private static final int VERSION = 1;
|
||||
|
||||
public static final FabricModJsonV1Generator INSTANCE = new FabricModJsonV1Generator();
|
||||
|
||||
private FabricModJsonV1Generator() {
|
||||
}
|
||||
|
||||
public String generate(FabricModJsonV1Spec spec) {
|
||||
Objects.requireNonNull(spec);
|
||||
|
||||
JsonObject fmj = new JsonObject();
|
||||
fmj.addProperty("schemaVersion", VERSION);
|
||||
|
||||
// Required
|
||||
addRequired(fmj, "id", spec.getModId());
|
||||
addRequired(fmj, "version", spec.getVersion());
|
||||
|
||||
// All other fields are optional
|
||||
// Match the order as specified in V1ModMetadataParser to make it easier to compare
|
||||
|
||||
addArray(fmj, "provides", spec.getProvides(), JsonPrimitive::new);
|
||||
add(fmj, "environment", spec.getEnvironment());
|
||||
add(fmj, "entrypoints", spec.getEntrypoints(), this::generateEntrypoints);
|
||||
addArray(fmj, "jars", spec.getJars(), this::generateJar);
|
||||
addArray(fmj, "mixins", spec.getMixins(), this::generateMixins);
|
||||
add(fmj, "accessWidener", spec.getAccessWidener());
|
||||
add(fmj, "depends", spec.getDepends(), this::generateDependencies);
|
||||
add(fmj, "recommends", spec.getRecommends(), this::generateDependencies);
|
||||
add(fmj, "suggests", spec.getSuggests(), this::generateDependencies);
|
||||
add(fmj, "conflicts", spec.getConflicts(), this::generateDependencies);
|
||||
add(fmj, "breaks", spec.getBreaks(), this::generateDependencies);
|
||||
add(fmj, "name", spec.getName());
|
||||
add(fmj, "description", spec.getDescription());
|
||||
addArray(fmj, "authors", spec.getAuthors(), this::generatePerson);
|
||||
addArray(fmj, "contributors", spec.getContributors(), this::generatePerson);
|
||||
add(fmj, "contact", spec.getContactInformation());
|
||||
addStringOrArray(fmj, "license", spec.getLicenses());
|
||||
add(fmj, "icon", spec.getIcons(), this::generateIcon);
|
||||
add(fmj, "languageAdapters", spec.getLanguageAdapters());
|
||||
add(fmj, "custom", spec.getCustomData(), this::generateCustomData);
|
||||
|
||||
return LoomGradlePlugin.GSON.toJson(fmj);
|
||||
}
|
||||
|
||||
private JsonElement generatePerson(FabricModJsonV1Spec.Person person) {
|
||||
if (person.getContactInformation().get().isEmpty()) {
|
||||
return new JsonPrimitive(person.getName().get());
|
||||
}
|
||||
|
||||
JsonObject json = new JsonObject();
|
||||
addRequired(json, "name", person.getName());
|
||||
add(json, "contact", person.getContactInformation());
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonObject generateEntrypoints(List<FabricModJsonV1Spec.Entrypoint> entrypoints) {
|
||||
Map<String, List<FabricModJsonV1Spec.Entrypoint>> entrypointsMap = entrypoints.stream()
|
||||
.collect(Collectors.groupingBy(entrypoint -> entrypoint.getEntrypoint().get()));
|
||||
|
||||
JsonObject json = new JsonObject();
|
||||
entrypointsMap.forEach((entrypoint, entries) -> json.add(entrypoint, generateEntrypoint(entries)));
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonArray generateEntrypoint(List<FabricModJsonV1Spec.Entrypoint> entries) {
|
||||
JsonArray json = new JsonArray();
|
||||
|
||||
for (FabricModJsonV1Spec.Entrypoint entry : entries) {
|
||||
json.add(generateEntrypointEntry(entry));
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonElement generateEntrypointEntry(FabricModJsonV1Spec.Entrypoint entrypoint) {
|
||||
if (!entrypoint.getAdapter().isPresent()) {
|
||||
return new JsonPrimitive(entrypoint.getValue().get());
|
||||
}
|
||||
|
||||
JsonObject json = new JsonObject();
|
||||
addRequired(json, "value", entrypoint.getValue());
|
||||
addRequired(json, "adapter", entrypoint.getAdapter());
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonObject generateJar(String jar) {
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("file", jar);
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonElement generateMixins(FabricModJsonV1Spec.Mixin mixin) {
|
||||
if (!mixin.getEnvironment().isPresent()) {
|
||||
return new JsonPrimitive(mixin.getValue().get());
|
||||
}
|
||||
|
||||
JsonObject json = new JsonObject();
|
||||
addRequired(json, "config", mixin.getValue());
|
||||
addRequired(json, "environment", mixin.getEnvironment());
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonObject generateDependencies(List<FabricModJsonV1Spec.Dependency> dependencies) {
|
||||
JsonObject json = new JsonObject();
|
||||
|
||||
for (FabricModJsonV1Spec.Dependency dependency : dependencies) {
|
||||
json.add(dependency.getModId().get(), generateDependency(dependency));
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonElement generateDependency(FabricModJsonV1Spec.Dependency dependency) {
|
||||
List<String> requirements = dependency.getVersionRequirements().get();
|
||||
|
||||
if (requirements.isEmpty()) {
|
||||
throw new IllegalStateException("Dependency " + dependency.getModId().get() + " must have at least one version requirement");
|
||||
}
|
||||
|
||||
if (requirements.size() == 1) {
|
||||
return new JsonPrimitive(dependency.getModId().get());
|
||||
}
|
||||
|
||||
JsonArray json = new JsonArray();
|
||||
|
||||
for (String s : requirements) {
|
||||
json.add(s);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonElement generateIcon(List<FabricModJsonV1Spec.Icon> icons) {
|
||||
if (icons.size() == 1 && !icons.getFirst().getSize().isPresent()) {
|
||||
return new JsonPrimitive(icons.getFirst().getPath().get());
|
||||
}
|
||||
|
||||
JsonObject json = new JsonObject();
|
||||
|
||||
for (FabricModJsonV1Spec.Icon icon : icons) {
|
||||
String size = String.valueOf(icon.getSize().get());
|
||||
json.addProperty(size, icon.getPath().get());
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
private JsonObject generateCustomData(Map<String, Object> customData) {
|
||||
JsonObject json = new JsonObject();
|
||||
customData.forEach((name, o) -> json.add(name, LoomGradlePlugin.GSON.toJsonTree(o)));
|
||||
return json;
|
||||
}
|
||||
}
|
||||
143
src/main/java/net/fabricmc/loom/util/fmj/gen/GeneratorUtils.java
Normal file
143
src/main/java/net/fabricmc/loom/util/fmj/gen/GeneratorUtils.java
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2025 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.util.fmj.gen;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import org.gradle.api.provider.ListProperty;
|
||||
import org.gradle.api.provider.MapProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
|
||||
public final class GeneratorUtils {
|
||||
private GeneratorUtils() {
|
||||
}
|
||||
|
||||
public static void add(JsonObject json, String key, Property<String> property) {
|
||||
add(json, key, property, JsonPrimitive::new);
|
||||
}
|
||||
|
||||
public static void addRequired(JsonObject json, String key, Property<String> property) {
|
||||
addRequired(json, key, property, JsonPrimitive::new);
|
||||
}
|
||||
|
||||
public static void addStringOrArray(JsonObject json, String key, ListProperty<String> property) {
|
||||
if (property.get().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add(json, key, property, GeneratorUtils::stringOrArray);
|
||||
}
|
||||
|
||||
public static <V> void addSingleOrArray(JsonObject json, String key, ListProperty<V> property, Function<V, JsonElement> converter) {
|
||||
if (property.get().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add(json, key, property, entries -> singleOrArray(entries, converter));
|
||||
}
|
||||
|
||||
public static <V> void addArray(JsonObject json, String key, ListProperty<V> property, Function<V, JsonElement> converter) {
|
||||
if (property.get().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add(json, key, property, entries -> array(entries, converter));
|
||||
}
|
||||
|
||||
public static <V, P extends Property<V>> void add(JsonObject json, String key, P property, Function<V, JsonElement> converter) {
|
||||
if (!property.isPresent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
json.add(key, converter.apply(property.get()));
|
||||
}
|
||||
|
||||
public static <V, P extends Property<V>> void addRequired(JsonObject json, String key, P property, Function<V, JsonElement> converter) {
|
||||
property.get(); // Ensure it's present
|
||||
add(json, key, property, converter);
|
||||
}
|
||||
|
||||
public static <V> void add(JsonObject json, String key, ListProperty<V> property, Function<List<V>, JsonElement> converter) {
|
||||
if (property.get().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
json.add(key, converter.apply(property.get()));
|
||||
}
|
||||
|
||||
public static <K, V> void add(JsonObject json, String key, MapProperty<K, V> property, Function<Map<K, V>, JsonElement> converter) {
|
||||
if (property.get().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
json.add(key, converter.apply(property.get()));
|
||||
}
|
||||
|
||||
public static void add(JsonObject json, String key, MapProperty<String, String> property) {
|
||||
if (property.get().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
add(json, key, property, GeneratorUtils::map);
|
||||
}
|
||||
|
||||
public static JsonElement stringOrArray(List<String> strings) {
|
||||
return singleOrArray(strings, JsonPrimitive::new);
|
||||
}
|
||||
|
||||
public static <V> JsonElement singleOrArray(List<V> entries, Function<V, JsonElement> converter) {
|
||||
if (entries.size() == 1) {
|
||||
return converter.apply(entries.getFirst());
|
||||
}
|
||||
|
||||
return array(entries, converter);
|
||||
}
|
||||
|
||||
public static <V> JsonElement array(List<V> entries, Function<V, JsonElement> converter) {
|
||||
JsonArray array = new JsonArray();
|
||||
|
||||
for (V entry : entries) {
|
||||
array.add(converter.apply(entry));
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
public static JsonObject map(Map<String, String> map) {
|
||||
JsonObject obj = new JsonObject();
|
||||
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
obj.addProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2025 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 spock.lang.Specification
|
||||
import spock.lang.Unroll
|
||||
|
||||
import net.fabricmc.loom.test.util.GradleProjectTestTrait
|
||||
|
||||
import static net.fabricmc.loom.test.LoomTestConstants.STANDARD_TEST_VERSIONS
|
||||
import static org.gradle.testkit.runner.TaskOutcome.SUCCESS
|
||||
|
||||
class FabricModJsonTask extends Specification implements GradleProjectTestTrait {
|
||||
@Unroll
|
||||
def "Generate FMJ"() {
|
||||
setup:
|
||||
def gradle = gradleProject(project: "minimalBase", version: version)
|
||||
|
||||
gradle.buildGradle << '''
|
||||
dependencies {
|
||||
minecraft "com.mojang:minecraft:1.21.8"
|
||||
mappings "net.fabricmc:yarn:1.21.8+build.1:v2"
|
||||
}
|
||||
|
||||
tasks.register("generateModJson", net.fabricmc.loom.task.FabricModJsonV1Task) {
|
||||
outputFile = file("fabric.mod.json")
|
||||
|
||||
json {
|
||||
modId = "examplemod"
|
||||
version = "1.0.0"
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
when:
|
||||
// Run the task twice to ensure its up to date
|
||||
def result = gradle.run(task: "generateModJson")
|
||||
|
||||
then:
|
||||
result.task(":generateModJson").outcome == SUCCESS
|
||||
new File(gradle.projectDir, "fabric.mod.json").text == """
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
""".stripIndent().trim()
|
||||
|
||||
where:
|
||||
version << STANDARD_TEST_VERSIONS
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,682 @@
|
||||
/*
|
||||
* This file is part of fabric-loom, licensed under the MIT License (MIT).
|
||||
*
|
||||
* Copyright (c) 2025 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.unit.fmj
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.intellij.lang.annotations.Language
|
||||
import spock.lang.Specification
|
||||
|
||||
import net.fabricmc.loader.impl.metadata.ModMetadataParser
|
||||
import net.fabricmc.loom.api.fmj.FabricModJsonV1Spec
|
||||
import net.fabricmc.loom.test.util.GradleTestUtil
|
||||
import net.fabricmc.loom.util.fmj.gen.FabricModJsonV1Generator
|
||||
|
||||
class FabricModJsonV1GeneratorTest extends Specification {
|
||||
static Project project = GradleTestUtil.mockProject()
|
||||
static ObjectFactory objectFactory = project.getObjects()
|
||||
|
||||
def "minimal"() {
|
||||
given:
|
||||
def spec = objectFactory.newInstance(FabricModJsonV1Spec.class)
|
||||
spec.modId.set("examplemod")
|
||||
spec.version.set("1.0.0")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "single license"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.licenses.add("MIT")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT"
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "multiple licenses"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.licenses.addAll("MIT", "Apache-2.0")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"license": [
|
||||
"MIT",
|
||||
"Apache-2.0"
|
||||
]
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "named author"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.author("Epic Modder")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"authors": [
|
||||
"Epic Modder"
|
||||
]
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "author with contact info"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.author("Epic Modder") {
|
||||
it.contactInformation.set(["discord": "epicmodder#1234", "email": "epicmodder@example.com"])
|
||||
}
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Epic Modder",
|
||||
"contact": {
|
||||
"discord": "epicmodder#1234",
|
||||
"email": "epicmodder@example.com"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "named contributor"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.contributor("Epic Modder")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"contributors": [
|
||||
"Epic Modder"
|
||||
]
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "contributor with contact info"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.contributor("Epic Modder") {
|
||||
it.contactInformation.set(["discord": "epicmodder#1234", "email": "epicmodder@example.com"])
|
||||
}
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Epic Modder",
|
||||
"contact": {
|
||||
"discord": "epicmodder#1234",
|
||||
"email": "epicmodder@example.com"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "contact info"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.contactInformation.set(["discord": "epicmodder#1234", "email": "epicmodder@example.com"])
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"contact": {
|
||||
"discord": "epicmodder#1234",
|
||||
"email": "epicmodder@example.com"
|
||||
}
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "provides"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.provides.set(['oldid', 'veryoldid'])
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"provides": [
|
||||
"oldid",
|
||||
"veryoldid"
|
||||
]
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "environment"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.environment.set("client")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"environment": "client"
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "jars"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.jars.set(["libs/some-lib.jar"])
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"jars": [
|
||||
{
|
||||
"file": "libs/some-lib.jar"
|
||||
}
|
||||
]
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "entrypoints"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.entrypoint("main", "com.example.Main")
|
||||
spec.entrypoint("main", "com.example.Blocks")
|
||||
spec.entrypoint("client", "com.example.KotlinClient::init") {
|
||||
it.adapter.set("kotlin")
|
||||
}
|
||||
spec.entrypoint("client") {
|
||||
it.value.set("com.example.Client")
|
||||
}
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"entrypoints": {
|
||||
"client": [
|
||||
{
|
||||
"value": "com.example.KotlinClient::init",
|
||||
"adapter": "kotlin"
|
||||
},
|
||||
"com.example.Client"
|
||||
],
|
||||
"main": [
|
||||
"com.example.Main",
|
||||
"com.example.Blocks"
|
||||
]
|
||||
}
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "mixins"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.mixin("mymod.mixins.json")
|
||||
spec.mixin("mymod.client.mixins.json") {
|
||||
it.environment.set("client")
|
||||
}
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"mixins": [
|
||||
"mymod.mixins.json",
|
||||
{
|
||||
"config": "mymod.client.mixins.json",
|
||||
"environment": "client"
|
||||
}
|
||||
]
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "access widener"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.accessWidener.set("mymod.accesswidener")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"accessWidener": "mymod.accesswidener"
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "depends"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.depends("fabricloader", ">=0.14.0")
|
||||
spec.depends("fabric-api", [">=0.14.0", "<0.15.0"])
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"depends": {
|
||||
"fabricloader": "fabricloader",
|
||||
"fabric-api": [
|
||||
"\\u003e\\u003d0.14.0",
|
||||
"\\u003c0.15.0"
|
||||
]
|
||||
}
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "single icon"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.icon("icon.png")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"icon": "icon.png"
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "multiple icons"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.icon(64, "icon_64.png")
|
||||
spec.icon(128, "icon_128.png")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"icon": {
|
||||
"64": "icon_64.png",
|
||||
"128": "icon_128.png"
|
||||
}
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "language adapters"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.languageAdapters.put("kotlin", "net.fabricmc.loader.api.language.KotlinAdapter")
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"languageAdapters": {
|
||||
"kotlin": "net.fabricmc.loader.api.language.KotlinAdapter"
|
||||
}
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "custom data"() {
|
||||
given:
|
||||
def spec = baseSpec()
|
||||
spec.customData.put("examplemap", ["custom": "data"])
|
||||
spec.customData.put("examplelist", [1, 2, 3])
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"custom": {
|
||||
"examplemap": {
|
||||
"custom": "data"
|
||||
},
|
||||
"examplelist": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
def "complete"() {
|
||||
given:
|
||||
def spec = objectFactory.newInstance(FabricModJsonV1Spec.class)
|
||||
spec.modId.set("examplemod")
|
||||
spec.version.set("1.0.0")
|
||||
spec.name.set("Example Mod")
|
||||
spec.description.set("This is an example mod.")
|
||||
spec.licenses.addAll("MIT", "Apache-2.0")
|
||||
spec.author("Epic Modder") {
|
||||
it.contactInformation.set(["discord": "epicmodder#1234", "email": "epicmodder@example.com"])
|
||||
}
|
||||
spec.contributor("Epic Modder") {
|
||||
it.contactInformation.set(["discord": "epicmodder#1234", "email": "epicmodder@example.com"])
|
||||
}
|
||||
spec.contactInformation.set(["discord": "epicmodder#1234", "email": "epicmodder@example.com"])
|
||||
spec.provides.set(['oldid', 'veryoldid'])
|
||||
spec.environment.set("client")
|
||||
spec.jars.set(["libs/some-lib.jar"])
|
||||
spec.entrypoint("main", "com.example.Main")
|
||||
spec.entrypoint("main", "com.example.Blocks")
|
||||
spec.entrypoint("client", "com.example.KotlinClient::init") {
|
||||
it.adapter.set("kotlin")
|
||||
}
|
||||
spec.entrypoint("client") {
|
||||
it.value.set("com.example.Client")
|
||||
}
|
||||
spec.mixin("mymod.mixins.json")
|
||||
spec.mixin("mymod.client.mixins.json") {
|
||||
it.environment.set("client")
|
||||
}
|
||||
spec.accessWidener.set("mymod.accesswidener")
|
||||
|
||||
spec.depends("fabricloader", ">=0.14.0")
|
||||
spec.depends("fabric-api", [">=0.14.0", "<0.15.0"])
|
||||
spec.recommends("recommended-mod", ">=1.0.0")
|
||||
spec.suggests("suggested-mod", ">=1.0.0")
|
||||
spec.conflicts("conflicting-mod", "<1.0.0")
|
||||
spec.breaks("broken-mod", "<1.0.0")
|
||||
|
||||
spec.icon(64, "icon_64.png")
|
||||
spec.icon(128, "icon_128.png")
|
||||
spec.languageAdapters.put("kotlin", "net.fabricmc.loader.api.language.KotlinAdapter")
|
||||
spec.customData.put("examplemap", ["custom": "data"])
|
||||
spec.customData.put("examplelist", [1, 2, 3])
|
||||
|
||||
when:
|
||||
def json = FabricModJsonV1Generator.INSTANCE.generate(spec)
|
||||
|
||||
then:
|
||||
json == j("""
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "examplemod",
|
||||
"version": "1.0.0",
|
||||
"provides": [
|
||||
"oldid",
|
||||
"veryoldid"
|
||||
],
|
||||
"environment": "client",
|
||||
"entrypoints": {
|
||||
"client": [
|
||||
{
|
||||
"value": "com.example.KotlinClient::init",
|
||||
"adapter": "kotlin"
|
||||
},
|
||||
"com.example.Client"
|
||||
],
|
||||
"main": [
|
||||
"com.example.Main",
|
||||
"com.example.Blocks"
|
||||
]
|
||||
},
|
||||
"jars": [
|
||||
{
|
||||
"file": "libs/some-lib.jar"
|
||||
}
|
||||
],
|
||||
"mixins": [
|
||||
"mymod.mixins.json",
|
||||
{
|
||||
"config": "mymod.client.mixins.json",
|
||||
"environment": "client"
|
||||
}
|
||||
],
|
||||
"accessWidener": "mymod.accesswidener",
|
||||
"depends": {
|
||||
"fabricloader": "fabricloader",
|
||||
"fabric-api": [
|
||||
"\\u003e\\u003d0.14.0",
|
||||
"\\u003c0.15.0"
|
||||
]
|
||||
},
|
||||
"recommends": {
|
||||
"recommended-mod": "recommended-mod"
|
||||
},
|
||||
"suggests": {
|
||||
"suggested-mod": "suggested-mod"
|
||||
},
|
||||
"conflicts": {
|
||||
"conflicting-mod": "conflicting-mod"
|
||||
},
|
||||
"breaks": {
|
||||
"broken-mod": "broken-mod"
|
||||
},
|
||||
"name": "Example Mod",
|
||||
"description": "This is an example mod.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Epic Modder",
|
||||
"contact": {
|
||||
"discord": "epicmodder#1234",
|
||||
"email": "epicmodder@example.com"
|
||||
}
|
||||
}
|
||||
],
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Epic Modder",
|
||||
"contact": {
|
||||
"discord": "epicmodder#1234",
|
||||
"email": "epicmodder@example.com"
|
||||
}
|
||||
}
|
||||
],
|
||||
"contact": {
|
||||
"discord": "epicmodder#1234",
|
||||
"email": "epicmodder@example.com"
|
||||
},
|
||||
"license": [
|
||||
"MIT",
|
||||
"Apache-2.0"
|
||||
],
|
||||
"icon": {
|
||||
"64": "icon_64.png",
|
||||
"128": "icon_128.png"
|
||||
},
|
||||
"languageAdapters": {
|
||||
"kotlin": "net.fabricmc.loader.api.language.KotlinAdapter"
|
||||
},
|
||||
"custom": {
|
||||
"examplemap": {
|
||||
"custom": "data"
|
||||
},
|
||||
"examplelist": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
""")
|
||||
tryParse(json) == 1
|
||||
}
|
||||
|
||||
// Ensure that Fabric loader can actually parse the generated JSON.
|
||||
private static int tryParse(String json) {
|
||||
def meta = new ByteArrayInputStream(json.bytes).withCloseable {
|
||||
//noinspection GroovyAccessibility
|
||||
ModMetadataParser.readModMetadata(it, false)
|
||||
}
|
||||
return meta.getSchemaVersion()
|
||||
}
|
||||
|
||||
private static FabricModJsonV1Spec baseSpec() {
|
||||
def spec = objectFactory.newInstance(FabricModJsonV1Spec.class)
|
||||
spec.modId.set("examplemod")
|
||||
spec.version.set("1.0.0")
|
||||
return spec
|
||||
}
|
||||
|
||||
private static String j(@Language("JSON") String json) {
|
||||
return json.stripIndent().trim()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user