Skip to content

Extra maps #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Test/build.gradle
Original file line number Diff line number Diff line change
@@ -10,6 +10,16 @@ modsDotGroovy {
platforms 'forge', 'quilt'
}

tasks.configureEach {
if (it.name == 'modsDotGroovyToToml') {
it.mixinConfigs.put('mymod.mixins.json', 'mymod.refmap.json')
}
}

afterEvaluate {
println(sourceSets.main.ext.refMapFile)
}

repositories {
mavenLocal()
}
19 changes: 17 additions & 2 deletions Test/src/main/resources/mods.groovy
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ ModsDotGroovy.make {
version = '1.190'

contributors = [
Owner: 'GroovyMC',
Owner: ['GroovyMC'],
Author: ['Matyrobbrt', 'Paint_Ninja', 'lukebemish']
]

@@ -69,6 +69,21 @@ ModsDotGroovy.make {
}

onQuilt {
mixin = "no.mixin.json"
mixin = 'mymod.mixins.json'
}

packMcMeta {
packFormat = 6
forgeDataPackFormat = 4
description = 'The resources of a lovely mod'
}

mixinConfig {
compatibilityLevel = 17
packageName = 'com.matyrobbrt.test.mixins'
mixins {
common 'ServerPlayerMixin'
client 'client.MinecraftMixin'
}
}
}
59 changes: 56 additions & 3 deletions src/lib/groovy/ModsDotGroovy.groovy
Original file line number Diff line number Diff line change
@@ -12,8 +12,10 @@ import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import groovyjarjarantlr4.v4.runtime.misc.Nullable
import modsdotgroovy.ImmutableModInfo
import modsdotgroovy.MixinConfigBuilder
import modsdotgroovy.ModInfoBuilder
import modsdotgroovy.ModsBuilder
import modsdotgroovy.PackMcMetaBuilder
import modsdotgroovy.VersionRange

