Skip to content
Merged
Show file tree
Hide file tree
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
37 changes: 21 additions & 16 deletions .project
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>MinecraftMineralogy</name>
<comment>Mineralogy</comment>
<projects/>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments/>
</buildCommand>
</buildSpec>
<linkedResources/>
</projectDescription>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>MinecraftMineralogy</name>
<comment>Mineralogy</comment>
<projects/>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments/>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments/>
</buildCommand>
</buildSpec>
<linkedResources/>
</projectDescription>
2 changes: 2 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ Mineralogy 6.0.1.110021 for Minecraft 1.10.2 Forge
- Documents OreSpawn's custom terrain dimensions and per-rock dimension membership as the resolution for issue #30, including Minecraft 1.10 numeric-ID mapping, dimension-specific rock sets and altitudes, and new-chunks-only behavior.
- Repairs saved rock-furnace loading so Minecraft can reconstruct the tile entity, restore its inventory and progress, and recover the rock-specific burn rate without changing historical NBT or tile IDs.
- Aligns novaculite with Mineralogy 6 hardness/resistance and makes double slabs drop two matching slabs normally or one matching full block with Silk Touch.
- Repairs generated ForgeGradle 7 Eclipse launches by restoring their Buildship project/cache configuration, retiring obsolete ForgeGradle 2 launches, quoting Slime Launcher paths that contain spaces, and verifying the result before handoff.
- Applies OreSpawn's released access-transformer contract to ForgeGradle's mapped development runtime and synchronizes processed metadata and bundled guides into Eclipse output without changing the production jar.
- Advances the functional bug version to 6.0.1 because this release includes fixes and compatibility improvements not present in Mineralogy 6.0.0.
206 changes: 194 additions & 12 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,17 @@ def normalizeArchiveLineEndings = { details ->

minecraft {
mappings channel: project.mappings_channel, version: project.mappings_version
// Forge applies OreSpawn's SRG FMLAT in an ordinary packaged installation,
// but ForgeGradle 7 does not remap a dependency transform for mapped
// development Minecraft. Mirror the exact two released rules for
// Gradle/Eclipse only. This file is outside main resources and is never
// packaged by Mineralogy.
accessTransformer.from(file('gradle/orespawn-development-access-transformer.cfg'))
String configuredRunDirectory = providers.gradleProperty('mineralogyRunDirectory').getOrElse('run')

runs {
configureEach {
workingDir.convention layout.projectDirectory.dir('run')
workingDir.convention layout.projectDirectory.dir(configuredRunDirectory)
systemProperty 'forge.logging.console.level', 'debug'
}

Expand Down Expand Up @@ -174,6 +181,18 @@ tasks.named('processResources', ProcessResources) {
filesMatching(archiveTextPatterns, normalizeArchiveLineEndings)
}

def prepareEclipseResources = tasks.register('prepareEclipseResources') {
group = 'ide'
description = 'Copies Gradle-processed production resources into Eclipse merged output.'
dependsOn tasks.named('processResources')
doLast {
project.copy {
from(layout.buildDirectory.dir('resources/main'))
into(layout.projectDirectory.dir('bin/main'))
}
}
}

tasks.named('jar', Jar) {
archiveClassifier = 'deobf'
destinationDirectory = layout.buildDirectory.dir('libs-dev')
Expand Down Expand Up @@ -325,6 +344,29 @@ tasks.register('verifyReleaseDependencies') {
throw new GradleException('Resolved OreSpawn file does not contain version '
+ project.orespawn_version)
}

def transformerEntry = oreSpawn.getEntry('META-INF/accesstransformer.cfg')
if (transformerEntry == null) {
throw new GradleException('Resolved OreSpawn file is missing its access transformer')
}
List<String> releasedTransformer = oreSpawn.getInputStream(transformerEntry)
.getText(StandardCharsets.UTF_8.name()).readLines()
.findAll { !it.trim().isEmpty() }
List<String> expectedReleasedTransformer = [
'public-f net.minecraft.world.WorldProvider field_76578_c # biomeProvider',
'public-f net.minecraft.world.gen.ChunkProviderOverworld field_186001_t # oceanBlock'
]
List<String> developmentTransformer = file(
'gradle/orespawn-development-access-transformer.cfg').readLines('UTF-8')
.findAll { !it.trim().isEmpty() }
List<String> expectedDevelopmentTransformer = [
'public-f net.minecraft.world.WorldProvider biomeProvider',
'public-f net.minecraft.world.gen.ChunkProviderOverworld oceanBlock'
]
if (releasedTransformer != expectedReleasedTransformer
|| developmentTransformer != expectedDevelopmentTransformer) {
throw new GradleException('Development access transformer no longer matches released OreSpawn semantics')
}
} finally {
oreSpawn.close()
}
Expand Down Expand Up @@ -385,7 +427,9 @@ tasks.register('verifyReleaseArtifacts') {
}
}
['agent-notes/', 'src/test/', 'OilRuntimeProbe', 'org/junit/',
'org/mockito/', 'net/bytebuddy/'].each { forbidden ->
'org/mockito/', 'net/bytebuddy/',
'orespawn-development-access-transformer.cfg',
'META-INF/accesstransformer.cfg'].each { forbidden ->
if (names.any { it.contains(forbidden) }) {
throw new GradleException("Release jar contains forbidden entry matching ${forbidden}")
}
Expand Down Expand Up @@ -594,27 +638,109 @@ eclipse {
synchronizationTasks 'genEclipseRuns'
}

def configureEclipseBuildship = tasks.register('configureEclipseBuildship') {
group = 'ide'
description = 'Creates the Buildship project configuration required by ForgeGradle Eclipse launches.'

doLast {
File preferencesFile = file('.settings/org.eclipse.buildship.core.prefs')
Properties preferences = new Properties()
if (preferencesFile.isFile()) {
preferencesFile.withInputStream { preferences.load(it) }
}

Map<String, String> requiredPreferences = [
'eclipse.preferences.version' : '1',
'connection.gradle.distribution' : 'GRADLE_DISTRIBUTION(WRAPPER)',
'connection.gradle.user.home' : gradle.gradleUserHomeDir.canonicalPath,
'connection.project.dir' : '',
'gradle.user.home' : gradle.gradleUserHomeDir.canonicalPath,
'override.workspace.settings' : 'true'
]
if (!preferencesFile.isFile() || requiredPreferences.any { key, value ->
preferences.getProperty(key) != value
}) {
requiredPreferences.each { key, value -> preferences.setProperty(key, value) }
preferencesFile.parentFile.mkdirs()
preferencesFile.withOutputStream {
preferences.store(it,
'Generated by configureEclipseBuildship; keep Eclipse and command-line caches aligned.')
}
}
}
}

tasks.named('genEclipseRuns') {
finalizedBy configureEclipseBuildship, 'isolateEclipseProductionRuns'
}

tasks.register('verifyEclipseProductionClasspath') {
group = 'verification'
description = 'Verifies that ordinary generated Eclipse launches exclude test code and dependencies.'
dependsOn 'isolateEclipseProductionRuns'
dependsOn tasks.named('genEclipseRuns')

doLast {
File buildshipPreferences = file('.settings/org.eclipse.buildship.core.prefs')
if (!buildshipPreferences.isFile()) {
throw new GradleException("Missing Eclipse Buildship project configuration: ${buildshipPreferences}")
}
Properties buildship = new Properties()
buildshipPreferences.withInputStream { buildship.load(it) }
if (buildship.getProperty('eclipse.preferences.version') != '1'
|| buildship.getProperty('connection.gradle.distribution') != 'GRADLE_DISTRIBUTION(WRAPPER)') {
throw new GradleException('Eclipse Buildship project configuration does not use the Gradle wrapper')
}
String configuredGradleHome = buildship.getProperty('connection.gradle.user.home', '').trim()
if (!configuredGradleHome
|| file(configuredGradleHome).canonicalFile != gradle.gradleUserHomeDir.canonicalFile) {
throw new GradleException("Eclipse Buildship Gradle home is not ${gradle.gradleUserHomeDir.canonicalPath}")
}
String projectMetadata = file('.project').getText('UTF-8')
if (!projectMetadata.contains('org.eclipse.buildship.core.gradleprojectnature')
|| !projectMetadata.contains('org.eclipse.buildship.core.gradleprojectbuilder')) {
throw new GradleException('Eclipse project metadata is missing its Buildship nature or builder')
}

File eclipseModMetadata = file('bin/main/mcmod.info')
if (!eclipseModMetadata.isFile()) {
throw new GradleException('Eclipse output is missing processed mcmod.info')
}
def eclipseMetadata = new JsonSlurper().parseText(eclipseModMetadata.getText('UTF-8'))
def eclipseMod = eclipseMetadata instanceof List ? eclipseMetadata.first() : eclipseMetadata
if (eclipseMod.version != project.mod_version || eclipseMod.mcversion != project.mc_version) {
throw new GradleException('Eclipse output contains unexpanded or incorrect mod metadata')
}
['README.md', 'PLAYER_GUIDE.md', 'DEVELOPER_GUIDE.md',
'CONTENT_CONFIG.md', 'PROVIDER.md', 'VERSIONS.md'].each { guide ->
if (!file("bin/main/META-INF/mineralogy/docs/${guide}").isFile()) {
throw new GradleException("Eclipse output is missing bundled guide ${guide}")
}
}

['MinecraftMineralogy_Client.launch', 'MinecraftMineralogy_Server.launch'].each { name ->
if (file(name).exists()) {
throw new GradleException("Obsolete ForgeGradle 2 Eclipse launch still exists: ${name}")
}
}

List<File> launchFiles = fileTree(project.projectDir) {
include '*.launch'
include '.eclipse/*.launch'
}.files.findAll { file ->
['runClient', 'runServer', 'runGameTestServer', 'runData'].any { file.name.contains(it) }
['runClient.launch', 'runServer.launch',
'runTestClient.launch', 'runTestServer.launch'].contains(file.name)
} as List<File>
if (launchFiles.isEmpty()) {
throw new GradleException('ForgeGradle did not generate ordinary Eclipse launch files')
if (launchFiles.size() != 4) {
throw new GradleException("ForgeGradle generated ${launchFiles.size()} of 4 expected Eclipse launch files")
}

List<String> forbidden = [
'src/test', 'bin/test', 'build/classes/java/test',
'junit-', 'mockito-', 'byte-buddy', 'objenesis', 'OilRuntimeProbe'
]
launchFiles.each { launch ->
launchFiles.findAll { launch ->
['runClient.launch', 'runServer.launch'].contains(launch.name)
}.each { launch ->
String contents = launch.getText('UTF-8').replace('\\', '/')
List<String> present = forbidden.findAll { contents.contains(it) }
if (!present.isEmpty()) {
Expand All @@ -628,25 +754,81 @@ tasks.register('verifyEclipseProductionClasspath') {
throw new GradleException("${launch.name} targets the wrong Eclipse project")
}
}
launchFiles.each { launch ->
String contents = launch.getText('UTF-8')
['--cache', '--metadata', '--to-srg', '--to-obf'].each { option ->
String prefix = option + ' '
int valueStart = contents.indexOf(prefix)
if (valueStart < 0) {
throw new GradleException("${launch.name} is missing Slime Launcher argument ${option}")
}
valueStart += prefix.length()
int valueEnd = contents.indexOf(' --', valueStart)
if (valueEnd < 0) {
throw new GradleException("${launch.name} has a malformed Slime Launcher argument ${option}")
}
String value = contents.substring(valueStart, valueEnd)
boolean quoted = value.startsWith('&quot;') && value.endsWith('&quot;')
if (value.find(/\s/) != null && !quoted) {
throw new GradleException("${launch.name} leaves whitespace unquoted in ${option}: ${value}")
}
}
}
}
}

tasks.register('isolateEclipseProductionRuns') {
group = 'ide'
description = 'Marks ordinary Eclipse launches as production-only after ForgeGradle generation.'
dependsOn tasks.named('genEclipseRuns')

description = 'Normalizes ordinary ForgeGradle Eclipse launches for production-only execution.'
dependsOn prepareEclipseResources
doLast {
['runClient.launch', 'runServer.launch'].each { name ->
[
'MinecraftMineralogy_Client.launch': 'MAIN_TYPE" value="GradleStart"',
'MinecraftMineralogy_Server.launch': 'MAIN_TYPE" value="GradleStartServer"'
].each { name, legacyMarker ->
File legacy = file(name)
if (legacy.isFile() && legacy.getText('UTF-8').contains(legacyMarker)) {
if (!legacy.delete()) {
throw new GradleException("Could not retire obsolete ForgeGradle 2 launch ${name}")
}
}
}

['runClient.launch', 'runServer.launch',
'runTestClient.launch', 'runTestServer.launch'].each { name ->
File launch = file(name)
if (!launch.isFile()) {
throw new GradleException("ForgeGradle did not generate ${name}")
}
String contents = launch.getText('UTF-8')
if (!contents.contains('org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE')) {
boolean changed = false
boolean production = ['runClient.launch', 'runServer.launch'].contains(name)
if (production && !contents.contains('org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE')) {
String marker = '</launchConfiguration>'
String attribute = ' <booleanAttribute key="org.eclipse.jdt.launching.ATTR_EXCLUDE_TEST_CODE" value="true"/>'
contents = contents.replace(marker, attribute + System.lineSeparator() + marker)
changed = true
}
['--cache', '--metadata', '--to-srg', '--to-obf'].each { option ->
String prefix = option + ' '
int valueStart = contents.indexOf(prefix)
if (valueStart < 0) {
throw new GradleException("${name} is missing Slime Launcher argument ${option}")
}
valueStart += prefix.length()
int valueEnd = contents.indexOf(' --', valueStart)
if (valueEnd < 0) {
throw new GradleException("${name} has a malformed Slime Launcher argument ${option}")
}
String value = contents.substring(valueStart, valueEnd)
if (value.find(/\s/) != null && !value.startsWith('&quot;')) {
contents = (contents.substring(0, valueStart)
+ '&quot;' + value + '&quot;'
+ contents.substring(valueEnd))
changed = true
}
}
if (changed) {
launch.setText(contents, 'UTF-8')
}
}
Expand Down
28 changes: 24 additions & 4 deletions docs/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,38 @@ recipes, so existing content remains loadable.

## Building

Use JDK 8, Gradle 4.9, and ForgeGradle `2.2-SNAPSHOT` from the nested 1.10
checkout:
Use a JDK 21 Gradle runtime with the checked-in Gradle 9.6.1 wrapper and
ForgeGradle `7.0.34`. The project itself still compiles and packages Java 8
bytecode through its Java 8 toolchain:

```powershell
$env:JAVA_HOME='C:\Program Files\Java\jdk1.8.0_202'
$env:JAVA_HOME='C:\path\to\jdk-21'
$env:GRADLE_USER_HOME='D:\MinecraftMineralogy\.gradle-verify-cache'
.\gradlew.bat test compileJava processResources --no-daemon
.\gradlew.bat clean build javadoc --no-daemon
.\gradlew.bat setupDecompWorkspace eclipse verifyEclipseProductionClasspath --no-daemon
.\gradlew.bat genEclipseRuns isolateEclipseProductionRuns verifyEclipseProductionClasspath --no-daemon
.\gradlew.bat assemble --no-daemon
```

Import the nested checkout into Eclipse as an existing Gradle project. Do not
run the legacy `setupDecompWorkspace` task. Regenerate the Forge run
configurations after changing the checkout path or Gradle cache; Mineralogy's
normalization task keeps the ordinary client/server launches production-only
and safely quotes Slime Launcher paths containing spaces. It also copies the
Gradle-processed resources into Eclipse's merged `bin/main`, so expanded
metadata and the bundled guide are identical to a Gradle development run.

OreSpawn's released jar owns the production access transformer. ForgeGradle 7
does not remap a dependency transform for Minecraft's mapped development
classes, so this project mirrors OreSpawn's two rules with mapped field names
in `gradle/orespawn-development-access-transformer.cfg`. The dependency audit
keeps those rules aligned with the released jar, and the development-only file
must never be packaged by Mineralogy.

For a disposable Gradle runtime, pass
`-PmineralogyRunDirectory=build/<temporary-name>`; Eclipse continues to use the
normal `run` directory unless that Gradle property is deliberately supplied.

Inspect complete client/server logs and test the reobfuscated jar with released
OreSpawn in a launcher-like Forge installation. The normal jar packages this
guide under `META-INF/mineralogy/docs/`.
Expand Down
2 changes: 2 additions & 0 deletions gradle/orespawn-development-access-transformer.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
public-f net.minecraft.world.WorldProvider biomeProvider
public-f net.minecraft.world.gen.ChunkProviderOverworld oceanBlock
Loading
Loading