Skip to content

Commit 2bbedbe

Browse files
authored
Java 21 Gradle Run Target (#16)
* gradle: add Java 21 run target with jetbrains hot reloading
1 parent 13882ab commit 2bbedbe

4 files changed

Lines changed: 298 additions & 10 deletions

File tree

build.gradle

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import com.gtnewhorizons.retrofuturagradle.mcp.InjectTagsTask
22
import org.jetbrains.changelog.Changelog
33
import org.jetbrains.gradle.ext.Gradle
44

5+
import com.gtnewhorizons.retrofuturagradle.MinecraftExtension
6+
import com.gtnewhorizons.retrofuturagradle.mcp.MCPTasks
7+
import com.gtnewhorizons.retrofuturagradle.minecraft.MinecraftTasks
8+
import com.gtnewhorizons.retrofuturagradle.minecraft.RunMinecraftTask
9+
import com.gtnewhorizons.retrofuturagradle.util.Distribution
10+
import de.undercouch.gradle.tasks.download.DownloadExtension
11+
import org.apache.commons.io.FileUtils
12+
13+
import javax.inject.Inject
14+
515
plugins {
616
id 'java'
717
id 'java-library'
@@ -11,6 +21,7 @@ plugins {
1121
id 'com.matthewprenger.cursegradle' version '1.4.0' apply false
1222
id 'com.modrinth.minotaur' version '2.+' apply false
1323
id 'org.jetbrains.changelog' version '2.5.0'
24+
id 'de.undercouch.download' version '5.6.0' apply false
1425
}
1526

1627
apply from: 'gradle/scripts/helpers.gradle'
@@ -63,6 +74,22 @@ java {
6374

6475
configurations {
6576
embed
77+
create("runtimeOnlyNonPublishable") {
78+
description = "Runtime only dependencies that are not published alongside the jar"
79+
canBeConsumed = false
80+
canBeResolved = false
81+
}
82+
create("devOnlyNonPublishable") {
83+
description = "Runtime and compiletime dependencies that are not published alongside the jar (compileOnly + runtimeOnlyNonPublishable)"
84+
canBeConsumed = false
85+
canBeResolved = false
86+
}
87+
88+
compileOnly.extendsFrom(devOnlyNonPublishable)
89+
runtimeOnlyNonPublishable.extendsFrom(devOnlyNonPublishable)
90+
runtimeClasspath.extendsFrom(runtimeOnlyNonPublishable)
91+
testRuntimeClasspath.extendsFrom(runtimeOnlyNonPublishable)
92+
6693
implementation.extendsFrom(embed)
6794
}
6895

@@ -230,9 +257,29 @@ idea {
230257
"1. Run Client"(Gradle) {
231258
taskNames = ["runClient"]
232259
}
260+
if (propertyBool('enable_java17_run_tasks')) {
261+
'1a. Run Hotswap Client (Java 17)'(Gradle) {
262+
taskNames = ['runClient17']
263+
envs=[HOTSWAP:"true"]
264+
}
265+
'1b. Run Hotswap Client (Java 21)'(Gradle) {
266+
taskNames = ['runClient21']
267+
envs=[HOTSWAP:"true"]
268+
}
269+
}
233270
"2. Run Server"(Gradle) {
234271
taskNames = ["runServer"]
235272
}
273+
if (propertyBool('enable_java17_run_tasks')) {
274+
'2a. Run Hotswap Server (Java 17)'(Gradle) {
275+
taskNames = ['runServer17']
276+
envs=[HOTSWAP:"true"]
277+
}
278+
'2b. Run Hotswap Server (Java 21)'(Gradle) {
279+
taskNames = ['runServer21']
280+
envs=[HOTSWAP:"true"]
281+
}
282+
}
236283
"3. Run Obfuscated Client"(Gradle) {
237284
taskNames = ["runObfClient"]
238285
}
@@ -354,3 +401,215 @@ idea.project.settings {
354401

355402
apply from: 'gradle/scripts/publishing.gradle'
356403
apply from: 'gradle/scripts/extra.gradle'
404+
405+
406+
if (propertyBool('enable_java17_run_tasks')) {
407+
408+
apply plugin: 'de.undercouch.download'
409+
410+
ext.java17Toolchain = (JavaToolchainSpec spec) -> {
411+
spec.languageVersion.set(JavaLanguageVersion.of(17))
412+
spec.vendor.set(JvmVendorSpec.matching("jetbrains"))
413+
}
414+
ext.java21Toolchain = (JavaToolchainSpec spec) -> {
415+
spec.languageVersion.set(JavaLanguageVersion.of(21))
416+
spec.vendor.set(JvmVendorSpec.matching("jetbrains"))
417+
}
418+
419+
ext.java17DependenciesCfg = configurations.create("java17Dependencies") {
420+
extendsFrom(configurations.getByName("runtimeClasspath")) // Ensure consistent transitive dependency resolution
421+
canBeConsumed = false
422+
}
423+
ext.java17PatchDependenciesCfg = configurations.create("java17PatchDependencies") {
424+
canBeConsumed = false
425+
}
426+
427+
dependencies {
428+
if (propertyString('mod_id') != 'lwjgl3ify') {
429+
java17Dependencies("io.github.twilightflower:lwjgl3ify:1.0.1")
430+
java17PatchDependencies("io.github.twilightflower:lwjgl3ify:1.0.1:forgePatches") {
431+
transitive = false
432+
}
433+
}
434+
}
435+
436+
ext.java17JvmArgs = [
437+
"-Dfile.encoding=UTF-8",
438+
"-Djava.system.class.loader=com.gtnewhorizons.retrofuturabootstrap.RfbSystemClassLoader",
439+
"-Djava.security.manager=allow",
440+
"--add-opens", "java.base/jdk.internal.loader=ALL-UNNAMED",
441+
"--add-opens", "java.base/java.net=ALL-UNNAMED",
442+
"--add-opens", "java.base/java.nio=ALL-UNNAMED",
443+
"--add-opens", "java.base/java.io=ALL-UNNAMED",
444+
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
445+
"--add-opens", "java.base/java.lang.reflect=ALL-UNNAMED",
446+
"--add-opens", "java.base/java.text=ALL-UNNAMED",
447+
"--add-opens", "java.base/java.util=ALL-UNNAMED",
448+
"--add-opens", "java.base/jdk.internal.reflect=ALL-UNNAMED",
449+
"--add-opens", "java.base/sun.nio.ch=ALL-UNNAMED",
450+
"--add-opens", "jdk.naming.dns/com.sun.jndi.dns=ALL-UNNAMED,java.naming",
451+
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED",
452+
"--add-opens", "java.desktop/sun.awt.image=ALL-UNNAMED",
453+
"--add-opens", "java.desktop/com.sun.imageio.plugins.png=ALL-UNNAMED",
454+
"--add-opens", "jdk.dynalink/jdk.dynalink.beans=ALL-UNNAMED",
455+
"--add-opens", "java.sql.rowset/javax.sql.rowset.serial=ALL-UNNAMED"
456+
]
457+
458+
ext.hotswapJvmArgs = [
459+
// DCEVM advanced hot reload
460+
"-XX:+AllowEnhancedClassRedefinition",
461+
"-XX:HotswapAgent=fatjar"
462+
]
463+
464+
ext.setupHotswapAgent17 = tasks.register("setupHotswapAgent17", SetupHotswapAgentTask, t -> {
465+
t.setTargetForToolchain(java17Toolchain)
466+
})
467+
468+
ext.setupHotswapAgent21 = tasks.register("setupHotswapAgent21", SetupHotswapAgentTask, t -> {
469+
t.setTargetForToolchain(java21Toolchain)
470+
})
471+
472+
def runClient17Task = tasks.register("runClient17", RunHotswappableMinecraftTask, Distribution.CLIENT, "runClient")
473+
runClient17Task.configure {
474+
dependsOn(setupHotswapAgent17)
475+
setup(project)
476+
javaLauncher = project.javaToolchains.launcherFor(project.java17Toolchain)
477+
}
478+
479+
def runServer17Task = tasks.register("runServer17", RunHotswappableMinecraftTask, Distribution.DEDICATED_SERVER, "runServer")
480+
runServer17Task.configure {
481+
dependsOn(setupHotswapAgent17)
482+
setup(project)
483+
javaLauncher = project.javaToolchains.launcherFor(project.java17Toolchain)
484+
}
485+
486+
def runClient21Task = tasks.register("runClient21", RunHotswappableMinecraftTask, Distribution.CLIENT, "runClient")
487+
runClient21Task.configure {
488+
dependsOn(setupHotswapAgent21)
489+
setup(project)
490+
javaLauncher = project.javaToolchains.launcherFor(project.java21Toolchain)
491+
}
492+
493+
def runServer21Task = tasks.register("runServer21", RunHotswappableMinecraftTask, Distribution.DEDICATED_SERVER, "runServer")
494+
runServer21Task.configure {
495+
dependsOn(setupHotswapAgent21)
496+
setup(project)
497+
javaLauncher = project.javaToolchains.launcherFor(project.java21Toolchain)
498+
}
499+
}
500+
501+
abstract class RunHotswappableMinecraftTask extends RunMinecraftTask {
502+
503+
// IntelliJ doesn't seem to allow pre-set commandline arguments, so we also support an env variable
504+
private boolean enableHotswap = Boolean.valueOf(System.getenv("HOTSWAP"))
505+
506+
public final Distribution side
507+
public final String superTask
508+
509+
@Input
510+
boolean getEnableHotswap() {
511+
return enableHotswap
512+
}
513+
514+
@Option(option = "hotswap", description = "Enables HotSwapAgent for enhanced class reloading under a debugger")
515+
boolean setEnableHotswap(boolean enable) {
516+
enableHotswap = enable
517+
}
518+
519+
@Inject
520+
RunHotswappableMinecraftTask(Distribution side, String superTask, org.gradle.api.invocation.Gradle gradle) {
521+
super(side, gradle)
522+
523+
this.side = side
524+
this.superTask = superTask
525+
setGroup("Modded Minecraft")
526+
setDescription("Runs the modded " + side.name().toLowerCase(Locale.ROOT) + " using modern Java and lwjgl3ify")
527+
this.getLwjglVersion().set(3)
528+
}
529+
530+
void setup(Project project) {
531+
final MinecraftExtension minecraft = project.getExtensions().getByType(MinecraftExtension.class)
532+
final MCPTasks mcpTasks = project.getExtensions().getByType(MCPTasks.class)
533+
final MinecraftTasks mcTasks = project.getExtensions().getByType(MinecraftTasks.class)
534+
535+
this.getExtraJvmArgs().addAll((List<String>) project.property("java17JvmArgs"))
536+
if (getEnableHotswap()) {
537+
this.getExtraJvmArgs().addAll((List<String>) project.property("hotswapJvmArgs"))
538+
}
539+
540+
this.classpath(project.property("java17PatchDependenciesCfg"))
541+
this.classpath(mcpTasks.getTaskPackageMcLauncher())
542+
this.classpath(mcpTasks.getTaskPackagePatchedMc())
543+
this.classpath(mcpTasks.getPatchedConfiguration())
544+
this.classpath(project.getTasks().named("jar"))
545+
this.classpath(project.property("java17DependenciesCfg"))
546+
547+
super.setup(project)
548+
549+
dependsOn(
550+
mcpTasks.getLauncherSources().getClassesTaskName(),
551+
mcTasks.getTaskDownloadVanillaAssets(),
552+
mcpTasks.getTaskPackagePatchedMc(),
553+
"jar"
554+
)
555+
556+
getMainClass().set((side == Distribution.CLIENT) ? "GradleStart" : "GradleStartServer")
557+
getUsername().set(minecraft.getUsername())
558+
getUserUUID().set(minecraft.getUserUUID())
559+
if (side == Distribution.DEDICATED_SERVER) {
560+
getExtraArgs().add("nogui")
561+
}
562+
563+
systemProperty("gradlestart.bouncerClient", "com.gtnewhorizons.retrofuturabootstrap.Main")
564+
systemProperty("gradlestart.bouncerServer", "com.gtnewhorizons.retrofuturabootstrap.Main")
565+
566+
if (project.use_mixins.toBoolean()) {
567+
this.extraJvmArgs.addAll(project.provider(() -> {
568+
def mixinCfg = project.configurations.detachedConfiguration(project.dependencies.create(project.mixinProviderSpec))
569+
mixinCfg.canBeConsumed = false
570+
mixinCfg.canBeResolved = true
571+
mixinCfg.transitive = false
572+
enableHotswap ? ["-javaagent:" + mixinCfg.singleFile.absolutePath] : []
573+
}))
574+
}
575+
}
576+
}
577+
578+
abstract class SetupHotswapAgentTask extends DefaultTask {
579+
580+
@OutputFile
581+
abstract RegularFileProperty getTargetFile()
582+
583+
void setTargetForToolchain(Action<JavaToolchainSpec> spec) {
584+
getTargetFile().set(project.javaToolchains.launcherFor(spec).map {
585+
it.metadata.installationPath.file("lib/hotswap/hotswap-agent.jar")
586+
})
587+
}
588+
589+
@Inject
590+
SetupHotswapAgentTask() {
591+
setGroup("GT Buildscript")
592+
setDescription("Installs a recent version of HotSwapAgent into the Java runtime directory")
593+
onlyIf("Run only if not already installed", t -> !((SetupHotswapAgentTask) t).getTargetFile().getAsFile().get().exists())
594+
}
595+
596+
@TaskAction
597+
void installHSA() {
598+
final String url = 'https://github.com/HotswapProjects/HotswapAgent/releases/download/RELEASE-2.0.3/hotswap-agent-2.0.3.jar'
599+
final File target = getTargetFile().getAsFile().get()
600+
final File parent = target.getParentFile()
601+
FileUtils.forceMkdir(parent)
602+
final DownloadExtension download = getProject().getExtensions().findByType(DownloadExtension.class)
603+
download.run(ds -> {
604+
try {
605+
ds.src(url)
606+
} catch (MalformedURLException e) {
607+
throw new RuntimeException(e)
608+
}
609+
ds.dest(target)
610+
ds.overwrite(false)
611+
ds.tempAndMove(true)
612+
})
613+
}
614+
}
615+

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ org.gradle.caching = true
55

66
# Source Options
77
use_modern_java_syntax = false
8+
enable_java17_run_tasks = true
89

910
# Compilation Options
1011
generate_sources_jar = true

gradle/scripts/dependencies.gradle

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,26 @@ repositories {
3333
metadataSources { artifact() }
3434
}
3535

36+
maven {
37+
name = 'BlameJared Maven'
38+
url = 'https://maven.blamejared.com'
39+
}
40+
maven {
41+
name = 'GTCEu Maven'
42+
url = 'https://maven.gtceu.com'
43+
}
44+
3645
mavenLocal() // Must be last for caching to work
3746
}
38-
47+
/**
48+
* NOT DEPENDENCIES:
49+
* - runtimeOnly: not required at all for the mod, added for developer convenience to run targets
50+
* OPTIONAL DEPENDENCIES:
51+
* - compileOnly: will not be included in developer run targets
52+
* - devOnlyNonPublishable: will be included in developer run targets
53+
* REQUIRED DEPENDENCIES:
54+
* - implementation: mods or other jars that are required to be installed alongside this mod
55+
*/
3956
dependencies {
4057
// Include StripLatestForgeRequirements by default for the dev env
4158
runtimeOnly 'com.cleanroommc:strip-latest-forge-requirements:1.0'
@@ -45,28 +62,28 @@ dependencies {
4562
// ---- Required dependencies ----
4663

4764
// AE2 Extended Life (AE2-UEL)
48-
implementation rfg.deobf("curse.maven:ae2-extended-life-570458:5378163")
65+
implementation rfg.deobf("curse.maven:ae2-extended-life-570458:6302098")
4966

5067
// Baubles (required by AE2)
5168
implementation rfg.deobf("curse.maven:baubles-227083:2518667")
5269

5370
// ---- Optional: JEI ----
54-
implementation rfg.deobf("curse.maven:jei-238222:4538010")
71+
devOnlyNonPublishable 'mezz.jei:jei_1.12.2:4.16.1.302'
5572

5673
// ---- Optional: AE2WUT (Wireless Universal Terminal) ----
57-
implementation rfg.deobf("curse.maven:ae2uel-wireless-universal-terminal-1196988:7366371")
74+
devOnlyNonPublishable rfg.deobf("curse.maven:ae2uel-wireless-universal-terminal-1196988:7366371")
5875

5976
// ---- Optional: Thaumic Energistics ----
60-
implementation rfg.deobf("curse.maven:thaumcraft-223628:2629023")
61-
implementation rfg.deobf("curse.maven:thaumic-energistics-223666:2915506")
77+
devOnlyNonPublishable rfg.deobf("curse.maven:thaumcraft-223628:2629023")
78+
devOnlyNonPublishable rfg.deobf("curse.maven:thaumic-energistics-223666:2915506")
6279

6380
// ---- Optional: CrazyAE ----
64-
implementation rfg.deobf("curse.maven:crazyae-1111042:6840104")
81+
devOnlyNonPublishable rfg.deobf("curse.maven:crazyae-1111042:6840104")
6582

6683
// ---- Optional: ECO AE Extension ----
67-
implementation "curse.maven:modularmachinery-community-edition-817377:7372951"
68-
implementation "curse.maven:geckolib-388172:4020277"
69-
implementation ':Nova-Engineering-ECO-AE-Extension-1.2.0@jar'
84+
compileOnly "curse.maven:modularmachinery-community-edition-817377:7372951"
85+
compileOnly "curse.maven:geckolib-388172:4020277"
86+
compileOnly ':Nova-Engineering-ECO-AE-Extension-1.2.0@jar'
7087

7188
// ---- Test dependencies ----
7289
// JUnit 4 via the Vintage engine on JUnit Platform

settings.gradle

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,21 @@ pluginManagement {
1515
}
1616
}
1717

18+
buildscript {
19+
repositories {
20+
mavenCentral()
21+
}
22+
dependencies {
23+
classpath group: 'commons-io', name: 'commons-io', version: '2.21.0'
24+
}
25+
}
26+
1827
plugins {
1928
// Automatic toolchain provisioning
2029
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
2130
}
2231

2332
// Different repo name from mod name
2433
rootProject.name = 'cell-terminal'
34+
35+

0 commit comments

Comments
 (0)