import java.net.http.HttpClient
@@ -29,18 +31,26 @@ class ModsDotGroovy {
protected Map data

protected ModsDotGroovy() {
this.data = switch (platform) {
case Platform.QUILT -> ["schema_version": 1, "quilt_loader": [:]]
case Platform.FORGE -> [:]
if (platform === Platform.QUILT) {
data = ['schema_version': 1, 'quilt_loader': [:]]
} else {
data = [:]
}
}

protected static Platform platform
protected static final Map<String, String> mixinRefMaps = [:]

protected static void setPlatform(String name) {
platform = Platform.valueOf(name.toUpperCase(Locale.ROOT))
}

protected static void setMixinRefMap(String configName, String refMap) {
if (!refMap.isBlank()) {
mixinRefMaps[configName] = refMap
}
}

void propertyMissing(String name, Object value) {
put(name, value)
}
@@ -242,6 +252,45 @@ class ModsDotGroovy {
}
}

void packMcMeta(@DelegatesTo(value = PackMcMetaBuilder, strategy = DELEGATE_FIRST)
@ClosureParams(value = SimpleType, options = 'modsdotgroovy.PackMcMetaBuilder') final Closure closure) {
final builder = new PackMcMetaBuilder()
closure.delegate = builder
closure.resolveStrategy = DELEGATE_FIRST
closure.call(builder)
extraMaps.put('packMcMeta', builder as Map)
}

void mixinConfig(String configId, @DelegatesTo(value = MixinConfigBuilder, strategy = DELEGATE_FIRST)
@ClosureParams(value = SimpleType, options = 'modsdotgroovy.MixinConfigBuilder') final Closure closure) {
final builder = new MixinConfigBuilder()
builder.setRefMap(mixinRefMaps[configId])
closure.delegate = builder
closure.resolveStrategy = DELEGATE_FIRST
closure.call(builder)
extraMaps.put('mixinConfig_' + configId, builder as Map)

onQuilt {
final old = data['mixin']
if (old === null) {
data['mixin'] = [configId]
} else if (old instanceof List) {
old.add(configId)
} else {
data['mixin'] = [configId, old]
}
}
}

void mixinConfig(@DelegatesTo(value = MixinConfigBuilder, strategy = DELEGATE_FIRST)
@ClosureParams(value = SimpleType, options = 'modsdotgroovy.MixinConfigBuilder') final Closure closure) {
mixinRefMaps.forEach { cfg, refmap ->
if (!extraMaps.containsKey('mixinConfig_' + cfg)) {
mixinConfig(cfg, closure)
}
}
}

private static String combineAsString(List<String> parts) {
String fullString = ''
switch (parts.size()) {
@@ -268,6 +317,10 @@ class ModsDotGroovy {
sanitizeMap(data)
}

private Map getExtraMaps() {
(Map)data.computeIfAbsent('extraMaps') { new HashMap<>() }
}

private static void sanitizeMap(Map data) {
final copy = new LinkedHashMap(data) // cannot use Map.copyOf as we wish to remove null values
copy.forEach { Object key, Object value ->
19 changes: 19 additions & 0 deletions src/lib/groovy/modsdotgroovy/MapBackend.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package modsdotgroovy

import groovy.transform.CompileStatic

@CompileStatic
class MapBackend {
protected final Map data = [:]
void put(String key, Object value) {
data[key] = value
}
void propertyMissing(String name, Object value) {
data[name] = value
}

<T> T asType(Class<T> type) {
if (type == Map.class) return (T)data
return type.cast(this)
}
}
87 changes: 87 additions & 0 deletions src/lib/groovy/modsdotgroovy/MixinConfigBuilder.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package modsdotgroovy

import groovy.transform.CompileStatic
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType

import static groovy.lang.Closure.DELEGATE_FIRST

@CompileStatic
class MixinConfigBuilder extends MapBackend {

MixinConfigBuilder() {
setMinVersion('0.8')
injectors {
setDefaultRequire(1)
}
setRequired(true)
}

/**
* Sets the refmap
* @param refMap refmap
*/
void setRefMap(String refMap) {
put('refmap', refMap)
}

void setRequired(boolean required) {
put('required', required)
}

void setPackageName(String packageName) {
put('package', packageName)
}

void setCompatibilityLevel(int javaVersion) {
put('compatibilityLevel', 'JAVA_' + javaVersion)
}

void setMinVersion(String minVersion) {
put('minVersion', minVersion)
}

void injectors(@DelegatesTo(value = Injectors, strategy = DELEGATE_FIRST)
@ClosureParams(value = SimpleType, options = 'modsdotgroovy.MixinConfigBuilder$Injectors') final Closure closure) {
final builder = new Injectors()
closure.delegate = builder
closure.resolveStrategy = DELEGATE_FIRST
closure.call(builder)
put('injectors', builder.data)
}

void mixins(@DelegatesTo(value = Mixins, strategy = DELEGATE_FIRST)
@ClosureParams(value = SimpleType, options = 'modsdotgroovy.MixinConfigBuilder$Mixins') final Closure closure) {
final builder = new Mixins()
closure.delegate = builder
closure.resolveStrategy = DELEGATE_FIRST
closure.call(builder)
this['mixins'] = builder.common
this['client'] = builder.client
this['server'] = builder.server
}

static final class Injectors extends MapBackend {
void setDefaultRequire(int defaultRequire) {
put('defaultRequire', defaultRequire)
}
}

static final class Mixins {
final List<String> common = []
final List<String> client = []
final List<String> server = []

void common(String... commonMixins) {
this.common.addAll(commonMixins)
}

void client(String... clientMixins) {
this.client.addAll(clientMixins)
}

void server(String... serverMixins) {
this.server.addAll(serverMixins)
}
}
}
31 changes: 31 additions & 0 deletions src/lib/groovy/modsdotgroovy/PackMcMetaBuilder.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 GroovyMC
* SPDX-License-Identifier: MIT
*/

package modsdotgroovy

import groovy.transform.CompileStatic

@CompileStatic
class PackMcMetaBuilder extends MapBackend {
void setDescription(String description) {
packMap['description'] = description
}

void setPackFormat(int packFormat) {
packMap['pack_format'] = packFormat
}

void setForgeResourcePackFormat(int packFormat) {
packMap['forge:resource_pack_format'] = packFormat
}

void setForgeDataPackFormat(int packFormat) {
packMap['forge:data_pack_format'] = packFormat
}

private Map getPackMap() {
(Map)data.computeIfAbsent('pack') { new HashMap<>() }
}
}
Original file line number Diff line number Diff line change
@@ -5,8 +5,12 @@

package io.github.groovymc.modsdotgroovy

import com.google.gson.GsonBuilder
import com.moandjiezana.toml.TomlWriter
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.transform.PackageScope
import groovy.transform.TupleConstructor
import org.codehaus.groovy.control.CompilerConfiguration
import org.gradle.api.DefaultTask
import org.gradle.api.Project
@@ -15,21 +19,25 @@ import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.file.CopySpec
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.internal.catalog.VersionCatalogView
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.jvm.tasks.Jar
import org.gradle.language.jvm.tasks.ProcessResources

import java.nio.file.Files
import java.util.function.Function

@CompileStatic
abstract class AbstractConvertTask extends DefaultTask {
protected final Map<String, Strategy> strategies = [:]

@InputFile
abstract RegularFileProperty getInput()
@Optional
@OutputFile
abstract RegularFileProperty getOutput()
@OutputFiles
abstract MapProperty<String, Object> getOutput()
@Deprecated(forRemoval = true)
@Optional
@InputFile
@@ -44,13 +52,33 @@ abstract class AbstractConvertTask extends DefaultTask {
@Input
@Optional
abstract ListProperty<String> getCatalogs()
@Input
@Optional
abstract MapProperty<String, String> getMixinConfigs()

@Internal
protected abstract String getOutputName()
protected abstract void setupPlatformSpecificArguments()
protected abstract String writeData(Map data)

void register(String id, Strategy strategy) {
strategies[id] = strategy
}

protected String getOutputName(String mapId) {
return strategies.get(mapId).outputName
}

protected String getOutputDir(String mapId) {
return strategies.get(mapId).outputDir
}

protected String writeData(String mapId, Map data) {
return strategies.get(mapId).writer.apply(data)
}

@Internal
protected abstract String getOutputDir()
protected Collection<String> getKnownMapIds() {
return strategies.keySet()
}

@Internal
protected abstract String getPlatform()

@@ -60,22 +88,34 @@ abstract class AbstractConvertTask extends DefaultTask {
if (ModsDotGroovy.metaClass.respondsTo(null,'setPlatform')) {
ModsDotGroovy.setPlatform('${getPlatform()}')
}
if (ModsDotGroovy.metaClass.respondsTo(null,'setMixinRefMap')) {
${mixinConfigs.get().entrySet().stream().map { Map.Entry it ->
"ModsDotGroovy.setMixinRefMap('$it.key', '$it.value')"
}.iterator().join('\n')}
}
"""
}

AbstractConvertTask() {
output.convention(project.layout.buildDirectory.dir(name).map {it.file(getOutputName())})
mixinConfigs.convention([:])
output.convention(project.objects.mapProperty(String, Object))
arguments.convention(project.objects.mapProperty(String, Object))
catalogs.convention(['libs'])
project.afterEvaluate {
registerStrategies()

arguments.put('buildProperties', project.extensions.extraProperties.properties)
catalogs.get().each {
arg(it, versionCatalogToMap(getLibsExtension(project, it)))
catalogs.get().forEach { String id ->
arg(id, versionCatalogToMap(getLibsExtension(project, id)))
}
arg('version', project.version)
arg('platform', getPlatform())
arg('group', project.group)
setupPlatformSpecificArguments()

knownMapIds.each { mapId ->
output.put(mapId, project.layout.buildDirectory.dir(name).map { it.file(getOutputName(mapId))} )
}
}
}

@@ -137,6 +177,8 @@ if (ModsDotGroovy.metaClass.respondsTo(null,'setPlatform')) {
}
}

protected void registerStrategies() {}

void arg(String name, Object arg) {
arguments[name] = arg
}
@@ -148,11 +190,14 @@ if (ModsDotGroovy.metaClass.respondsTo(null,'setPlatform')) {
getProject().logger.warn("Input file {} for task '{}' could not be found!", input, getName())
return
}
final data = from(input)
final outPath = getOutput().get().asFile.toPath()
if (outPath.parent !== null && !Files.exists(outPath.parent)) Files.createDirectories(outPath.parent)
Files.deleteIfExists(outPath)
Files.writeString(outPath, writeData(data))
final maps = split(from(input))
maps.each { mapId, data ->
output.getting(mapId).map { project.file(it) }.getOrNull()?.toPath()?.tap { outPath ->
if (outPath.parent !== null && !Files.exists(outPath.parent)) Files.createDirectories(outPath.parent)
Files.deleteIfExists(outPath)
Files.writeString(outPath, writeData(mapId, data))
}
}
}

@SuppressWarnings('GrDeprecatedAPIUsage')
@@ -181,14 +226,44 @@ if (ModsDotGroovy.metaClass.respondsTo(null,'setPlatform')) {
.extendsFrom(project.configurations.getByName(ModsDotGroovy.CONFIGURATION_NAME))

project.tasks.named(sourceSet.processResourcesTaskName, ProcessResources).configure {
it.exclude(fileName)
it.dependsOn(this)
it.from(output.get().asFile) { CopySpec spec ->
spec.into getOutputDir()
setupOnProcessResources(it, fileName)
}
}

@CompileDynamic
@PackageScope void setupOnProcessResources(ProcessResources processResources, Object exclusion) {
//noinspection GroovyAssignabilityCheck
processResources.exclude(exclusion)
processResources.dependsOn(this)
output.get().each { String mapId, Object file ->
processResources.from(file) { CopySpec spec ->
spec.into(getOutputDir(mapId))
if (mapId == 'mixinConfig') {
spec.rename((String from) -> {
if (from == 'mixins.json') {
return mixinConfigName.getOrElse(project.tasks.named('jar')
.map { (Jar) it }.map { it.archiveBaseName.get() + '.' }.getOrElse('') + 'mixins.json')
}
return from
})
}
}
}
}

static Map<String, Map> split(Map map) {
final Map<String, Map> maps = [:]
final Map root = new HashMap(map)
maps['root'] = root

((Map)root.getOrDefault('extraMaps', [:])).each { id, val ->
maps[id as String] = val as Map
}
root.remove('extraMaps')

return maps
}

@CompileStatic
static class DelegateConfig extends CompilerConfiguration {
DelegateConfig(CompilerConfiguration configuration) {
@@ -197,4 +272,26 @@ if (ModsDotGroovy.metaClass.respondsTo(null,'setPlatform')) {
@Delegate
final CompilerConfiguration configuration
}

@TupleConstructor
static final class Strategy {
final String outputName
final String outputDir
final Function<Map, String> writer
}

protected static final Function<Map, String> TOML_WRITER = { Map data ->
final tomlWriter = new TomlWriter.Builder()
.indentValuesBy(2)
.indentTablesBy(4)
.build()
return tomlWriter.write(data)
}

protected static final Function<Map, String> JSON_WRITER = { Map data ->
final gson = new GsonBuilder()
.setPrettyPrinting()
.create()
return gson.toJson(data)
}
}
Original file line number Diff line number Diff line change
@@ -5,12 +5,22 @@

package io.github.groovymc.modsdotgroovy

import com.google.gson.GsonBuilder
import groovy.transform.CompileStatic

@CompileStatic
abstract class ConvertToQuiltJsonTask extends AbstractConvertTask {

@Override
protected String getOutputName() {
return 'quilt.mod.json'
protected void registerStrategies() {
register('root', new Strategy(
'quilt.mod.json', '', JSON_WRITER
))

mixinConfigs.get().forEach { String config, String refMap ->
register("mixinConfig_$config", new Strategy(
config, '', JSON_WRITER
))
}
}

@Override
@@ -35,19 +45,6 @@ abstract class ConvertToQuiltJsonTask extends AbstractConvertTask {
}
}

@Override
protected String writeData(Map data) {
final gson = new GsonBuilder()
.setPrettyPrinting()
.create()
return gson.toJson(data)
}

@Override
protected String getOutputDir() {
return ''
}

@Override
protected String getPlatform() {
return 'quilt'
Original file line number Diff line number Diff line change
@@ -5,14 +5,25 @@

package io.github.groovymc.modsdotgroovy

import com.moandjiezana.toml.TomlWriter
import groovy.transform.CompileStatic

@CompileStatic
abstract class ConvertToTomlTask extends AbstractConvertTask {

@Override
protected String getOutputName() {
return 'mods.toml'
protected void registerStrategies() {
register('root', new Strategy(
'mods.toml', 'META-INF', TOML_WRITER
))
register('packMcMeta', new Strategy(
'pack.mcmeta', '', JSON_WRITER
))

mixinConfigs.get().forEach { String config, String refMap ->
register("mixinConfig_$config", new Strategy(
config, '', JSON_WRITER
))
}
}

@Override
@@ -34,20 +45,6 @@ abstract class ConvertToTomlTask extends AbstractConvertTask {
}
}

@Override
protected String writeData(Map data) {
final tomlWriter = new TomlWriter.Builder()
.indentValuesBy(2)
.indentTablesBy(4)
.build()
return tomlWriter.write(data)
}

@Override
protected String getOutputDir() {
return 'META-INF'
}

@Override
protected String getPlatform() {
return 'forge'
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ abstract class MDGExtension {
abstract Property<MultiloaderConfiguration> getMultiloader()
abstract MapProperty<String, Object> getArguments()
abstract ListProperty<String> getCatalogs()
abstract MapProperty<String, List<String>> getMixins()

protected final Project project

@@ -33,6 +34,7 @@ abstract class MDGExtension {
platforms.set([Platform.FORGE])
arguments.set([:])
catalogs.set(['libs'])
mixins.convention([:])
}

String mdgDsl(String version = null) {
@@ -61,6 +63,15 @@ abstract class MDGExtension {
multiloader.set(conf)
}

void mixinConfig(String config, String refMap) {
final List<String> cfgs = ((List)mixins.get().get(refMap)) ?: new ArrayList<>().tap { mixins.put(refMap, it) }
cfgs.add(config)
}

void mixinConfig(String id) {
mixinConfig(id + '.mixins.json', id + '.refmap.json')
}

enum Platform {
QUILT {
@Override
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.file.FileTreeElement
import org.gradle.api.plugins.ExtraPropertiesExtension
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.SourceSet
import org.gradle.language.jvm.tasks.ProcessResources
@@ -37,6 +38,14 @@ class ModsDotGroovy implements Plugin<Project> {

configuration.dependencies.add(project.dependencies.create(ext.mdgDsl()))

ext.mixins.get().forEach { refMap, ids ->
project.tasks.withType(AbstractConvertTask).configureEach { task ->
ids.forEach {
task.mixinConfigs.put(it, refMap)
}
}
}

if (ext.automaticConfiguration.get()) {
final List<MDGExtension.Platform> platforms = ext.platforms.get()
for (MDGExtension.Platform platform : platforms.unique(false)) {
@@ -54,14 +63,14 @@ class ModsDotGroovy implements Plugin<Project> {
switch (platform) {
case MDGExtension.Platform.FORGE:
makeAndAppendForgeTask(modsGroovy, project).with {
arguments.set(ext.arguments.get())
catalogs.set(ext.catalogs.get())
arguments.putAll(ext.arguments.get())
catalogs.addAll(ext.catalogs.get())
}
break
case MDGExtension.Platform.QUILT:
makeAndAppendQuiltTask(modsGroovy, project).with {
arguments.set(ext.arguments.get())
catalogs.set(ext.catalogs.get())
arguments.putAll(ext.arguments.get())
catalogs.addAll(ext.catalogs.get())
}
}
} else {
@@ -90,15 +99,15 @@ class ModsDotGroovy implements Plugin<Project> {
forge.each {
makeAndAppendForgeTask(modsGroovy, it).with {
dslConfiguration.set(commonConfiguration)
arguments.set(ext.arguments.get())
catalogs.set(ext.catalogs.get())
arguments.putAll(ext.arguments.get())
catalogs.addAll(ext.catalogs.get())
}
}
quilt.each {
makeAndAppendQuiltTask(modsGroovy, it).with{
dslConfiguration.set(commonConfiguration)
arguments.set(ext.arguments.get())
catalogs.set(ext.catalogs.get())
arguments.putAll(ext.arguments.get())
catalogs.addAll(ext.catalogs.get())
}
}
}
@@ -111,13 +120,21 @@ class ModsDotGroovy implements Plugin<Project> {
final convertTask = project.getTasks().create('modsDotGroovyToToml', ConvertToTomlTask) {
it.getInput().set(modsGroovy.file)
}
project.tasks.named(modsGroovy.sourceSet.processResourcesTaskName, ProcessResources).configure {
exclude((FileTreeElement el) -> el.file == convertTask.input.get().asFile)
dependsOn(convertTask)
from(convertTask.output.get().asFile) {
into 'META-INF'

final ext = modsGroovy.sourceSet.getExtensions().getByType(ExtraPropertiesExtension)
if (ext.has('refMapFile')) {
final String refMapName = ext.get('refMapFile')
convertTask.mixinConfigs.put(refMapName.substring(0, refMapName.indexOf('.refmap')) + '.mixins.json', refMapName)
} else {
convertTask.mixinConfigs.get().entrySet().find()?.tap {
ext.set('refMapFile', it.value)
}
}

project.tasks.named(modsGroovy.sourceSet.processResourcesTaskName, ProcessResources).configure {
convertTask.setupOnProcessResources(it, (FileTreeElement el) -> el.file == convertTask.input.get().asFile)
}

return convertTask
}

@@ -126,11 +143,7 @@ class ModsDotGroovy implements Plugin<Project> {
it.getInput().set(modsGroovy.file)
}
project.tasks.named(modsGroovy.sourceSet.processResourcesTaskName, ProcessResources).configure {
exclude((FileTreeElement el) -> el.file == convertTask.input.get().asFile)
dependsOn(convertTask)
from(convertTask.output.get().asFile) {
into ''
}
convertTask.setupOnProcessResources(it, (FileTreeElement el) -> el.file == convertTask.input.get().asFile)
}
return convertTask
}