Compare commits

..

3 commits
main ... work

Author SHA1 Message Date
Kyle Wood
0ae3814cf5
broken 2021-01-17 02:46:32 -08:00
Kyle Wood
2c4b1e1409 Run paperclip patch process in separate process 2021-01-02 23:06:31 -08:00
Kyle Wood
c4eb91fe8f work 2021-01-02 21:55:01 -08:00
188 changed files with 4777 additions and 16416 deletions

View file

@ -1,85 +1,4 @@
# noinspection EditorConfigKeyCorrectness
[*.{kt,kts}]
max_line_length = 150
ij_kotlin_imports_layout = *
no-wildcard-imports = no-wildcard-imports
ij_continuation_indent_size = 4
ij_kotlin_packages_to_use_import_on_demand = io.papermc.paperweight.util.**, io.papermc.paperweight.tasks.*, org.gradle.kotlin.dsl.*, kotlin.io.path.*
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true
ij_kotlin_space_after_type_colon = true
ij_kotlin_space_before_catch_parentheses = true
ij_kotlin_space_before_comma = false
ij_kotlin_space_before_extend_colon = true
ij_kotlin_space_before_for_parentheses = true
ij_kotlin_space_before_if_parentheses = true
ij_kotlin_space_before_lambda_arrow = true
ij_kotlin_space_before_type_colon = false
ij_kotlin_space_before_when_parentheses = true
ij_kotlin_space_before_while_parentheses = true
ij_kotlin_spaces_around_additive_operators = true
ij_kotlin_spaces_around_assignment_operators = true
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_spaces_around_function_type_arrow = true
ij_kotlin_spaces_around_logical_operators = true
ij_kotlin_spaces_around_multiplicative_operators = true
ij_kotlin_spaces_around_range = false
ij_kotlin_spaces_around_relational_operators = true
ij_kotlin_spaces_around_unary_operator = false
ij_kotlin_spaces_around_when_arrow = true
ij_kotlin_variable_annotation_wrap = off
ij_kotlin_while_on_new_line = false
ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
ij_kotlin_name_count_to_use_star_import = 8
ij_kotlin_name_count_to_use_star_import_for_members = 5
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
ij_kotlin_keep_blank_lines_in_declarations = 2
ij_kotlin_keep_first_column_comment = true
ij_kotlin_keep_indents_on_empty_lines = false
ij_kotlin_keep_line_breaks = true
ij_kotlin_lbrace_on_next_line = false
ij_kotlin_line_comment_add_space = false
ij_kotlin_line_comment_at_first_column = true
ij_kotlin_method_annotation_wrap = split_into_lines
ij_kotlin_method_call_chain_wrap = normal
ij_kotlin_method_parameters_new_line_after_left_paren = true
ij_kotlin_method_parameters_right_paren_on_new_line = true
ij_kotlin_method_parameters_wrap = on_every_item
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
ij_kotlin_align_multiline_method_parentheses = false
ij_kotlin_align_multiline_parameters = true
ij_kotlin_align_multiline_parameters_in_calls = false
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_assignment_wrap = normal
ij_kotlin_blank_lines_after_class_header = 0
ij_kotlin_blank_lines_around_block_when_branches = 0
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
ij_kotlin_block_comment_at_first_column = true
ij_kotlin_call_parameters_new_line_after_left_paren = true
ij_kotlin_call_parameters_right_paren_on_new_line = true
ij_kotlin_call_parameters_wrap = on_every_item
ij_kotlin_catch_on_new_line = false
ij_kotlin_class_annotation_wrap = split_into_lines
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_for_chained_calls = false
ij_kotlin_continuation_indent_for_expression_bodies = false
ij_kotlin_continuation_indent_in_argument_lists = false
ij_kotlin_continuation_indent_in_elvis = false
ij_kotlin_continuation_indent_in_if_conditions = false
ij_kotlin_continuation_indent_in_parameter_lists = false
ij_kotlin_continuation_indent_in_supertype_lists = false
ij_kotlin_else_on_new_line = false
ij_kotlin_enum_constants_wrap = off
ij_kotlin_extends_list_wrap = normal
ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = true
ij_kotlin_import_nested_classes = false
max_line_length=150
# noinspection EditorConfigKeyCorrectness
kotlin_imports_layout=ascii

19
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,19 @@
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-20.04
container:
image: adoptopenjdk:11-jdk-hotspot-focal
options: --user root
steps:
- uses: actions/checkout@v1
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- uses: gradle/wrapper-validation-action@v1
- run: ./gradlew build --no-daemon --stacktrace

View file

@ -1,35 +0,0 @@
name: Deploy Snapshot
on:
push:
branches: [ 'main' ]
paths-ignore:
- 'license/*'
- 'readme.md'
- '.gitignore'
- '.gitattributes'
- '.editorconfig'
jobs:
deploy:
name: Deploy Snapshot
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Get project version
id: get_version
shell: bash
run: |
project_version=$(./gradlew -q --console=plain printVersion --no-daemon)
echo version=$project_version >> $GITHUB_OUTPUT
- name: Deploy snapshot version
if: endsWith(steps.get_version.outputs.version, '-SNAPSHOT')
run: ./gradlew -Dorg.gradle.parallel=true publish --no-daemon --stacktrace
env:
ORG_GRADLE_PROJECT_paperUsername: ${{ secrets.DEPLOY_USER }}
ORG_GRADLE_PROJECT_paperPassword: ${{ secrets.DEPLOY_PASS }}

View file

@ -1,30 +1,24 @@
name: Deploy
on:
push:
tags: [ 'v*' ]
branches:
- main
jobs:
deploy:
name: Deploy
runs-on: 'ubuntu-latest'
runs-on: ubuntu-20.04
container:
image: adoptopenjdk:11-jdk-hotspot-focal
options: --user root
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
- uses: actions/checkout@v1
- uses: actions/cache@v2
with:
java-version: 17
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Deploy release
run: ./gradlew publishPlugins --no-daemon --stacktrace
path: |
~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- uses: gradle/wrapper-validation-action@v1
- run: ./gradlew publish --no-daemon --stacktrace -PdemonwavUsername=paperweight
env:
GRADLE_PUBLISH_KEY: "${{ secrets.GRADLE_PLUGIN_PORTAL_KEY }}"
GRADLE_PUBLISH_SECRET: "${{ secrets.GRADLE_PLUGIN_PORTAL_SECRET }}"
- name: Parse tag
id: vars
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Create release and changelog
uses: MC-Machinations/auto-release-changelog@v1.1.3
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: paperweight ${{ steps.vars.outputs.tag }}
ORG_GRADLE_PROJECT_demonwavPassword: ${{ secrets.paperweightToken }}

View file

@ -1,22 +0,0 @@
name: Test
on:
push:
branches: [ "**" ]
pull_request:
jobs:
test:
# Only run on PRs if the source branch is on someone else's repo
if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}
name: Test
runs-on: 'ubuntu-latest'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Execute Gradle build
run: ./gradlew build --no-daemon --stacktrace

4
.gitignore vendored
View file

@ -37,8 +37,8 @@ ehthumbs_vista.db
*.lnk
# Gradle
**/.gradle/
**/build/
.gradle
/build/
.gradletasknamecache
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)

View file

@ -1,9 +1,281 @@
tasks.register("printVersion") {
doFirst {
println(version)
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.publish.maven.MavenPom
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
idea
eclipse
`kotlin-dsl`
`maven-publish`
id("net.minecrell.licenser") version "0.4.1"
id("com.github.johnrengelman.shadow") version "6.0.0"
id("org.jlleitschuh.gradle.ktlint") version "9.4.1"
}
group = "io.papermc.paperweight"
version = "1.0.0-SNAPSHOT"
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
withSourcesJar()
}
val sourcesJar by tasks.existing
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.freeCompilerArgs = listOf("-Xjvm-default=enable")
}
gradlePlugin {
// we handle publications manually
isAutomatedPublishing = false
}
val shade: Configuration by configurations.creating
configurations.implementation {
extendsFrom(shade)
}
repositories {
mavenCentral()
maven("https://oss.sonatype.org/content/repositories/snapshots/") {
mavenContent {
includeGroup("org.cadixdev")
}
}
maven("https://repo.demonwav.com/snapshots/") {
mavenContent {
includeGroup("org.cadixdev")
}
}
maven("https://maven.fabricmc.net/") {
mavenContent {
includeGroup("net.fabricmc")
}
}
}
tasks.wrapper {
distributionType = Wrapper.DistributionType.ALL
dependencies {
shade("org.apache.httpcomponents:httpclient:4.5.13")
shade("com.github.salomonbrys.kotson:kotson:2.5.0")
// ASM for inspection
val asmVersion = "9.0"
shade("org.ow2.asm:asm:$asmVersion")
shade("org.ow2.asm:asm-tree:$asmVersion")
// Cadix
val lorenzVersion = "0.5.6"
shade("org.cadixdev:lorenz:$lorenzVersion")
shade("org.cadixdev:lorenz-asm:$lorenzVersion")
shade("org.cadixdev:lorenz-io-proguard:$lorenzVersion")
shade("org.cadixdev:atlas:0.2.0")
shade("org.cadixdev:at:0.1.0-rc1")
shade("org.cadixdev:mercury:0.1.0-rc2-PW-SNAPSHOT")
shade("net.fabricmc:lorenz-tiny:3.0.0")
shade("io.sigpipe:jbsdiff:1.0")
val jgraphtVersion = "1.5.0"
shade("org.jgrapht:jgrapht-core:$jgraphtVersion")
shade("org.jgrapht:jgrapht-io:$jgraphtVersion")
}
ktlint {
enableExperimentalRules.set(true)
disabledRules.add("no-wildcard-imports")
}
tasks.register("format") {
group = "formatting"
description = "Formats source code according to project style"
dependsOn(tasks.licenseFormat, tasks.ktlintFormat)
}
license {
header = file("license/copyright.txt")
include("**/*.kt")
}
idea {
module {
isDownloadSources = true
}
}
fun ShadowJar.configureStandard() {
configurations = listOf(shade)
dependencies {
exclude(dependency("org.jetbrains.kotlin:.*:.*"))
}
mergeServiceFiles()
}
tasks.shadowJar {
configureStandard()
val prefix = "paper.libs"
listOf(
"com.github.salomonbrys.kotson",
"com.google.gson",
"io.sigpipe",
"me.jamiemansfield",
"net.fabricmc",
"org.antlr",
"org.apache.commons.codec",
"org.apache.commons.compress",
"org.apache.commons.logging",
"org.apache.commons.text",
"org.apache.felix",
"org.apache.http",
"org.cadixdev",
"org.eclipse",
"org.jgrapht",
"org.jheaps",
"org.objectweb",
"org.osgi",
"org.tukaani"
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
}
val devShadowJar by tasks.registering(ShadowJar::class) {
configureStandard()
from(project.sourceSets.main.get().output)
archiveClassifier.set("dev")
}
val isSnapshot = version().endsWith("-SNAPSHOT")
publishing {
publications {
register<MavenPublication>("shadow") {
pluginConfig(version())
artifact(tasks.shadowJar) {
classifier = null
}
}
register<MavenPublication>("maven") {
standardConfig(version())
}
register<MavenPublication>("shadowLocal") {
pluginConfig(localVersion())
artifact(devShadowJar) {
classifier = null
}
}
register<MavenPublication>("mavenLocal") {
standardConfig(localVersion())
}
repositories {
val url = if (isSnapshot) {
"https://repo.demonwav.com/snapshots"
} else {
"https://repo.demonwav.com/releases"
}
maven(url) {
credentials(PasswordCredentials::class)
name = "demonwav"
}
}
}
}
tasks.withType(PublishToMavenRepository::class).configureEach {
onlyIf {
!publication.name.endsWith("Local")
}
}
tasks.withType(PublishToMavenLocal::class).configureEach {
onlyIf {
publication.name.endsWith("Local")
}
}
fun MavenPublication.standardConfig(versionName: String) {
groupId = project.group.toString()
artifactId = project.name
version = versionName
from(components["java"])
artifact(devShadowJar)
withoutBuildIdentifier()
pom {
pomConfig()
}
}
fun MavenPublication.pluginConfig(versionName: String) {
groupId = project.group.toString()
artifactId = "io.papermc.paperweight.gradle.plugin"
version = versionName
artifact(sourcesJar)
withoutBuildIdentifier()
pom {
pomConfig()
}
}
fun MavenPom.pomConfig() {
val repoPath = "PaperMC/paperweight"
val repoUrl = "https://github.com/$repoPath"
name.set("paperweight")
description.set("Gradle plugin for the PaperMC project")
url.set(repoUrl)
inceptionYear.set("2020")
packaging = "jar"
licenses {
license {
name.set("LGPLv2.1")
url.set("$repoUrl/blob/master/license/LGPLv2.1.txt")
distribution.set("repo")
}
}
issueManagement {
system.set("GitHub")
url.set("$repoUrl/issues")
}
developers {
developer {
id.set("DemonWav")
name.set("Kyle Wood")
email.set("demonwav@gmail.com")
url.set("https://github.com/DemonWav")
}
}
scm {
url.set(repoUrl)
connection.set("scm:git:$repoUrl.git")
developerConnection.set("scm:git:git@github.com:$repoPath.git")
}
}
fun version(): String {
return project.version.toString()
}
fun localVersion(): String {
return if (isSnapshot) {
version().substringBefore('-') + "-LOCAL-SNAPSHOT"
} else {
version() + "-LOCAL"
}
}

View file

@ -1,22 +0,0 @@
plugins {
`kotlin-dsl`
`kotlin-dsl-precompiled-script-plugins`
}
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
implementation(libs.gradle.licenser)
implementation(libs.gradle.spotless)
implementation(libs.gradle.shadow)
implementation(libs.gradle.kotlin.dsl)
implementation(libs.gradle.plugin.kotlin.withVersion(embeddedKotlinVersion))
implementation(libs.gradle.plugin.publish)
}
fun Provider<MinimalExternalModuleDependency>.withVersion(version: String): Provider<String> {
return map { "${it.module.group}:${it.module.name}:$version" }
}

View file

@ -1,9 +0,0 @@
rootProject.name = "buildSrc"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View file

@ -1,117 +0,0 @@
import com.diffplug.gradle.spotless.SpotlessExtension
import net.kyori.indra.licenser.spotless.IndraSpotlessLicenserExtension
plugins {
idea
id("org.gradle.kotlin.kotlin-dsl")
}
java {
withSourcesJar()
}
tasks.withType(JavaCompile::class).configureEach {
options.release = 11
}
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(17)
}
target {
compilations.configureEach {
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs = listOf("-Xjvm-default=all", "-Xjdk-release=11")
}
}
}
}
repositories {
maven("https://repo.papermc.io/repository/maven-snapshots/") {
mavenContent {
includeModule("org.cadixdev", "mercury")
}
}
maven("https://repo.papermc.io/repository/maven-public/") {
mavenContent {
includeGroup("codechicken")
includeGroup("net.fabricmc")
}
}
mavenCentral()
gradlePluginPortal()
}
dependencies {
compileOnly(gradleApi())
compileOnly(kotlin("stdlib-jdk8"))
}
testing {
suites {
val test by getting(JvmTestSuite::class) {
useKotlinTest(embeddedKotlinVersion)
dependencies {
implementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
}
}
}
}
configurations.all {
if (name == "compileOnly") {
return@all
}
dependencies.remove(project.dependencies.gradleApi())
dependencies.removeIf { it.group == "org.jetbrains.kotlin" }
}
tasks.jar {
manifest {
attributes(
"Implementation-Version" to project.version
)
}
}
// The following is to work around https://github.com/diffplug/spotless/issues/1599
// Ensure the ktlint step is before the license header step
plugins.apply("com.diffplug.spotless")
extensions.configure<SpotlessExtension> {
val overrides = mapOf(
"ktlint_standard_no-wildcard-imports" to "disabled",
"ktlint_standard_filename" to "disabled",
"ktlint_standard_trailing-comma-on-call-site" to "disabled",
"ktlint_standard_trailing-comma-on-declaration-site" to "disabled",
)
val ktlintVer = "0.50.0"
kotlin {
ktlint(ktlintVer).editorConfigOverride(overrides)
}
kotlinGradle {
ktlint(ktlintVer).editorConfigOverride(overrides)
}
}
plugins.apply("net.kyori.indra.licenser.spotless")
extensions.configure<IndraSpotlessLicenserExtension> {
licenseHeaderFile(rootProject.file("license/copyright.txt"))
newLine(true)
}
tasks.register("format") {
group = "formatting"
description = "Formats source code according to project style"
dependsOn(tasks.named("spotlessApply"))
}
idea {
module {
isDownloadSources = true
}
}

View file

@ -1,157 +0,0 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("org.jetbrains.kotlin.jvm")
id("com.github.johnrengelman.shadow")
id("com.gradle.plugin-publish")
}
fun version(): String = version.toString()
val noRelocate = project.hasProperty("disable-relocation")
if (noRelocate) {
if (version().contains("-SNAPSHOT")) {
version = version().substringBefore("-SNAPSHOT") + "-NO-RELOCATE-SNAPSHOT"
} else {
version = version() + "-NO-RELOCATE"
}
}
val shade: Configuration by configurations.creating
configurations.implementation {
extendsFrom(shade)
}
fun ShadowJar.configureStandard() {
configurations = listOf(shade)
dependencies {
exclude(dependency("org.jetbrains.kotlin:.*:.*"))
}
exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "OSGI-INF/**", "*.profile", "module-info.class", "ant_tasks/**")
mergeServiceFiles()
}
val sourcesJar by tasks.existing(AbstractArchiveTask::class) {
from(
zipTree(project(":nugget-lib").tasks
.named("sourcesJar", AbstractArchiveTask::class)
.flatMap { it.archiveFile })
) {
exclude("META-INF/**")
}
}
val prefix = project.name.substringAfter("nugget-")
gradlePlugin {
website.set("https://git.zontreck.com/ObsidianMC/nugget")
vcsUrl.set("https://git.zontreck.com/ObsidianMC/nugget")
plugins.create("nugget-$prefix") {
id = "io.papermc.paperweight." + prefix
displayName = "nugget $prefix"
tags.set(listOf("obsidian", "minecraft"))
}
}
val shadowJar by tasks.existing(ShadowJar::class) {
archiveClassifier.set(null as String?)
configureStandard()
inputs.property("noRelocate", noRelocate)
if (noRelocate) {
return@existing
}
val prefix = "obsidian.libs"
listOf(
"com.github.salomonbrys.kotson",
"com.google.errorprone.annotations",
"com.google.gson",
"dev.denwav.hypo",
"io.sigpipe.jbsdiff",
"me.jamiemansfield",
"net.fabricmc",
"org.apache.commons",
"org.apache.felix",
"org.apache.http",
"org.cadixdev",
"org.eclipse",
"org.jgrapht",
"org.jheaps",
"org.objectweb.asm",
"org.osgi",
"org.tukaani.xz",
"org.slf4j",
"codechicken.diffpatch",
"codechicken.repack"
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
}
val MAVEN_PASSWORD = "AriasCreationsMavenPassword"
val MAVEN_USER = "AriasCreationsMavenUser"
publishing {
repositories {
maven {
url = uri("https://git.zontreck.com/api/packages/ObsidianMC/maven")
name = "obsidian"
credentials {
username = project.findProperty(MAVEN_USER)?.toString()
password = project.findProperty(MAVEN_PASSWORD)?.toString()
}
}
}
publications {
withType(MavenPublication::class).configureEach {
pom {
pomConfig()
}
}
}
}
fun MavenPom.pomConfig() {
val repoPath = "ObsidianMC/nugget"
val repoUrl = "https://git.zontreck.com/$repoPath"
name.set("nugget")
description.set("Gradle plugin for the ObsidianMC project")
url.set(repoUrl)
inceptionYear.set("2020")
licenses {
license {
name.set("LGPLv2.1")
url.set("$repoUrl/blob/master/license/LGPLv2.1.txt")
distribution.set("repo")
}
}
issueManagement {
system.set("GitHub")
url.set("$repoUrl/issues")
}
developers {
developer {
id.set("DenWav")
name.set("Kyle Wood")
email.set("kyle@denwav.dev")
url.set("https://github.com/DenWav")
}
}
scm {
url.set(repoUrl)
connection.set("scm:git:$repoUrl.git")
developerConnection.set("scm:git:git@github.com:$repoPath.git")
}
}

View file

@ -1,5 +0,0 @@
group = com.zontreck.nugget
version = 1.7.4-SNAPSHOT
org.gradle.caching = true
org.gradle.parallel = true

View file

@ -1,46 +0,0 @@
[versions]
asm = "9.7"
lorenz = "0.5.8"
hypo = "1.2.4"
[libraries]
asm-core = { module = "org.ow2.asm:asm", version.ref = "asm" }
asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" }
httpclient = "org.apache.httpcomponents:httpclient:4.5.14"
kotson = "com.github.salomonbrys.kotson:kotson:2.5.0"
gson = "com.google.code.gson:gson:2.10.1"
cadix-lorenz-core = { module = "org.cadixdev:lorenz", version.ref = "lorenz" }
cadix-lorenz-asm = { module = "org.cadixdev:lorenz-asm", version.ref = "lorenz" }
cadix-lorenz-proguard = { module = "org.cadixdev:lorenz-io-proguard", version.ref = "lorenz" }
cadix-atlas = "org.cadixdev:atlas:0.2.1"
cadix-at = "org.cadixdev:at:0.1.0-rc1"
cadix-mercury = "org.cadixdev:mercury:0.1.2-paperweight-SNAPSHOT"
hypo-model = { module = "dev.denwav.hypo:hypo-model", version.ref = "hypo" }
hypo-core = { module = "dev.denwav.hypo:hypo-core", version.ref = "hypo" }
hypo-hydrate = { module = "dev.denwav.hypo:hypo-hydrate", version.ref = "hypo" }
hypo-asm-core = { module = "dev.denwav.hypo:hypo-asm", version.ref = "hypo" }
hypo-asm-hydrate = { module = "dev.denwav.hypo:hypo-asm-hydrate", version.ref = "hypo" }
hypo-mappings = { module = "dev.denwav.hypo:hypo-mappings", version.ref = "hypo" }
slf4j-jdk14 = "org.slf4j:slf4j-jdk14:1.7.32"
lorenzTiny = "net.fabricmc:lorenz-tiny:3.0.0"
jbsdiff = "io.sigpipe:jbsdiff:1.0"
diffpatch = "codechicken:DiffPatch:1.5.0.29"
# Gradle
gradle-licenser = "net.kyori:indra-licenser-spotless:3.1.3"
gradle-spotless = "com.diffplug.spotless:spotless-plugin-gradle:6.23.1"
gradle-shadow = "com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:8.1.1"
gradle-kotlin-dsl = "org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:4.3.0"
gradle-plugin-kotlin = { module = "org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin" }
gradle-plugin-publish = "com.gradle.publish:plugin-publish-plugin:1.2.1"
[bundles]
asm = ["asm-core", "asm-tree"]
cadix = ["cadix-lorenz-core", "cadix-lorenz-asm", "cadix-lorenz-proguard", "cadix-atlas", "cadix-at", "cadix-mercury"]
hypo = ["hypo-model", "hypo-core", "hypo-hydrate", "hypo-asm-core", "hypo-asm-hydrate", "hypo-mappings"]
kotson = ["kotson", "gson"]

Binary file not shown.

View file

@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

282
gradlew vendored
View file

@ -1,7 +1,7 @@
#!/bin/sh
#!/usr/bin/env sh
#
# Copyright © 2015-2021 the original authors.
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,99 +17,67 @@
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
MAX_FD="maximum"
warn () {
echo "$*"
} >&2
}
die () {
echo
echo "$*"
echo
exit 1
} >&2
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -119,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD=$JAVA_HOME/bin/java
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -130,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=$( cygpath --unix "$JAVACMD" )
JAVACMD=`cygpath --unix "$JAVACMD"`
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

35
gradlew.bat vendored
View file

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -41,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
if "%ERRORLEVEL%" == "0" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@ -57,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View file

@ -1,6 +1,6 @@
paperweight is a Gradle plugin for the PaperMC project.
Copyright (c) 2023 Kyle Wood (DenWav)
Copyright (c) 2020 Kyle Wood (DemonWav)
Contributors
This library is free software; you can redistribute it and/or

View file

@ -1,17 +0,0 @@
plugins {
`config-kotlin`
`config-publish`
}
dependencies {
shade(projects.nuggetLib)
implementation(libs.bundles.kotson)
}
gradlePlugin {
plugins.all {
description = "Gradle plugin for developing Paper"
implementationClass = "io.papermc.paperweight.core.PaperweightCore"
}
}

View file

@ -1,244 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.core
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.core.extension.PaperweightCoreExtension
import io.papermc.paperweight.core.taskcontainers.AllTasks
import io.papermc.paperweight.core.tasks.PaperweightCorePrepareForDownstream
import io.papermc.paperweight.taskcontainers.BundlerJarTasks
import io.papermc.paperweight.taskcontainers.DevBundleTasks
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.tasks.patchremap.RemapPatches
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.io.File
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.logging.Logging
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.*
class PaperweightCore : Plugin<Project> {
companion object {
private val logger = Logging.getLogger(PaperweightCore::class.java)
}
override fun apply(target: Project) {
checkJavaVersion()
Git.checkForGit()
printId<PaperweightCore>("nugget-core", target.gradle)
val ext = target.extensions.create(PAPERWEIGHT_EXTENSION, PaperweightCoreExtension::class, target)
target.gradle.sharedServices.registerIfAbsent(DOWNLOAD_SERVICE_NAME, DownloadService::class) {}
target.tasks.register<Delete>("cleanCache") {
group = "paper"
description = "Delete the project setup cache and task outputs."
delete(target.layout.cache)
}
// Make sure the submodules are initialized, since there are files there
// which are required for configuration
target.layout.maybeInitSubmodules(target.offlineMode(), logger)
target.configurations.create(PARAM_MAPPINGS_CONFIG)
target.configurations.create(REMAPPER_CONFIG)
target.configurations.create(DECOMPILER_CONFIG)
target.configurations.create(PAPERCLIP_CONFIG)
if (target.providers.gradleProperty("nugget.dev").orNull == "true") {
target.tasks.register<CreateDiffOutput>("diff") {
inputDir.convention(ext.paper.paperServerDir.map { it.dir("src/main/java") })
val prop = target.providers.gradleProperty("paperweight.diff.output")
if (prop.isPresent) {
baseDir.convention(target.layout.projectDirectory.dir(prop))
}
}
}
val tasks = AllTasks(target)
val devBundleTasks = DevBundleTasks(target)
val bundlerJarTasks = BundlerJarTasks(
target,
ext.bundlerJarName,
ext.mainClass
)
target.createPatchRemapTask(tasks)
target.tasks.register<PaperweightCorePrepareForDownstream>(PAPERWEIGHT_PREPARE_DOWNSTREAM) {
dependsOn(tasks.applyPatches)
vanillaJar.set(tasks.downloadServerJar.flatMap { it.outputJar })
remappedJar.set(tasks.lineMapJar.flatMap { it.outputJar })
decompiledJar.set(tasks.decompileJar.flatMap { it.outputJar })
mcVersion.set(target.ext.minecraftVersion)
mcLibrariesFile.set(tasks.extractFromBundler.flatMap { it.serverLibrariesTxt })
mcLibrariesDir.set(tasks.extractFromBundler.flatMap { it.serverLibraryJars })
mcLibrariesSourcesDir.set(tasks.downloadMcLibrariesSources.flatMap { it.outputDir })
spigotLibrariesSourcesDir.set(tasks.downloadSpigotDependencies.flatMap { it.outputSourcesDir })
mappings.set(tasks.patchMappings.flatMap { it.outputMappings })
notchToSpigotMappings.set(tasks.generateSpigotMappings.flatMap { it.notchToSpigotMappings })
sourceMappings.set(tasks.generateMappings.flatMap { it.outputMappings })
reobfPackagesToFix.set(ext.paper.reobfPackagesToFix)
reobfMappingsPatch.set(ext.paper.reobfMappingsPatch)
vanillaJarIncludes.set(ext.vanillaJarIncludes)
paramMappingsUrl.set(ext.paramMappingsRepo)
paramMappingsConfig.set(target.configurations.named(PARAM_MAPPINGS_CONFIG))
atFile.set(tasks.mergeAdditionalAts.flatMap { it.outputFile })
spigotRecompiledClasses.set(tasks.remapSpigotSources.flatMap { it.spigotRecompiledClasses })
bundlerVersionJson.set(tasks.extractFromBundler.flatMap { it.versionJson })
serverLibrariesTxt.set(tasks.extractFromBundler.flatMap { it.serverLibrariesTxt })
serverLibrariesList.set(tasks.extractFromBundler.flatMap { it.serverLibrariesList })
dataFile.set(
target.layout.file(
providers.gradleProperty(PAPERWEIGHT_DOWNSTREAM_FILE_PROPERTY).map { File(it) }
)
)
}
target.afterEvaluate {
target.repositories {
maven(ext.paramMappingsRepo) {
name = PARAM_MAPPINGS_REPO_NAME
content { onlyForConfigurations(PARAM_MAPPINGS_CONFIG) }
}
maven(ext.remapRepo) {
name = REMAPPER_REPO_NAME
content { onlyForConfigurations(REMAPPER_CONFIG) }
}
maven(ext.decompileRepo) {
name = DECOMPILER_REPO_NAME
content { onlyForConfigurations(DECOMPILER_CONFIG) }
}
}
// Setup the server jar
val cache = target.layout.cache
val serverProj = target.ext.serverProject.orNull ?: return@afterEvaluate
val serverJar = serverProj.tasks.register("serverJar", Zip::class)
tasks.generateReobfMappings {
inputJar.set(serverJar.flatMap { it.archiveFile })
}
tasks.generateRelocatedReobfMappings {
inputJar.set(serverJar.flatMap { it.archiveFile })
}
val (includeMappings, reobfJar) = serverProj.setupServerProject(
target,
tasks.lineMapJar.flatMap { it.outputJar },
tasks.decompileJar.flatMap { it.outputJar },
ext.mcDevSourceDir.path,
cache.resolve(SERVER_LIBRARIES_TXT),
ext.paper.reobfPackagesToFix,
tasks.generateRelocatedReobfMappings,
serverJar
) ?: return@afterEvaluate
devBundleTasks.configure(
ext.serverProject.get(),
ext.bundlerJarName.get(),
ext.mainClass,
ext.minecraftVersion,
tasks.decompileJar.map { it.outputJar.path },
tasks.extractFromBundler.map { it.serverLibrariesTxt.path },
tasks.extractFromBundler.map { it.serverLibrariesList.path },
tasks.downloadServerJar.map { it.outputJar.path },
tasks.mergeAdditionalAts.map { it.outputFile.path },
tasks.extractFromBundler.map { it.versionJson.path }.convertToFileProvider(layout, providers)
) {
vanillaJarIncludes.set(ext.vanillaJarIncludes)
reobfMappingsFile.set(tasks.generateRelocatedReobfMappings.flatMap { it.outputMappings })
paramMappingsCoordinates.set(
target.provider {
determineArtifactCoordinates(target.configurations.getByName(PARAM_MAPPINGS_CONFIG)).single()
}
)
paramMappingsUrl.set(ext.paramMappingsRepo)
}
devBundleTasks.configureAfterEvaluate()
bundlerJarTasks.configureBundlerTasks(
tasks.extractFromBundler.flatMap { it.versionJson },
tasks.extractFromBundler.flatMap { it.serverLibrariesList },
tasks.downloadServerJar.flatMap { it.outputJar },
includeMappings.flatMap { it.outputJar },
reobfJar,
ext.minecraftVersion
)
}
}
private fun Project.createPatchRemapTask(allTasks: AllTasks) {
val extension: PaperweightCoreExtension = ext
/*
* To ease the waiting time for debugging this task, all of the task dependencies have been removed (notice all
* of those .get() calls). This means when you make changes to paperweight Gradle won't know that this task
* technically depends on the output of all of those other tasks.
*
* In order to run all of the other necessary tasks before running this task in order to setup the inputs, run:
*
* ./gradlew patchPaper applyVanillaSrgAt
*
* Then you should be able to run `./gradlew remapPatches` without having to worry about all of the other tasks
* running whenever you make changes to paperweight.
*/
@Suppress("UNUSED_VARIABLE")
val remapPatches: TaskProvider<RemapPatches> by tasks.registering<RemapPatches> {
group = "paperweight"
description = "NOT FOR TYPICAL USE: Attempt to remap Paper's patches from Spigot mappings to Mojang mappings."
inputPatchDir.set(extension.paper.unmappedSpigotServerPatchDir)
apiPatchDir.set(extension.paper.spigotApiPatchDir)
mappingsFile.set(allTasks.patchMappings.flatMap { it.outputMappings }.get())
ats.set(allTasks.remapSpigotSources.flatMap { it.generatedAt }.get())
// Pull in as many jars as possible to reduce the possibility of type bindings not resolving
classpathJars.from(allTasks.applyMergedAt.flatMap { it.outputJar }.get()) // final remapped jar
classpathJars.from(allTasks.remapSpigotSources.flatMap { it.vanillaRemappedSpigotJar }.get()) // Spigot remapped jar
classpathJars.from(allTasks.extractFromBundler.flatMap { it.serverJar }.get()) // pure vanilla jar
spigotApiDir.set(allTasks.patchSpigotApi.flatMap { it.outputDir }.get())
spigotServerDir.set(allTasks.patchSpigotServer.flatMap { it.outputDir }.get())
spigotDecompJar.set(allTasks.spigotDecompileJar.flatMap { it.outputJar }.get())
// library class imports
mcLibrarySourcesDir.set(allTasks.downloadMcLibrariesSources.flatMap { it.outputDir }.get())
devImports.set(extension.paper.devImports)
outputPatchDir.set(extension.paper.remappedSpigotServerPatchDir)
}
}
}

View file

@ -1,183 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.core.taskcontainers
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.core.ext
import io.papermc.paperweight.core.extension.PaperweightCoreExtension
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.*
@Suppress("MemberVisibilityCanBePrivate")
open class AllTasks(
project: Project,
tasks: TaskContainer = project.tasks,
cache: Path = project.layout.cache,
extension: PaperweightCoreExtension = project.ext,
downloadService: Provider<DownloadService> = project.download
) : SpigotTasks(project) {
val mergeAdditionalAts by tasks.registering<MergeAccessTransforms> {
firstFile.set(mergeGeneratedAts.flatMap { it.outputFile })
secondFile.set(mergePaperAts.flatMap { it.outputFile })
}
val applyMergedAt by tasks.registering<ApplyAccessTransform> {
inputJar.set(fixJar.flatMap { it.outputJar })
atFile.set(mergeAdditionalAts.flatMap { it.outputFile })
}
val copyResources by tasks.registering<CopyResources> {
inputJar.set(applyMergedAt.flatMap { it.outputJar })
vanillaJar.set(extractFromBundler.flatMap { it.serverJar })
}
val decompileJar by tasks.registering<RunVineFlower> {
executable.from(project.configurations.named(DECOMPILER_CONFIG))
inputJar.set(copyResources.flatMap { it.outputJar })
libraries.from(extractFromBundler.map { it.serverLibraryJars.asFileTree })
outputJar.set(cache.resolve(FINAL_DECOMPILE_JAR))
}
val lineMapJar by tasks.registering<LineMapJar> {
inputJar.set(copyResources.flatMap { it.outputJar })
outputJar.set(cache.resolve(FINAL_REMAPPED_JAR))
decompiledJar.set(decompileJar.flatMap { it.outputJar })
}
val applyApiPatches by tasks.registering<ApplyGitPatches> {
group = "paper"
description = "Setup the Paper-API project"
if (project.isBaseExecution) {
doNotTrackState("$name should always run when requested as part of the base execution.")
}
printOutput.set(project.isBaseExecution)
branch.set("HEAD")
upstreamBranch.set("upstream")
upstream.set(patchSpigotApi.flatMap { it.outputDir })
patchDir.set(extension.paper.spigotApiPatchDir)
unneededFiles.value(listOf("README.md"))
outputDir.set(extension.paper.paperApiDir)
}
val downloadMcLibrariesSources by tasks.registering<DownloadMcLibraries> {
mcLibrariesFile.set(extractFromBundler.flatMap { it.serverLibrariesTxt })
repositories.set(listOf(MC_LIBRARY_URL, MAVEN_CENTRAL_URL))
outputDir.set(cache.resolve(MINECRAFT_SOURCES_PATH))
sources.set(true)
downloader.set(downloadService)
}
@Suppress("DuplicatedCode")
val applyServerPatches by tasks.registering<ApplyPaperPatches> {
group = "paper"
description = "Setup the Paper-Server project"
if (project.isBaseExecution) {
doNotTrackState("$name should always run when requested as part of the base execution.")
}
printOutput.set(project.isBaseExecution)
patchDir.set(extension.paper.spigotServerPatchDir)
remappedSource.set(remapSpigotSources.flatMap { it.sourcesOutputZip })
remappedTests.set(remapSpigotSources.flatMap { it.testsOutputZip })
caseOnlyClassNameChanges.set(cleanupSourceMappings.flatMap { it.caseOnlyNameChanges })
upstreamDir.set(patchSpigotServer.flatMap { it.outputDir })
sourceMcDevJar.set(decompileJar.flatMap { it.outputJar })
mcLibrariesDir.set(downloadMcLibrariesSources.flatMap { it.outputDir })
spigotLibrariesDir.set(downloadSpigotDependencies.flatMap { it.outputSourcesDir })
devImports.set(extension.paper.devImports.fileExists(project))
unneededFiles.value(listOf("nms-patches", "applyPatches.sh", "CONTRIBUTING.md", "makePatches.sh", "README.md"))
outputDir.set(extension.paper.paperServerDir)
mcDevSources.set(extension.mcDevSourceDir)
}
val applyPatches by tasks.registering<Task> {
group = "paper"
description = "Set up the Paper development environment"
dependsOn(applyApiPatches, applyServerPatches)
}
val rebuildApiPatches by tasks.registering<RebuildGitPatches> {
group = "paper"
description = "Rebuilds patches to api"
inputDir.set(extension.paper.paperApiDir)
baseRef.set("base")
patchDir.set(extension.paper.spigotApiPatchDir)
}
val rebuildServerPatches by tasks.registering<RebuildGitPatches> {
group = "paper"
description = "Rebuilds patches to server"
inputDir.set(extension.paper.paperServerDir)
baseRef.set("base")
patchDir.set(extension.paper.spigotServerPatchDir)
}
@Suppress("unused")
val rebuildPatches by tasks.registering<Task> {
group = "paper"
description = "Rebuilds patches to api and server"
dependsOn(rebuildApiPatches, rebuildServerPatches)
}
val generateReobfMappings by tasks.registering<GenerateReobfMappings> {
inputMappings.set(patchMappings.flatMap { it.outputMappings })
notchToSpigotMappings.set(generateSpigotMappings.flatMap { it.notchToSpigotMappings })
sourceMappings.set(generateMappings.flatMap { it.outputMappings })
spigotRecompiledClasses.set(remapSpigotSources.flatMap { it.spigotRecompiledClasses })
reobfMappings.set(cache.resolve(REOBF_MOJANG_SPIGOT_MAPPINGS))
}
val patchReobfMappings by tasks.registering<PatchMappings> {
inputMappings.set(generateReobfMappings.flatMap { it.reobfMappings })
patch.set(extension.paper.reobfMappingsPatch.fileExists(project))
fromNamespace.set(DEOBF_NAMESPACE)
toNamespace.set(SPIGOT_NAMESPACE)
outputMappings.set(cache.resolve(PATCHED_REOBF_MOJANG_SPIGOT_MAPPINGS))
}
val generateRelocatedReobfMappings by tasks.registering<GenerateRelocatedReobfMappings> {
inputMappings.set(patchReobfMappings.flatMap { it.outputMappings })
outputMappings.set(cache.resolve(RELOCATED_PATCHED_REOBF_MOJANG_SPIGOT_MAPPINGS))
}
}

View file

@ -1,64 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.core.taskcontainers
import com.github.salomonbrys.kotson.fromJson
import io.papermc.paperweight.core.ext
import io.papermc.paperweight.core.extension.PaperweightCoreExtension
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.*
@Suppress("MemberVisibilityCanBePrivate")
open class GeneralTasks(
project: Project,
tasks: TaskContainer = project.tasks,
extension: PaperweightCoreExtension = project.ext,
) : InitialTasks(project) {
// Configuration won't necessarily always run, so do it as the first task when it's needed as well
val initSubmodules by tasks.registering<InitSubmodules> {
offlineMode.set(project.offlineMode())
}
val buildDataInfo: Provider<BuildDataInfo> = project.contents(extension.craftBukkit.buildDataInfo) {
gson.fromJson(it)
}
val filterVanillaJar by tasks.registering<FilterJar> {
inputJar.set(extractFromBundler.flatMap { it.serverJar })
includes.set(extension.vanillaJarIncludes)
}
val collectAtsFromPatches by tasks.registering<CollectATsFromPatches> {
patchDir.set(extension.paper.spigotServerPatchDir)
}
val mergePaperAts by tasks.registering<MergeAccessTransforms> {
firstFile.set(extension.paper.additionalAts.fileExists(project))
secondFile.set(collectAtsFromPatches.flatMap { it.outputFile })
}
}

View file

@ -1,98 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.core.taskcontainers
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.core.ext
import io.papermc.paperweight.core.extension.PaperweightCoreExtension
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import io.papermc.paperweight.util.data.*
import java.nio.file.Path
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.*
@Suppress("MemberVisibilityCanBePrivate")
open class InitialTasks(
project: Project,
tasks: TaskContainer = project.tasks,
cache: Path = project.layout.cache,
extension: PaperweightCoreExtension = project.ext,
downloadService: Provider<DownloadService> = project.download
) {
val downloadMcManifest by tasks.registering<DownloadTask> {
url.set(MC_MANIFEST_URL)
outputFile.set(cache.resolve(MC_MANIFEST))
doNotTrackState("The Minecraft manifest is a changing resource")
downloader.set(downloadService)
}
private val mcManifest = downloadMcManifest.flatMap { it.outputFile }.map { gson.fromJson<MinecraftManifest>(it) }
val downloadMcVersionManifest by tasks.registering<DownloadTask> {
url.set(
mcManifest.zip(extension.minecraftVersion) { manifest, version ->
manifest.versions.first { it.id == version }.url
}
)
expectedHash.set(
mcManifest.zip(extension.minecraftVersion) { manifest, version ->
manifest.versions.first { it.id == version }.hash()
}
)
outputFile.set(cache.resolve(VERSION_JSON))
downloader.set(downloadService)
}
private val versionManifest = downloadMcVersionManifest.flatMap { it.outputFile }.map { gson.fromJson<MinecraftVersionManifest>(it) }
val downloadMappings by tasks.registering<DownloadTask> {
url.set(versionManifest.map { version -> version.serverMappingsDownload().url })
expectedHash.set(versionManifest.map { version -> version.serverMappingsDownload().hash() })
outputFile.set(cache.resolve(SERVER_MAPPINGS))
downloader.set(downloadService)
}
val downloadServerJar by tasks.registering<DownloadServerJar> {
downloadUrl.set(versionManifest.map { version -> version.serverDownload().url })
expectedHash.set(versionManifest.map { version -> version.serverDownload().hash() })
downloader.set(downloadService)
}
val extractFromBundler by tasks.registering<ExtractFromBundler> {
bundlerJar.set(downloadServerJar.flatMap { it.outputJar })
versionJson.set(cache.resolve(SERVER_VERSION_JSON))
serverLibrariesTxt.set(cache.resolve(SERVER_LIBRARIES_TXT))
serverLibrariesList.set(cache.resolve(SERVER_LIBRARIES_LIST))
serverVersionsList.set(cache.resolve(SERVER_VERSIONS_LIST))
serverLibraryJars.set(cache.resolve(MINECRAFT_JARS_PATH))
}
}

View file

@ -1,205 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.core.taskcontainers
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.core.ext
import io.papermc.paperweight.core.extension.PaperweightCoreExtension
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.*
@Suppress("MemberVisibilityCanBePrivate")
open class SpigotTasks(
project: Project,
tasks: TaskContainer = project.tasks,
cache: Path = project.layout.cache,
extension: PaperweightCoreExtension = project.ext,
downloadService: Provider<DownloadService> = project.download,
) : VanillaTasks(project) {
val addAdditionalSpigotMappings by tasks.registering<AddAdditionalSpigotMappings> {
dependsOn(initSubmodules)
classSrg.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.classMappings }))
additionalClassEntriesSrg.set(extension.paper.additionalSpigotClassMappings.fileExists(project))
}
val generateSpigotMappings by tasks.registering<GenerateSpigotMappings> {
classMappings.set(addAdditionalSpigotMappings.flatMap { it.outputClassSrg })
sourceMappings.set(generateMappings.flatMap { it.outputMappings })
outputMappings.set(cache.resolve(SPIGOT_MOJANG_YARN_MAPPINGS))
notchToSpigotMappings.set(cache.resolve(OBF_SPIGOT_MAPPINGS))
spigotMemberMappings.set(cache.resolve(SPIGOT_MEMBER_MAPPINGS))
}
val spigotRemapJar by tasks.registering<SpigotRemapJar> {
inputJar.set(filterVanillaJar.flatMap { it.outputJar })
classMappings.set(addAdditionalSpigotMappings.flatMap { it.outputClassSrg })
accessTransformers.set(extension.craftBukkit.mappingsDir.file(buildDataInfo.map { it.accessTransforms }))
memberMappings.set(generateSpigotMappings.flatMap { it.spigotMemberMappings })
mcVersion.set(extension.minecraftVersion)
workDirName.set(extension.craftBukkit.buildDataInfo.asFile.map { it.parentFile.parentFile.name })
specialSourceJar.set(extension.craftBukkit.specialSourceJar)
specialSource2Jar.set(extension.craftBukkit.specialSource2Jar)
classMapCommand.set(buildDataInfo.map { it.classMapCommand })
memberMapCommand.set(buildDataInfo.map { it.memberMapCommand })
finalMapCommand.set(buildDataInfo.map { it.finalMapCommand })
}
val cleanupMappings by tasks.registering<CleanupMappings> {
sourceJar.set(spigotRemapJar.flatMap { it.outputJar })
libraries.from(extractFromBundler.map { it.serverLibraryJars.asFileTree })
inputMappings.set(generateSpigotMappings.flatMap { it.outputMappings })
outputMappings.set(cache.resolve(CLEANED_SPIGOT_MOJANG_YARN_MAPPINGS))
}
val patchMappings by tasks.registering<PatchMappings> {
inputMappings.set(cleanupMappings.flatMap { it.outputMappings })
patch.set(extension.paper.mappingsPatch.fileExists(project))
fromNamespace.set(SPIGOT_NAMESPACE)
toNamespace.set(DEOBF_NAMESPACE)
outputMappings.set(cache.resolve(PATCHED_SPIGOT_MOJANG_YARN_MAPPINGS))
}
val cleanupSourceMappings by tasks.registering<CleanupSourceMappings> {
sourceJar.set(spigotRemapJar.flatMap { it.outputJar })
libraries.from(extractFromBundler.map { it.serverLibraryJars.asFileTree })
inputMappings.set(patchMappings.flatMap { it.outputMappings })
outputMappings.set(cache.resolve(PATCHED_SPIGOT_MOJANG_YARN_SOURCE_MAPPINGS))
}
val filterSpigotExcludes by tasks.registering<FilterSpigotExcludes> {
inputZip.set(spigotRemapJar.flatMap { it.outputJar })
excludesFile.set(extension.craftBukkit.excludesFile)
}
val spigotDecompileJar by tasks.registering<SpigotDecompileJar> {
inputJar.set(filterSpigotExcludes.flatMap { it.outputZip })
fernFlowerJar.set(extension.craftBukkit.fernFlowerJar)
decompileCommand.set(buildDataInfo.map { it.decompileCommand })
}
val patchCraftBukkitPatches by tasks.registering<ApplyRawDiffPatches> {
dependsOn(initSubmodules)
inputDir.set(extension.craftBukkit.patchDir)
patchDir.set(extension.paper.craftBukkitPatchPatchesDir.fileExists(project))
}
val patchCraftBukkit by tasks.registering<ApplyCraftBukkitPatches> {
sourceJar.set(spigotDecompileJar.flatMap { it.outputJar })
cleanDirPath.set("net/minecraft")
branch.set("patched")
patchZip.set(patchCraftBukkitPatches.flatMap { it.outputZip })
craftBukkitDir.set(extension.craftBukkit.craftBukkitDir)
}
val patchSpigotApiPatches by tasks.registering<ApplyRawDiffPatches> {
dependsOn(initSubmodules)
inputDir.set(extension.spigot.bukkitPatchDir)
patchDir.set(extension.paper.spigotApiPatchPatchesDir.fileExists(project))
}
val patchSpigotServerPatches by tasks.registering<ApplyRawDiffPatches> {
dependsOn(initSubmodules)
inputDir.set(extension.spigot.craftBukkitPatchDir)
patchDir.set(extension.paper.spigotServerPatchPatchesDir.fileExists(project))
}
val patchSpigotApi by tasks.registering<ApplyGitPatches> {
branch.set("HEAD")
upstreamBranch.set("upstream")
upstream.set(extension.craftBukkit.bukkitDir)
patchZip.set(patchSpigotApiPatches.flatMap { it.outputZip })
outputDir.set(extension.spigot.spigotApiDir)
}
val patchSpigotServer by tasks.registering<ApplyGitPatches> {
branch.set("HEAD")
upstreamBranch.set("upstream")
upstream.set(patchCraftBukkit.flatMap { it.outputDir })
patchZip.set(patchSpigotServerPatches.flatMap { it.outputZip })
outputDir.set(extension.spigot.spigotServerDir)
}
val patchSpigot by tasks.registering<Task> {
dependsOn(patchSpigotApi, patchSpigotServer)
}
val downloadSpigotDependencies by tasks.registering<DownloadSpigotDependencies> {
dependsOn(patchSpigot)
apiPom.set(patchSpigotApi.flatMap { it.outputDir.file("pom.xml") })
serverPom.set(patchSpigotServer.flatMap { it.outputDir.file("pom.xml") })
mcLibrariesFile.set(extractFromBundler.flatMap { it.serverLibrariesTxt })
outputDir.set(cache.resolve(SPIGOT_JARS_PATH))
outputSourcesDir.set(cache.resolve(SPIGOT_SOURCES_JARS_PATH))
downloader.set(downloadService)
}
val remapSpigotAt by tasks.registering<RemapSpigotAt> {
inputJar.set(spigotRemapJar.flatMap { it.outputJar })
mapping.set(patchMappings.flatMap { it.outputMappings })
spigotAt.set(extension.craftBukkit.atFile)
}
@Suppress("DuplicatedCode")
val remapSpigotSources by tasks.registering<RemapSources> {
spigotServerDir.set(patchSpigotServer.flatMap { it.outputDir })
spigotApiDir.set(patchSpigotApi.flatMap { it.outputDir })
mappings.set(cleanupSourceMappings.flatMap { it.outputMappings })
vanillaJar.set(extractFromBundler.flatMap { it.serverJar })
mojangMappedVanillaJar.set(fixJar.flatMap { it.outputJar })
vanillaRemappedSpigotJar.set(filterSpigotExcludes.flatMap { it.outputZip })
spigotDeps.from(downloadSpigotDependencies.map { it.outputDir.asFileTree })
additionalAts.set(mergePaperAts.flatMap { it.outputFile })
}
val remapGeneratedAt by tasks.registering<RemapAccessTransform> {
inputFile.set(remapSpigotSources.flatMap { it.generatedAt })
mappings.set(patchMappings.flatMap { it.outputMappings })
}
val mergeGeneratedAts by tasks.registering<MergeAccessTransforms> {
firstFile.set(remapGeneratedAt.flatMap { it.outputFile })
secondFile.set(remapSpigotAt.flatMap { it.outputFile })
}
}

View file

@ -1,63 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.core.taskcontainers
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import org.gradle.api.Project
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.*
@Suppress("MemberVisibilityCanBePrivate")
open class VanillaTasks(
project: Project,
tasks: TaskContainer = project.tasks,
cache: Path = project.layout.cache,
) : GeneralTasks(project) {
val generateMappings by tasks.registering<GenerateMappings> {
vanillaJar.set(filterVanillaJar.flatMap { it.outputJar })
libraries.from(extractFromBundler.map { it.serverLibraryJars.asFileTree })
vanillaMappings.set(downloadMappings.flatMap { it.outputFile })
paramMappings.fileProvider(project.configurations.named(PARAM_MAPPINGS_CONFIG).map { it.singleFile })
outputMappings.set(cache.resolve(MOJANG_YARN_MAPPINGS))
}
val remapJar by tasks.registering<RemapJar> {
inputJar.set(filterVanillaJar.flatMap { it.outputJar })
mappingsFile.set(generateMappings.flatMap { it.outputMappings })
fromNamespace.set(OBF_NAMESPACE)
toNamespace.set(DEOBF_NAMESPACE)
remapper.from(project.configurations.named(REMAPPER_CONFIG))
remapperArgs.set(TinyRemapper.minecraftRemapArgs)
}
val fixJar by tasks.registering<FixJarTask> {
inputJar.set(remapJar.flatMap { it.outputJar })
vanillaJar.set(extractFromBundler.flatMap { it.serverJar })
}
}

View file

@ -1,146 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.core.tasks
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.data.UpstreamData
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class PaperweightCorePrepareForDownstream : DefaultTask() {
@get:InputFile
abstract val vanillaJar: RegularFileProperty
@get:InputFile
abstract val remappedJar: RegularFileProperty
@get:InputFile
abstract val decompiledJar: RegularFileProperty
@get:Input
abstract val mcVersion: Property<String>
@get:InputDirectory
abstract val mcLibrariesDir: DirectoryProperty
@get:InputDirectory
abstract val mcLibrariesSourcesDir: DirectoryProperty
@get:InputDirectory
abstract val spigotLibrariesSourcesDir: DirectoryProperty
@get:Input
abstract val vanillaJarIncludes: ListProperty<String>
@get:InputFile
abstract val mcLibrariesFile: RegularFileProperty
@get:InputFile
abstract val mappings: RegularFileProperty
@get:InputFile
abstract val notchToSpigotMappings: RegularFileProperty
@get:InputFile
abstract val sourceMappings: RegularFileProperty
@get:Input
abstract val reobfPackagesToFix: ListProperty<String>
@get:InputFile
abstract val reobfMappingsPatch: RegularFileProperty
@get:OutputFile
abstract val dataFile: RegularFileProperty
@get:Inject
abstract val providers: ProviderFactory
@get:Input
abstract val paramMappingsUrl: Property<String>
@get:Classpath
abstract val paramMappingsConfig: Property<Configuration>
@get:InputFile
abstract val atFile: RegularFileProperty
@get:InputFile
abstract val spigotRecompiledClasses: RegularFileProperty
@get:InputFile
abstract val bundlerVersionJson: RegularFileProperty
@get:InputFile
abstract val serverLibrariesTxt: RegularFileProperty
@get:InputFile
abstract val serverLibrariesList: RegularFileProperty
@TaskAction
fun run() {
val dataFilePath = dataFile.path
dataFilePath.parent.createDirectories()
val data = UpstreamData(
vanillaJar.path,
remappedJar.path,
decompiledJar.path,
mcVersion.get(),
mcLibrariesDir.path,
mcLibrariesSourcesDir.path,
mcLibrariesFile.path,
spigotLibrariesSourcesDir.path,
mappings.path,
notchToSpigotMappings.path,
sourceMappings.path,
reobfPackagesToFix.get(),
reobfMappingsPatch.path,
vanillaJarIncludes.get(),
determineMavenDep(paramMappingsUrl, paramMappingsConfig),
atFile.path,
spigotRecompiledClasses.path,
bundlerVersionJson.path,
serverLibrariesTxt.path,
serverLibrariesList.path
)
dataFilePath.bufferedWriter(Charsets.UTF_8).use { writer ->
gson.toJson(data, writer)
}
}
}

View file

@ -1,30 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.core
import io.papermc.paperweight.core.extension.PaperweightCoreExtension
import io.papermc.paperweight.util.constants.*
import org.gradle.api.Project
val Project.ext: PaperweightCoreExtension
get() = extensions.getByName(PAPERWEIGHT_EXTENSION) as PaperweightCoreExtension

View file

@ -1,27 +0,0 @@
plugins {
`config-kotlin`
}
repositories {
gradlePluginPortal()
}
dependencies {
implementation(libs.httpclient)
implementation(libs.bundles.kotson)
// ASM for inspection
implementation(libs.bundles.asm)
implementation(libs.bundles.hypo)
implementation(libs.slf4j.jdk14) // slf4j impl for hypo
implementation(libs.bundles.cadix)
implementation(libs.lorenzTiny)
implementation(libs.jbsdiff)
implementation(variantOf(libs.diffpatch) { classifier("all") }) {
isTransitive = false
}
}

View file

@ -1,74 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight
import io.papermc.paperweight.extension.PaperweightSourceGeneratorExt
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import kotlin.io.path.*
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.*
abstract class PaperweightSourceGeneratorHelper : Plugin<Project> {
override fun apply(target: Project) = with(target) {
val ext = extensions.create("paperweight", PaperweightSourceGeneratorExt::class)
val applyAts by tasks.registering<ApplyAccessTransform> {
inputJar.set(rootProject.tasks.named<FixJarTask>("fixJar").flatMap { it.outputJar })
atFile.set(ext.atFile)
}
val copyResources by tasks.registering<CopyResources> {
inputJar.set(applyAts.flatMap { it.outputJar })
vanillaJar.set(rootProject.tasks.named<ExtractFromBundler>("extractFromBundler").flatMap { it.serverJar })
}
val libsFile = rootProject.layout.cache.resolve(SERVER_LIBRARIES_TXT)
val vanilla = configurations.register("vanillaServer") {
withDependencies {
dependencies {
val libs = libsFile.convertToPathOrNull()
if (libs != null && libs.exists()) {
libs.forEachLine { line ->
add(create(line))
}
}
}
}
}
dependencies {
vanilla.name(files(copyResources.flatMap { it.outputJar }))
}
afterEvaluate {
if (ext.addVanillaServerToImplementation.get()) {
configurations.named("implementation") {
extendsFrom(vanilla.get())
}
}
}
}
}

View file

@ -1,31 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.extension
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.kotlin.dsl.property
abstract class PaperweightServerExtension(objects: ObjectFactory) {
val craftBukkitPackageVersion: Property<String> = objects.property()
}

View file

@ -1,36 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.extension
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
@Suppress("LeakingThis")
abstract class PaperweightSourceGeneratorExt {
abstract val atFile: RegularFileProperty
abstract val addVanillaServerToImplementation: Property<Boolean>
init {
addVanillaServerToImplementation.convention(true)
}
}

View file

@ -1,156 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.taskcontainers
import com.google.gson.JsonObject
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.*
@Suppress("MemberVisibilityCanBePrivate")
class BundlerJarTasks(
project: Project,
private val bundlerJarName: Provider<String>,
private val mainClassString: Provider<String>,
) {
val createBundlerJar: TaskProvider<CreateBundlerJar>
val createPaperclipJar: TaskProvider<CreatePaperclipJar>
val createReobfBundlerJar: TaskProvider<CreateBundlerJar>
val createReobfPaperclipJar: TaskProvider<CreatePaperclipJar>
init {
val (createBundlerJar, createPaperclipJar) = project.createBundlerJarTask("mojmap")
val (createReobfBundlerJar, createReobfPaperclipJar) = project.createBundlerJarTask("reobf")
this.createBundlerJar = createBundlerJar
this.createPaperclipJar = createPaperclipJar
this.createReobfBundlerJar = createReobfBundlerJar
this.createReobfPaperclipJar = createReobfPaperclipJar
}
fun configureBundlerTasks(
bundlerVersionJson: Provider<RegularFile>,
serverLibrariesList: Provider<RegularFile>,
vanillaJar: Provider<RegularFile>,
mojangJar: Provider<RegularFile>,
reobfJar: TaskProvider<RemapJar>,
mcVersion: Provider<String>
) {
createBundlerJar.configureWith(
bundlerVersionJson,
serverLibrariesList,
vanillaJar,
mojangJar,
)
createReobfBundlerJar.configureWith(
bundlerVersionJson,
serverLibrariesList,
vanillaJar,
reobfJar.flatMap { it.outputJar },
)
createPaperclipJar.configureWith(vanillaJar, createBundlerJar, mcVersion)
createReobfPaperclipJar.configureWith(vanillaJar, createReobfBundlerJar, mcVersion)
}
private fun Project.createBundlerJarTask(
classifier: String = "",
): Pair<TaskProvider<CreateBundlerJar>, TaskProvider<CreatePaperclipJar>> {
val bundlerTaskName = "create${classifier.capitalized()}BundlerJar"
val paperclipTaskName = "create${classifier.capitalized()}PaperclipJar"
val bundlerJarTask = tasks.register<CreateBundlerJar>(bundlerTaskName) {
group = "paperweight"
description = "Build a runnable bundler jar"
paperclip.from(configurations.named(PAPERCLIP_CONFIG))
mainClass.set(mainClassString)
outputZip.set(layout.buildDirectory.file("libs/${jarName("bundler", classifier)}"))
}
val paperclipJarTask = tasks.register<CreatePaperclipJar>(paperclipTaskName) {
group = "paperweight"
description = "Build a runnable paperclip jar"
libraryChangesJson.set(bundlerJarTask.flatMap { it.libraryChangesJson })
outputZip.set(layout.buildDirectory.file("libs/${jarName("paperclip", classifier)}"))
}
return bundlerJarTask to paperclipJarTask
}
private fun Project.jarName(kind: String, classifier: String): String {
return listOfNotNull(
project.name,
kind,
project.version,
classifier.takeIf { it.isNotBlank() },
).joinToString("-") + ".jar"
}
private fun TaskProvider<CreateBundlerJar>.configureWith(
bundlerVersionJson: Provider<RegularFile>,
serverLibrariesListFile: Provider<RegularFile>,
vanillaJar: Provider<RegularFile>,
serverJar: Provider<RegularFile>,
) = this {
libraryArtifacts.set(project.configurations.named(SERVER_RUNTIME_CLASSPATH))
serverLibrariesList.set(serverLibrariesListFile)
vanillaBundlerJar.set(vanillaJar)
versionArtifacts {
registerVersionArtifact(
bundlerJarName.get(),
bundlerVersionJson,
serverJar
)
}
}
private fun TaskProvider<CreatePaperclipJar>.configureWith(
vanillaJar: Provider<RegularFile>,
createBundlerJar: TaskProvider<CreateBundlerJar>,
mcVers: Provider<String>
) = this {
originalBundlerJar.set(vanillaJar)
bundlerJar.set(createBundlerJar.flatMap { it.outputZip })
mcVersion.set(mcVers)
}
companion object {
fun NamedDomainObjectContainer<CreateBundlerJar.VersionArtifact>.registerVersionArtifact(
name: String,
versionJson: Provider<RegularFile>,
serverJar: Provider<RegularFile>
) = register(name) {
id.set(versionJson.map { gson.fromJson<JsonObject>(it)["id"].asString })
file.set(serverJar)
}
}
}

View file

@ -1,123 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.taskcontainers
import io.papermc.paperweight.taskcontainers.BundlerJarTasks.Companion.registerVersionArtifact
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.Project
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.*
@Suppress("MemberVisibilityCanBePrivate")
class DevBundleTasks(
project: Project,
tasks: TaskContainer = project.tasks,
) {
val serverBundlerForDevBundle by tasks.registering<CreateBundlerJar> {
paperclip.from(project.configurations.named(PAPERCLIP_CONFIG))
}
val paperclipForDevBundle by tasks.registering<CreatePaperclipJar> {
bundlerJar.set(serverBundlerForDevBundle.flatMap { it.outputZip })
libraryChangesJson.set(serverBundlerForDevBundle.flatMap { it.libraryChangesJson })
}
val generateDevelopmentBundle by tasks.registering<GenerateDevBundle> {
group = "paperweight"
remapperConfig.set(project.configurations.named(REMAPPER_CONFIG))
decompilerConfig.set(project.configurations.named(DECOMPILER_CONFIG))
devBundleFile.set(project.layout.buildDirectory.file("libs/paperweight-development-bundle-${project.version}.zip"))
ignoreUnsupportedEnvironment.set(project.providers.gradleProperty(GenerateDevBundle.unsupportedEnvironmentPropName).map { it.toBoolean() })
}
fun configure(
serverProj: Project,
bundlerJarName: String,
mainClassName: Property<String>,
minecraftVer: Provider<String>,
decompileJar: Provider<Path>,
serverLibrariesTxt: Provider<Path>,
serverLibrariesListFile: Provider<Path>,
vanillaBundlerJarFile: Provider<Path>,
accessTransformFile: Provider<Path>,
versionJsonFile: Provider<RegularFile>,
devBundleConfiguration: GenerateDevBundle.() -> Unit
) {
serverBundlerForDevBundle {
mainClass.set(mainClassName)
serverLibrariesList.pathProvider(serverLibrariesListFile)
vanillaBundlerJar.pathProvider(vanillaBundlerJarFile)
versionArtifacts {
registerVersionArtifact(
bundlerJarName,
versionJsonFile,
serverProj.tasks.named<IncludeMappings>("includeMappings").flatMap { it.outputJar }
)
}
}
paperclipForDevBundle {
originalBundlerJar.pathProvider(vanillaBundlerJarFile)
mcVersion.set(minecraftVer)
}
generateDevelopmentBundle {
sourceDir.set(serverProj.layout.projectDirectory.dir("src/main/java"))
minecraftVersion.set(minecraftVer)
mojangMappedPaperclipFile.set(paperclipForDevBundle.flatMap { it.outputZip })
vanillaServerLibraries.set(
serverLibrariesTxt.map { txt ->
txt.readLines(Charsets.UTF_8).filter { it.isNotBlank() }
}
)
serverVersion.set(serverProj.version.toString())
serverCoordinates.set(GenerateDevBundle.createCoordinatesFor(serverProj))
serverProject.set(serverProj)
runtimeConfiguration.set(project.configurations.named(SERVER_RUNTIME_CLASSPATH))
decompiledJar.pathProvider(decompileJar)
atFile.pathProvider(accessTransformFile)
devBundleConfiguration(this)
}
}
fun configureAfterEvaluate() {
generateDevelopmentBundle {
remapperUrl.set(project.repositories.named<MavenArtifactRepository>(REMAPPER_REPO_NAME).map { it.url.toString() })
decompilerUrl.set(project.repositories.named<MavenArtifactRepository>(DECOMPILER_REPO_NAME).map { it.url.toString() })
}
}
}

View file

@ -1,157 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import java.nio.file.Files
import java.nio.file.Path
import java.util.Date
import javax.inject.Inject
import kotlin.io.path.*
import kotlin.streams.asSequence
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
abstract class ApplyCraftBukkitPatches : ControllableOutputTask() {
@get:InputFile
abstract val sourceJar: RegularFileProperty
@get:Input
abstract val cleanDirPath: Property<String>
@get:Optional
@get:InputDirectory
abstract val patchDir: DirectoryProperty
@get:Optional
@get:InputFile
abstract val patchZip: RegularFileProperty
@get:Input
abstract val branch: Property<String>
@get:Input
abstract val ignoreGitIgnore: Property<Boolean>
@get:InputDirectory
abstract val craftBukkitDir: DirectoryProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Inject
abstract val providers: ProviderFactory
override fun init() {
printOutput.convention(false)
ignoreGitIgnore.convention(Git.ignoreProperty(providers)).finalizeValueOnRead()
outputDir.convention(project, defaultOutput("repo").path)
}
@TaskAction
fun run() {
Git.checkForGit()
outputDir.path.deleteRecursive()
outputDir.path.parent.let {
it.createDirectories()
val git = Git(it)
git("clone", "--no-hardlinks", craftBukkitDir.path.absolutePathString(), outputDir.path.absolutePathString()).setupOut().execute()
}
val git = Git(outputDir.path)
val basePatchDirFile = outputDir.path.resolve("src/main/java")
basePatchDirFile.resolve(cleanDirPath.get()).deleteRecursive()
val patchSource = patchDir.pathOrNull ?: patchZip.path // used for error messages
val rootPatchDir = patchDir.pathOrNull ?: patchZip.path.let { unzip(it, findOutputDir(it)) }
try {
if (!rootPatchDir.isDirectory()) {
throw PaperweightException("Patch directory does not exist $patchSource")
}
val patchList = Files.walk(rootPatchDir).use { it.asSequence().filter { file -> file.isRegularFile() }.toSet() }
if (patchList.isEmpty()) {
throw PaperweightException("No patch files found in $patchSource")
}
// Copy in patch targets
sourceJar.path.openZip().use { fs ->
for (file in patchList) {
val javaName = javaFileName(rootPatchDir, file)
val out = basePatchDirFile.resolve(javaName)
val sourcePath = fs.getPath(javaName)
out.parent.createDirectories()
sourcePath.copyTo(out)
}
}
git(*Git.add(ignoreGitIgnore, "src")).setupOut().execute()
git("commit", "-m", "Vanilla $ ${Date()}", "--author=Vanilla <auto@mated.null>").setupOut().execute()
// Apply patches
for (file in patchList) {
val javaName = javaFileName(rootPatchDir, file)
if (printOutput.get()) {
println("Patching ${javaName.removeSuffix(".java")}")
}
val dirPrefix = basePatchDirFile.relativeTo(outputDir.path).invariantSeparatorsPathString
git("apply", "--ignore-whitespace", "--directory=$dirPrefix", file.absolutePathString()).setupOut().execute()
}
git(*Git.add(ignoreGitIgnore, "src")).setupOut().execute()
git("commit", "-m", "CraftBukkit $ ${Date()}", "--author=CraftBukkit <auto@mated.null>").setupOut().execute()
} finally {
if (rootPatchDir != patchDir.pathOrNull) {
rootPatchDir.deleteRecursive()
}
}
}
private fun javaFileName(rootDir: Path, file: Path): String {
return file.relativeTo(rootDir).toString().replaceAfterLast('.', "java")
}
private fun Command.setupOut() = apply {
if (printOutput.get()) {
setup(System.out, System.err)
} else {
setup(UselessOutputStream, UselessOutputStream)
}
}
}

View file

@ -1,220 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
abstract class ApplyGitPatches : ControllableOutputTask() {
@get:Input
abstract val branch: Property<String>
@get:Input
abstract val upstreamBranch: Property<String>
@get:InputDirectory
abstract val upstream: DirectoryProperty
@get:Optional
@get:InputDirectory
abstract val patchDir: DirectoryProperty
@get:Optional
@get:InputFile
abstract val patchZip: RegularFileProperty
@get:Optional
@get:Input
abstract val unneededFiles: ListProperty<String>
@get:Input
abstract val ignoreGitIgnore: Property<Boolean>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Inject
abstract val providers: ProviderFactory
@get:Input
abstract val verbose: Property<Boolean>
override fun init() {
printOutput.convention(false).finalizeValueOnRead()
ignoreGitIgnore.convention(Git.ignoreProperty(providers)).finalizeValueOnRead()
verbose.convention(providers.verboseApplyPatches())
}
@TaskAction
fun run() {
Git.checkForGit()
Git(upstream.path).let { git ->
git("fetch").setupOut().run()
git("branch", "-f", upstreamBranch.get(), branch.get()).runSilently(silenceErr = true)
}
val outputPath = outputDir.path
recreateCloneDirectory(outputPath)
val target = outputPath.name
if (printOutput.get()) {
logger.lifecycle("Resetting $target to ${upstream.path.name}...")
}
val rootPatchDir = patchDir.pathOrNull ?: patchZip.path.let { unzip(it, findOutputDir(it)) }
try {
Git(outputPath).let { git ->
checkoutRepoFromUpstream(git, upstream.path, upstreamBranch.get())
if (unneededFiles.isPresent && unneededFiles.get().size > 0) {
unneededFiles.get().forEach { path -> outputDir.path.resolve(path).deleteRecursive() }
git(*Git.add(ignoreGitIgnore, ".")).executeSilently()
git("commit", "-m", "Initial", "--author=Initial Source <auto@mated.null>").executeSilently()
}
git("tag", "-d", "base").runSilently(silenceErr = true)
git("tag", "base").executeSilently(silenceErr = true)
applyGitPatches(git, target, outputDir.path, rootPatchDir, printOutput.get(), verbose.get())
}
} finally {
if (rootPatchDir != patchDir.pathOrNull) {
rootPatchDir.deleteRecursive()
}
}
}
}
fun ControllableOutputTask.applyGitPatches(
git: Git,
target: String,
outputDir: Path,
patchDir: Path?,
printOutput: Boolean,
verbose: Boolean,
) {
if (printOutput) {
logger.lifecycle("Applying patches to $target...")
}
val statusFile = outputDir.resolve(".git/patch-apply-failed")
statusFile.deleteForcefully()
git("am", "--abort").runSilently(silenceErr = true)
val patches = patchDir?.useDirectoryEntries("*.patch") { it.toMutableList() } ?: mutableListOf()
if (patches.isEmpty()) {
if (printOutput) {
logger.lifecycle("No patches found")
}
return
}
// This prevents the `git am` command line from getting too big with too many patches
// mostly an issue with Windows
layout.cache.createDirectories()
val tempDir = createTempDirectory(layout.cache, "paperweight")
try {
val mailDir = tempDir.resolve("new")
mailDir.createDirectories()
for (patch in patches) {
patch.copyTo(mailDir.resolve(patch.fileName))
}
val gitOut = printOutput && verbose
val result = git("am", "--3way", "--ignore-whitespace", tempDir.absolutePathString()).captureOut(gitOut)
if (result.exit != 0) {
statusFile.writeText("1")
if (!gitOut) {
// Log the output anyway on failure
logger.lifecycle(result.out)
}
logger.error("*** Please review above details and finish the apply then")
logger.error("*** save the changes with `./gradlew rebuildPatches`")
throw PaperweightException("Failed to apply patches")
} else {
statusFile.deleteForcefully()
if (printOutput) {
logger.lifecycle("${patches.size} patches applied cleanly to $target")
}
}
} finally {
tempDir.deleteRecursive()
}
}
fun Git.disableAutoGpgSigningInRepo() {
invoke("config", "commit.gpgSign", "false").executeSilently(silenceErr = true)
invoke("config", "tag.gpgSign", "false").executeSilently(silenceErr = true)
}
fun checkoutRepoFromUpstream(git: Git, upstream: Path, upstreamBranch: String) {
git("init", "--quiet").executeSilently(silenceErr = true)
git.disableAutoGpgSigningInRepo()
git("remote", "remove", "upstream").runSilently(silenceErr = true)
git("remote", "add", "upstream", upstream.toUri().toString()).executeSilently(silenceErr = true)
git("fetch", "upstream", "--prune").executeSilently(silenceErr = true)
if (git("checkout", "master").runSilently(silenceErr = true) != 0) {
git("checkout", "-b", "master").runSilently(silenceErr = true)
}
git("reset", "--hard", "upstream/$upstreamBranch").executeSilently(silenceErr = true)
git("gc").runSilently(silenceErr = true)
}
fun recreateCloneDirectory(target: Path) {
if (target.exists()) {
if (target.resolve(".git").isDirectory()) {
val git = Git(target)
git("clean", "-fxd").runSilently(silenceErr = true)
git("reset", "--hard", "HEAD").runSilently(silenceErr = true)
} else {
for (entry in target.listDirectoryEntries()) {
entry.deleteRecursive()
}
target.createDirectories()
}
} else {
target.createDirectories()
}
}

View file

@ -1,179 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import com.github.salomonbrys.kotson.fromJson
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
abstract class ApplyPaperPatches : ControllableOutputTask() {
@get:InputDirectory
abstract val patchDir: DirectoryProperty
@get:InputFile
abstract val remappedSource: RegularFileProperty
@get:InputFile
abstract val remappedTests: RegularFileProperty
@get:InputFile
abstract val caseOnlyClassNameChanges: RegularFileProperty
@get:InputDirectory
abstract val upstreamDir: DirectoryProperty
@get:Input
abstract val upstreamBranch: Property<String>
@get:InputFile
abstract val sourceMcDevJar: RegularFileProperty
@get:InputDirectory
abstract val mcLibrariesDir: DirectoryProperty
@get:InputDirectory
abstract val spigotLibrariesDir: DirectoryProperty
@get:Optional
@get:InputFile
abstract val devImports: RegularFileProperty
@get:Optional
@get:Input
abstract val unneededFiles: ListProperty<String>
@get:Input
abstract val ignoreGitIgnore: Property<Boolean>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Inject
abstract val providers: ProviderFactory
@get:OutputDirectory
abstract val mcDevSources: DirectoryProperty
@get:Input
abstract val verbose: Property<Boolean>
override fun init() {
upstreamBranch.convention("master")
ignoreGitIgnore.convention(Git.ignoreProperty(providers)).finalizeValueOnRead()
verbose.convention(providers.verboseApplyPatches())
}
@TaskAction
fun runLocking() {
val lockFile = layout.cache.resolve(applyPatchesLock(outputDir.path))
acquireProcessLockWaiting(lockFile)
try {
run()
} finally {
lockFile.deleteForcefully()
}
}
private fun run() {
Git.checkForGit()
val outputFile = outputDir.path
recreateCloneDirectory(outputFile)
val target = outputFile.name
if (printOutput.get()) {
logger.lifecycle("Creating $target from remapped source...")
}
Git(outputFile).let { git ->
checkoutRepoFromUpstream(git, upstreamDir.path, upstreamBranch.get())
val sourceDir = createDir(outputDir.path.resolve("src/main/java"))
val mcDataDir = outputDir.path.resolve("src/main/resources")
val testDir = createDir(outputDir.path.resolve("src/test/java"))
fs.copy {
from(archives.zipTree(remappedSource.path))
into(sourceDir)
}
fs.copy {
from(archives.zipTree(remappedTests.path))
into(testDir)
}
val patches = patchDir.path.listDirectoryEntries("*.patch")
McDev.importMcDev(
patches = patches,
decompJar = sourceMcDevJar.path,
importsFile = devImports.pathOrNull,
targetDir = sourceDir,
dataTargetDir = mcDataDir,
librariesDirs = listOf(spigotLibrariesDir.path, mcLibrariesDir.path),
printOutput = printOutput.get()
)
val caseOnlyChanges = caseOnlyClassNameChanges.path.bufferedReader(Charsets.UTF_8).use { reader ->
gson.fromJson<List<ClassNameChange>>(reader)
}
for (caseOnlyChange in caseOnlyChanges) {
val obfFile = sourceDir.resolve(caseOnlyChange.obfName + ".java").relativeTo(outputFile)
val deobfFile = sourceDir.resolve(caseOnlyChange.deobfName + ".java").relativeTo(outputFile)
git("mv", "-f", obfFile.toString(), deobfFile.toString()).runSilently(silenceErr = true)
}
unneededFiles.orNull?.forEach { path -> outputFile.resolve(path).deleteRecursive() }
git(*Git.add(ignoreGitIgnore, ".")).executeSilently()
git("commit", "-m", "Initial", "--author=Initial Source <auto@mated.null>").executeSilently()
git("tag", "-d", "base").runSilently(silenceErr = true)
git("tag", "base").executeSilently()
applyGitPatches(git, target, outputFile, patchDir.path, printOutput.get(), verbose.get())
makeMcDevSrc(layout.cache, sourceMcDevJar.path, mcDevSources.path, outputDir.path, sourceDir, mcDataDir)
}
}
private fun createDir(dir: Path): Path {
dir.deleteRecursive()
dir.createDirectories()
return dir
}
}

View file

@ -1,69 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
@CacheableTask
abstract class ApplyRawDiffPatches : ZippedTask() {
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val inputDir: DirectoryProperty
@get:Optional
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val patchDir: DirectoryProperty
override fun init() {
super.init()
outputZip.convention(defaultOutput("zip"))
}
override fun run(rootDir: Path) {
Git.checkForGit()
val input = inputDir.path
input.copyRecursivelyTo(rootDir)
val patches = patchDir.pathOrNull ?: return
val patchSet = patches.useDirectoryEntries("*.patch") { it.toMutableList() }
patchSet.sort()
val git = Git(rootDir)
for (patch in patchSet) {
git("apply", patch.absolutePathString()).executeOut()
}
}
}

View file

@ -1,135 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import dev.denwav.hypo.asm.AsmClassDataProvider
import dev.denwav.hypo.asm.hydrate.BridgeMethodHydrator
import dev.denwav.hypo.asm.hydrate.SuperConstructorHydrator
import dev.denwav.hypo.core.HypoContext
import dev.denwav.hypo.hydrate.HydrationManager
import dev.denwav.hypo.mappings.ChangeChain
import dev.denwav.hypo.mappings.MappingsCompletionManager
import dev.denwav.hypo.mappings.contributors.CopyMappingsDown
import dev.denwav.hypo.mappings.contributors.PropagateMappingsUp
import dev.denwav.hypo.mappings.contributors.RemoveUnusedMappings
import dev.denwav.hypo.model.ClassProviderRoot
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
@CacheableTask
abstract class CleanupMappings : JavaLauncherTask() {
@get:Classpath
abstract val sourceJar: RegularFileProperty
@get:CompileClasspath
abstract val libraries: ConfigurableFileCollection
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val inputMappings: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmargs.convention(listOf("-Xmx1G"))
}
@TaskAction
fun run() {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmargs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
queue.submit(CleanupMappingsAction::class) {
inputMappings.set(this@CleanupMappings.inputMappings.path)
libraries.from(this@CleanupMappings.libraries.files)
sourceJar.set(this@CleanupMappings.sourceJar.path)
outputMappings.set(this@CleanupMappings.outputMappings.path)
}
}
abstract class CleanupMappingsAction : WorkAction<CleanupMappingsAction.Parameters> {
interface Parameters : WorkParameters {
val inputMappings: RegularFileProperty
val libraries: ConfigurableFileCollection
val sourceJar: RegularFileProperty
val outputMappings: RegularFileProperty
}
override fun execute() {
val mappings = MappingFormats.TINY.read(
parameters.inputMappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
val cleanedMappings = HypoContext.builder()
.withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(parameters.sourceJar.path)))
.withContextProvider(AsmClassDataProvider.of(parameters.libraries.toJarClassProviderRoots()))
.withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
.build().use { hypoContext ->
HydrationManager.createDefault()
.register(BridgeMethodHydrator.create())
.register(SuperConstructorHydrator.create())
.hydrate(hypoContext)
ChangeChain.create()
.addLink(RemoveUnusedMappings.create())
.addLink(PropagateMappingsUp.create())
.addLink(CopyMappingsDown.create())
.applyChain(mappings, MappingsCompletionManager.create(hypoContext))
}
MappingFormats.TINY.write(
cleanedMappings,
parameters.outputMappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
}
}
}

View file

@ -1,336 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import dev.denwav.hypo.asm.AsmClassDataProvider
import dev.denwav.hypo.asm.hydrate.BridgeMethodHydrator
import dev.denwav.hypo.asm.hydrate.SuperConstructorHydrator
import dev.denwav.hypo.core.HypoContext
import dev.denwav.hypo.hydrate.HydrationManager
import dev.denwav.hypo.mappings.ChangeChain
import dev.denwav.hypo.mappings.ChangeRegistry
import dev.denwav.hypo.mappings.ClassMappingsChange
import dev.denwav.hypo.mappings.LorenzUtil
import dev.denwav.hypo.mappings.MappingsCompletionManager
import dev.denwav.hypo.mappings.MergeResult
import dev.denwav.hypo.mappings.MergeableMappingsChange
import dev.denwav.hypo.mappings.changes.AbstractMappingsChange
import dev.denwav.hypo.mappings.changes.MemberReference
import dev.denwav.hypo.mappings.changes.RemoveMappingChange
import dev.denwav.hypo.mappings.contributors.ChangeContributor
import dev.denwav.hypo.model.ClassProviderRoot
import dev.denwav.hypo.model.data.ClassData
import dev.denwav.hypo.model.data.types.PrimitiveType
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.util.Collections
import javax.inject.Inject
import kotlin.io.path.*
import org.cadixdev.lorenz.MappingSet
import org.cadixdev.lorenz.model.ClassMapping
import org.cadixdev.lorenz.model.TopLevelClassMapping
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
@CacheableTask
abstract class CleanupSourceMappings : JavaLauncherTask() {
@get:Classpath
abstract val sourceJar: RegularFileProperty
@get:CompileClasspath
abstract val libraries: ConfigurableFileCollection
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val inputMappings: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@get:OutputFile
abstract val caseOnlyNameChanges: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmargs.convention(listOf("-Xmx1G"))
caseOnlyNameChanges.convention(defaultOutput("caseOnlyClassNameChanges", "json"))
}
@TaskAction
fun run() {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmargs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
queue.submit(CleanupSourceMappingsAction::class) {
inputMappings.set(this@CleanupSourceMappings.inputMappings.path)
libraries.from(this@CleanupSourceMappings.libraries.files)
sourceJar.set(this@CleanupSourceMappings.sourceJar.path)
outputMappings.set(this@CleanupSourceMappings.outputMappings.path)
caseOnlyNameChanges.set(this@CleanupSourceMappings.caseOnlyNameChanges.path)
}
}
object ParamIndexesForSource : ChangeContributor {
override fun contribute(currentClass: ClassData?, classMapping: ClassMapping<*, *>?, context: HypoContext, registry: ChangeRegistry) {
if (currentClass == null || classMapping == null) {
return
}
for (methodMapping in classMapping.methodMappings) {
val method = LorenzUtil.findMethod(currentClass, methodMapping) ?: continue
var methodRef: MemberReference? = null
var lvtIndex = if (method.isStatic) 0 else 1
if (method.isConstructor && currentClass.outerClass() != null && !currentClass.isStaticInnerClass) {
lvtIndex += 1
}
for ((sourceIndex, param) in method.params().withIndex()) {
if (methodMapping.hasParameterMapping(lvtIndex)) {
if (methodRef == null) {
methodRef = MemberReference.of(methodMapping)
}
registry.submitChange(ParamIndexChange(methodRef, lvtIndex, sourceIndex))
}
lvtIndex++
if (param === PrimitiveType.LONG || param === PrimitiveType.DOUBLE) {
lvtIndex++
}
}
}
}
override fun name(): String = "ParamIndexesForSource"
class ParamIndexChange(
target: MemberReference,
fromIndex: Int,
toIndex: Int
) : AbstractMappingsChange(target), MergeableMappingsChange<ParamIndexChange> {
private val indexMap: MutableMap<Int, Int> = HashMap()
init {
indexMap[fromIndex] = toIndex
}
override fun applyChange(input: MappingSet, ref: MemberReference) {
val classMapping = input.getOrCreateClassMapping(ref.className())
val methodMapping = classMapping.getOrCreateMethodMapping(ref.name(), ref.desc())
val paramsMap = LorenzUtil.getParamsMap(methodMapping)
val params = paramsMap.values.toList()
paramsMap.clear()
for (param in params) {
methodMapping.createParameterMapping(indexMap[param.index] ?: param.index, param.deobfuscatedName)
}
}
override fun mergeWith(that: ParamIndexChange): MergeResult<ParamIndexChange> {
for (fromIndex in this.indexMap.keys) {
if (that.indexMap.containsKey(fromIndex)) {
return MergeResult.failure("Cannot merge 2 param mappings changes with matching fromIndexes")
}
}
for (toIndex in this.indexMap.values) {
if (that.indexMap.containsValue(toIndex)) {
return MergeResult.failure("Cannot merge 2 param mappings changes with matching toIndex")
}
}
this.indexMap += that.indexMap
return MergeResult.success(this)
}
override fun toString(): String {
return "Move param mappings for ${target()} for index pairs [${indexMap.entries.joinToString(", ") { "${it.key}:${it.value}" }}]"
}
}
}
object RemoveLambdaMappings : ChangeContributor {
override fun contribute(currentClass: ClassData?, classMapping: ClassMapping<*, *>?, context: HypoContext, registry: ChangeRegistry) {
if (currentClass == null || classMapping == null) {
return
}
for (methodMapping in classMapping.methodMappings) {
if (methodMapping.deobfuscatedName.startsWith("lambda$")) {
registry.submitChange(RemoveMappingChange.of(MemberReference.of(methodMapping)))
}
}
}
override fun name(): String = "RemoveLambdaMappings"
}
class FindCaseOnlyClassNameChanges(private val changes: MutableList<ClassNameChange>) : ChangeContributor {
override fun contribute(currentClass: ClassData?, classMapping: ClassMapping<*, *>?, context: HypoContext, registry: ChangeRegistry) {
if (classMapping !is TopLevelClassMapping) {
return
}
val obfName = classMapping.obfuscatedName
val deobfName = classMapping.deobfuscatedName
if (obfName != deobfName && obfName.equals(deobfName, ignoreCase = true)) {
changes += ClassNameChange(obfName, deobfName)
}
}
override fun name(): String = "FindCaseOnlyClassNameChanges"
}
class ChangeObfClassName(
private val targetClass: String,
private val newFullObfuscatedName: String
) : ClassMappingsChange {
override fun targetClass(): String = targetClass
override fun applyChange(input: MappingSet) {
val classMapping = LorenzUtil.getClassMapping(input, targetClass) ?: return
LorenzUtil.removeClassMapping(classMapping)
val newMap = input.getOrCreateClassMapping(newFullObfuscatedName)
copyMapping(classMapping, newMap)
}
private fun copyMapping(from: ClassMapping<*, *>, to: ClassMapping<*, *>) {
to.deobfuscatedName = from.deobfuscatedName
for (methodMapping in from.methodMappings) {
methodMapping.copy(to)
}
for (fieldMapping in from.fieldMappings) {
fieldMapping.copy(to)
}
for (innerClassMapping in from.innerClassMappings) {
innerClassMapping.copy(to)
}
}
}
companion object {
const val TEMP_SUFFIX = "paperweight-remove-anon-renames-temp-suffix"
}
object RemoveAnonymousClassRenames : ChangeContributor {
override fun contribute(currentClass: ClassData?, classMapping: ClassMapping<*, *>?, context: HypoContext, registry: ChangeRegistry) {
if (classMapping == null) return
val obf = classMapping.obfuscatedName.toIntOrNull()
val deobf = classMapping.deobfuscatedName.toIntOrNull()
if (obf != null && deobf != null && obf != deobf) {
val newName = classMapping.fullObfuscatedName.substringBeforeLast('$') + '$' + classMapping.deobfuscatedName + TEMP_SUFFIX
registry.submitChange(ChangeObfClassName(classMapping.fullObfuscatedName, newName))
}
}
override fun name(): String = "RemoveAnonymousClassRenames"
}
object CleanupAfterRemoveAnonymousClassRenames : ChangeContributor {
override fun contribute(currentClass: ClassData?, classMapping: ClassMapping<*, *>?, context: HypoContext, registry: ChangeRegistry) {
if (classMapping == null) return
if (classMapping.fullObfuscatedName.endsWith(TEMP_SUFFIX)) {
val newName = classMapping.fullObfuscatedName.substringBefore(TEMP_SUFFIX)
registry.submitChange(ChangeObfClassName(classMapping.fullObfuscatedName, newName))
}
}
override fun name(): String = "CleanupAfterRemoveAnonymousClassRenames"
}
abstract class CleanupSourceMappingsAction : WorkAction<CleanupSourceMappingsAction.Parameters> {
interface Parameters : WorkParameters {
val inputMappings: RegularFileProperty
val libraries: ConfigurableFileCollection
val sourceJar: RegularFileProperty
val outputMappings: RegularFileProperty
val caseOnlyNameChanges: RegularFileProperty
}
override fun execute() {
val mappings = MappingFormats.TINY.read(
parameters.inputMappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
val caseOnlyChanges = Collections.synchronizedList(mutableListOf<ClassNameChange>())
val cleanedMappings = HypoContext.builder()
.withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(parameters.sourceJar.path)))
.withContextProvider(AsmClassDataProvider.of(parameters.libraries.toJarClassProviderRoots()))
.withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
.build().use { hypoContext ->
HydrationManager.createDefault()
.register(BridgeMethodHydrator.create())
.register(SuperConstructorHydrator.create())
.hydrate(hypoContext)
ChangeChain.create()
.addLink(RemoveLambdaMappings)
.addLink(ParamIndexesForSource)
.addLink(FindCaseOnlyClassNameChanges(caseOnlyChanges))
.addLink(RemoveAnonymousClassRenames)
.addLink(CleanupAfterRemoveAnonymousClassRenames)
.applyChain(mappings, MappingsCompletionManager.create(hypoContext))
}
MappingFormats.TINY.write(
cleanedMappings,
parameters.outputMappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
parameters.caseOnlyNameChanges.path.bufferedWriter(Charsets.UTF_8).use { writer ->
gson.toJson(caseOnlyChanges, writer)
}
}
}
}

View file

@ -1,92 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class CollectATsFromPatches : BaseTask() {
companion object {
private const val PATCH_CONTENT_START = "diff --git a/"
private const val CO_AUTHOR_LINE = "Co-authored-by: "
}
@get:Input
abstract val header: Property<String>
@get:InputDirectory
abstract val patchDir: DirectoryProperty
@get:InputDirectory
@get:Optional
abstract val extraPatchDir: DirectoryProperty
@get:OutputFile
abstract val outputFile: RegularFileProperty
override fun init() {
header.convention("== AT ==")
outputFile.convention(defaultOutput("at"))
}
@TaskAction
fun run() {
outputFile.path.deleteForcefully()
val patches = patchDir.path.listDirectoryEntries("*.patch") +
(extraPatchDir.pathOrNull?.listDirectoryEntries("*.patch") ?: emptyList())
outputFile.path.writeLines(readAts(patches))
}
private fun readAts(patches: Iterable<Path>): List<String> {
val result = hashSetOf<String>()
val start = header.get()
for (patch in patches) {
patch.useLines {
var reading = false
for (line in it) {
if (line.startsWith(PATCH_CONTENT_START) || line.startsWith(CO_AUTHOR_LINE, true)) {
break
}
if (reading && line.isNotBlank() && !line.startsWith('#')) {
result.add(line)
}
if (line.startsWith(start)) {
reading = true
}
}
}
}
return result.sorted()
}
}

View file

@ -1,210 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.data.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
@CacheableTask
abstract class CreateBundlerJar : ZippedTask() {
interface VersionArtifact {
@get:Input
val name: String
@get:Input
val id: Property<String>
@get:Classpath
val file: RegularFileProperty
}
@get:Classpath
abstract val paperclip: ConfigurableFileCollection
@get:Input
abstract val mainClass: Property<String>
@get:Nested
val versionArtifacts: NamedDomainObjectContainer<VersionArtifact> = createVersionArtifactContainer()
@get:Classpath
@get:Optional
abstract val libraryArtifacts: Property<Configuration>
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFile
abstract val serverLibrariesList: RegularFileProperty
@get:Classpath
abstract val vanillaBundlerJar: RegularFileProperty
@get:OutputFile
abstract val libraryChangesJson: RegularFileProperty
private fun createVersionArtifactContainer(): NamedDomainObjectContainer<VersionArtifact> =
objects.domainObjectContainer(VersionArtifact::class) { objects.newInstance(it) }
override fun init() {
super.init()
libraryChangesJson.convention(defaultOutput("$name-library-changes", "json"))
}
override fun run(rootDir: Path) {
paperclip.singleFile.toPath().openZip().use { zip ->
zip.getPath("/").copyRecursivelyTo(rootDir)
}
val versions = handleVersions(rootDir)
val libraries = handleServerDependencies(rootDir)
val versionsFile = rootDir.resolve(FileEntry.VERSIONS_LIST).also { it.parent.createDirectories() }
val librariesFile = rootDir.resolve(FileEntry.LIBRARIES_LIST).also { it.parent.createDirectories() }
versionsFile.bufferedWriter().use { writer ->
for (v in versions.sortedBy { it.id }) {
writer.append(v.toString()).append('\n')
}
}
librariesFile.bufferedWriter().use { writer ->
for (l in libraries.sortedBy { it.id }) {
writer.append(l.toString()).append('\n')
}
}
rootDir.resolve("META-INF/main-class").writeText(mainClass.get())
// copy version.json file
vanillaBundlerJar.path.openZip().use { fs ->
fs.getPath("/").resolve(FileEntry.VERSION_JSON).copyTo(rootDir.resolve("version.json"))
}
}
private fun handleServerDependencies(rootDir: Path): List<FileEntry<ModuleId>> {
val libraries = mutableListOf<FileEntry<ModuleId>>()
val changedLibraries = mutableListOf<LibraryChange>()
val serverLibraryEntries = FileEntry.parse(serverLibrariesList.path, ModuleId::parse)
val outputDir = rootDir.resolve("META-INF/libraries")
val dependencies = collectDependencies()
for (dep in dependencies) {
val serverLibrary = serverLibraryEntries.firstOrNull {
it.id.group == dep.module.group &&
it.id.name == dep.module.name &&
it.id.classifier == dep.module.classifier
}
if (serverLibrary != null) {
if (serverLibrary.id.version == dep.module.version) {
// nothing to do
libraries += serverLibrary
dep.copyTo(outputDir.resolve(dep.module.toPath()))
} else {
// we have a different version of this library
val newId = dep.module
val newPath = newId.toPath()
changedLibraries += LibraryChange(serverLibrary.id, serverLibrary.path, newId, newPath)
val jarFile = dep.copyTo(outputDir.resolve(newPath))
libraries += FileEntry(jarFile.sha256asHex(), newId, newPath)
}
} else {
// New dependency
val id = dep.module
val path = id.toPath()
val jarFile = dep.copyTo(outputDir.resolve(path))
libraries += FileEntry(jarFile.sha256asHex(), id, path)
}
}
// This file will be used to check library changes in the generatePaperclipPatches step
ensureParentExists(libraryChangesJson)
libraryChangesJson.path.bufferedWriter().use { writer ->
gson.toJson(changedLibraries, writer)
}
return libraries
}
private fun handleVersions(rootDir: Path): List<FileEntry<String>> {
val outputDir = rootDir.resolve("META-INF/versions")
return versionArtifacts.map { versionArtifact ->
val id = versionArtifact.id.get()
val versionPath = "$id/${versionArtifact.name}-$id.jar"
val inputFile = versionArtifact.file.path
val outputFile = outputDir.resolve(versionPath)
ensureParentExists(outputFile)
inputFile.copyTo(outputFile)
FileEntry(inputFile.sha256asHex(), id, versionPath)
}
}
private fun collectDependencies(): Set<ResolvedArtifactResult> {
return libraryArtifacts.map { config ->
config.incoming.artifacts.artifacts.filterTo(HashSet()) {
val id = it.id.componentIdentifier
id is ModuleComponentIdentifier || id is ProjectComponentIdentifier
}
}.getOrElse(hashSetOf<ResolvedArtifactResult>())
}
private fun ResolvedArtifactResult.copyTo(path: Path): Path {
ensureParentExists(path)
return file.toPath().copyTo(path, overwrite = true)
}
private val ResolvedArtifactResult.module: ModuleId
get() {
return when (val ident = id.componentIdentifier) {
is ModuleComponentIdentifier -> ModuleId.fromIdentifier(id)
is ProjectComponentIdentifier -> {
val capability = variant.capabilities.first()
val version = capability.version ?: throw PaperweightException("Unknown version for ${capability.group}:${capability.name}")
ModuleId(capability.group, capability.name, version)
}
else -> throw PaperweightException("Unknown artifact result type: ${ident::class.java.name}")
}
}
}

View file

@ -1,63 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.UntrackedTask
import org.gradle.api.tasks.options.Option
@UntrackedTask(because = "Always copy files")
abstract class CreateDiffOutput : BaseTask() {
@get:InputDirectory
abstract val inputDir: DirectoryProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Internal
abstract val baseDir: DirectoryProperty
@get:Internal
@get:Option(option = "target", description = "Directory name of the diff output")
abstract val target: Property<String>
override fun init() {
baseDir.convention(layout.cacheDir("paperweight/diff"))
outputDir.convention(baseDir.flatMap { it.dir(target) })
}
@TaskAction
fun run() {
val output = outputDir.path
output.deleteRecursive()
inputDir.path.copyRecursivelyTo(output)
}
}

View file

@ -1,325 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.data.*
import io.sigpipe.jbsdiff.Diff
import java.nio.file.Path
import java.nio.file.Paths
import java.util.StringJoiner
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkQueue
import org.gradle.workers.WorkerExecutor
@CacheableTask
abstract class CreatePaperclipJar : JavaLauncherZippedTask() {
@get:Classpath
abstract val originalBundlerJar: RegularFileProperty
@get:Classpath
abstract val bundlerJar: RegularFileProperty
@get:Input
abstract val mcVersion: Property<String>
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFile
abstract val libraryChangesJson: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmargs.convention(listOf("-Xmx1G"))
}
override fun run(rootDir: Path) {
// Vanilla's URL uses a SHA1 hash of the vanilla server jar
val patchEntries: List<PatchEntry>
bundlerJar.path.openZip().use { newBundlerFs ->
originalBundlerJar.path.openZip().use { originalBundlerFs ->
val originalBundlerRoot = originalBundlerFs.getPath("/")
val newBundlerRoot = newBundlerFs.getPath("/")
patchEntries = createPatches(rootDir, newBundlerRoot, originalBundlerRoot)
}
}
rootDir.resolve(PatchEntry.PATCHES_LIST).bufferedWriter().use { writer ->
for (entry in patchEntries) {
writer.append(entry.toString()).append('\n')
}
}
val originalJar = originalBundlerJar.path
val vanillaSha256Hash = originalJar.sha256asHex()
val vanillaSha1Hash = originalJar.hashFile(HashingAlgorithm.SHA1).asHexString()
val vanillaUrl = "https://piston-data.mojang.com/v1/objects/$vanillaSha1Hash/server.jar"
val vanillaFileName = "mojang_${mcVersion.get()}.jar"
val context = DownloadContext(vanillaSha256Hash, vanillaUrl, vanillaFileName)
rootDir.resolve(DownloadContext.FILE).writeText(context.toString())
}
private fun createPatches(rootDir: Path, newBundlerRoot: Path, originalBundlerRoot: Path): List<PatchEntry> {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmargs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
val patchJobs = mutableListOf<PatchJob>()
val originalVersions = FileEntry.parse(originalBundlerRoot.resolve(FileEntry.VERSIONS_LIST))
val originalLibraries = FileEntry.parse(originalBundlerRoot.resolve(FileEntry.LIBRARIES_LIST), ModuleId::parse)
// Copy all files, we will only replace the files which need to be patched
newBundlerRoot.copyRecursivelyTo(rootDir)
// We will generate patches for library versions which have changed, assuming the changes will be small
val libraryChanges = gson.fromJson<List<LibraryChange>>(libraryChangesJson)
val newVersions = FileEntry.parse(newBundlerRoot.resolve(FileEntry.VERSIONS_LIST))
val newLibraries = FileEntry.parse(newBundlerRoot.resolve(FileEntry.LIBRARIES_LIST), ModuleId::parse)
// First, create paperclip patches for any changed versions
for (newVersion in newVersions) {
// If there is no original version, then we have nothing to do
val originalVersion = originalVersions.firstOrNull { it.id == newVersion.id } ?: continue
// If the hashes match we'll be able to pull this file from the original jar
if (newVersion.hash == originalVersion.hash) {
EntryLocation.VERSION.removeEntry(rootDir, newVersion.path)
continue
}
// Both jars have these versions, but they are different, so we need to create a patch
patchJobs += queue.submitPatchJob(rootDir, originalBundlerRoot, newBundlerRoot, originalVersion, newVersion, EntryLocation.VERSION)
}
// Remove library jars we don't need
for (newLibrary in newLibraries) {
val originalLibrary = originalLibraries.firstOrNull { it.id == newLibrary.id } ?: continue
if (newLibrary.path != originalLibrary.path && newLibrary.hash == originalLibrary.hash) {
throw PaperweightException("Paperclip cannot currently handle non-patch libraries with new paths")
}
if (newLibrary.hash != originalLibrary.hash) {
// Create patch for this library as well
patchJobs += queue.submitPatchJob(rootDir, originalBundlerRoot, newBundlerRoot, originalLibrary, newLibrary, EntryLocation.LIBRARY)
} else {
// The original bundler contains the right file, we don't need ours
EntryLocation.LIBRARY.removeEntry(rootDir, newLibrary.path)
}
}
// Now check for any library changes
for (libraryChange in libraryChanges) {
val originalLibrary = originalLibraries.firstOrNull { it.id == libraryChange.inputId }
?: throw PaperweightException("Unmatched library change, original id: ${libraryChange.inputId}")
val newLibrary = newLibraries.firstOrNull { it.id == libraryChange.outputId }
?: throw PaperweightException("Unmatched library change, new id: ${libraryChange.outputId}")
patchJobs += queue.submitPatchJob(rootDir, originalBundlerRoot, newBundlerRoot, originalLibrary, newLibrary, EntryLocation.LIBRARY)
}
queue.await()
// Find the patch files so we can hash them
return patchJobs.map { job ->
val patchLocation = job.entryLocation.resolve(rootDir)
val patchHash = job.patchFile.sha256asHex()
PatchEntry(
job.entryLocation,
job.originalEntry.hash,
patchHash,
job.newEntry.hash,
job.originalEntry.path,
job.patchFile.relativeTo(patchLocation).invariantSeparatorsPathString,
job.newEntry.path
)
}
}
private fun WorkQueue.submitPatchJob(
rootDir: Path,
originalRoot: Path,
newRoot: Path,
originalEntry: FileEntry<*>,
newEntry: FileEntry<*>,
location: EntryLocation
): PatchJob {
val outputFile = location.resolve(rootDir, newEntry.path)
outputFile.deleteForcefully()
val patchFile = outputFile.resolveSibling(Paths.get(originalEntry.path).name + ".patch")
// The original files are in a zip file system, which can't be serialized as that is going outside the JVM
// So we copy it out to a real file
val originalFile = location.resolve(originalRoot, originalEntry.path)
val tempOriginal = createTempFile()
originalFile.copyTo(tempOriginal, overwrite = true)
val newFile = location.resolve(newRoot, newEntry.path)
val tempNew = createTempFile()
newFile.copyTo(tempNew, overwrite = true)
submit(PaperclipAction::class) {
this.originalFile.set(tempOriginal)
this.patchedFile.set(tempNew)
this.outputFile.set(patchFile)
}
return PatchJob(originalEntry, newEntry, patchFile, location)
}
abstract class PaperclipAction : WorkAction<PaperclipParameters> {
override fun execute() {
val outputFile = parameters.outputFile.path
val originalFile = parameters.originalFile.path
val patchedFile = parameters.patchedFile.path
// Read the files into memory
val originalBytes = parameters.originalFile.path.readBytes()
val patchedBytes = parameters.patchedFile.path.readBytes()
try {
outputFile.parent.createDirectories()
outputFile.outputStream().use { patchOutput ->
Diff.diff(originalBytes, patchedBytes, patchOutput)
}
} catch (e: Exception) {
throw PaperweightException("Error creating patch between $originalFile and $patchedFile", e)
} finally {
runCatching { originalFile.deleteForcefully() }
runCatching { patchedFile.deleteForcefully() }
}
}
}
interface PaperclipParameters : WorkParameters {
val originalFile: RegularFileProperty
val patchedFile: RegularFileProperty
val outputFile: RegularFileProperty
}
data class PatchJob(
val originalEntry: FileEntry<*>,
val newEntry: FileEntry<*>,
val patchFile: Path,
val entryLocation: EntryLocation
)
data class PatchEntry(
val location: EntryLocation,
val originalHash: String,
val patchHash: String,
val outputHash: String,
val originalPath: String,
val patchPath: String,
val outputPath: String
) {
override fun toString(): String {
val joiner = StringJoiner("\t")
joiner.add(location.value)
joiner.add(originalHash)
joiner.add(patchHash)
joiner.add(outputHash)
joiner.add(originalPath)
joiner.add(patchPath)
joiner.add(outputPath)
return joiner.toString()
}
companion object {
const val PATCHES_LIST = "META-INF/patches.list"
}
}
enum class EntryLocation(val value: String) {
VERSION("versions") {
override fun resolve(dir: Path, path: String?): Path {
val base = dir.resolve(FileEntry.VERSIONS_DIR)
if (path == null) {
return base
}
return base.resolve(path)
}
},
LIBRARY("libraries") {
override fun resolve(dir: Path, path: String?): Path {
val base = dir.resolve(FileEntry.LIBRARIES_DIR)
if (path == null) {
return base
}
return base.resolve(path)
}
};
abstract fun resolve(dir: Path, path: String? = null): Path
fun removeEntry(dir: Path, path: String) {
val entryDir = resolve(dir)
var file = entryDir.resolve(path)
while (file.exists() && file != entryDir) {
file.deleteForcefully()
file = file.parent
if (file.listDirectoryEntries().isNotEmpty()) {
break
}
}
}
}
data class DownloadContext(val hash: String, val url: String, val fileName: String) {
override fun toString(): String {
return "$hash\t$url\t$fileName"
}
companion object {
const val FILE = "META-INF/download-context"
}
}
}

View file

@ -1,130 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import com.google.gson.JsonObject
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.data.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class ExtractFromBundler : BaseTask() {
@get:Classpath
abstract val bundlerJar: RegularFileProperty
@get:OutputFile
abstract val serverJar: RegularFileProperty
@get:OutputFile
abstract val serverLibrariesTxt: RegularFileProperty
@get:OutputDirectory
abstract val serverLibraryJars: DirectoryProperty
@get:OutputFile
abstract val versionJson: RegularFileProperty
@get:OutputFile
abstract val serverLibrariesList: RegularFileProperty
@get:OutputFile
abstract val serverVersionsList: RegularFileProperty
override fun init() {
super.init()
serverJar.set(defaultOutput())
}
@TaskAction
fun run() {
ServerBundler.extractFromBundler(
bundlerJar.path,
serverJar.path,
serverLibraryJars.path,
serverLibrariesTxt.path,
serverLibrariesList.path,
serverVersionsList.path,
versionJson.path
)
}
}
object ServerBundler {
fun extractFromBundler(
bundlerJar: Path,
serverJar: Path,
serverLibraryJars: Path,
serverLibrariesTxt: Path?,
serverLibrariesList: Path?,
serverVersionsList: Path?,
versionJson: Path?
) {
bundlerJar.openZip().use { bundlerFs ->
val root = bundlerFs.rootDirectories.first()
extractServerJar(root, serverJar, versionJson)
extractLibraryJars(root, serverLibraryJars)
serverLibrariesTxt?.let { writeLibrariesTxt(root, it) }
serverLibrariesList?.let { root.resolve(FileEntry.LIBRARIES_LIST).copyTo(it, overwrite = true) }
serverVersionsList?.let { root.resolve(FileEntry.VERSIONS_LIST).copyTo(it, overwrite = true) }
}
}
private fun extractServerJar(bundlerZip: Path, serverJar: Path, outputVersionJson: Path?) {
val serverVersionJson = bundlerZip.resolve("version.json")
outputVersionJson?.let { output ->
serverVersionJson.copyTo(output, overwrite = true)
}
val versionId = gson.fromJson<JsonObject>(serverVersionJson)["id"].asString
val versions = bundlerZip.resolve("/META-INF/versions.list").readLines()
.map { it.split('\t') }
.associate { it[1] to it[2] }
val serverJarPath = bundlerZip.resolve("/META-INF/versions/${versions[versionId]}")
serverJar.parent.createDirectories()
serverJarPath.copyTo(serverJar, overwrite = true)
}
private fun extractLibraryJars(bundlerZip: Path, serverLibraryJars: Path) {
serverLibraryJars.deleteRecursive()
serverLibraryJars.parent.createDirectories()
bundlerZip.resolve("/META-INF/libraries").copyRecursivelyTo(serverLibraryJars)
}
private fun writeLibrariesTxt(bundlerZip: Path, serverLibrariesTxt: Path) {
val libs = bundlerZip.resolve(FileEntry.LIBRARIES_LIST).readLines()
.map { it.split('\t')[1] }
serverLibrariesTxt.parent.createDirectories()
serverLibrariesTxt.writeLines(libs)
}
}

View file

@ -1,161 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import java.nio.file.Files
import java.nio.file.Path
import java.util.stream.Collectors
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.work.ChangeType
import org.gradle.work.Incremental
import org.gradle.work.InputChanges
abstract class FilterPatchedFiles : BaseTask() {
@get:InputDirectory
@get:Incremental
abstract val inputSrcDir: DirectoryProperty
@get:InputDirectory
@get:Incremental
abstract val inputResourcesDir: DirectoryProperty
@get:InputFile
abstract val vanillaJar: RegularFileProperty
@get:OutputFile
abstract val outputJar: RegularFileProperty
override fun init() {
outputJar.convention(defaultOutput())
}
@TaskAction
fun run(changes: InputChanges) {
if (!changes.isIncremental) {
return runFull()
}
val srcAdded = changes.added(inputSrcDir)
val srcRemoved = changes.removed(inputSrcDir)
val resourceAdded = changes.added(inputResourcesDir)
val resourceRemoved = changes.removed(inputResourcesDir)
if (srcAdded.isEmpty() && srcRemoved.isEmpty() && resourceAdded.isEmpty() && resourceRemoved.isEmpty()) {
logger.info("No adds or removes, not doing work.")
didWork = false
return
}
vanillaJar.path.openZip().use { vanillaFs ->
val vanillaRoot = vanillaFs.rootDirectories.single()
outputJar.path.openZip().use { outputFs ->
val outputRoot = outputFs.rootDirectories.single()
for (add in resourceAdded) {
if (vanillaRoot.resolve(add).exists()) {
outputRoot.resolve(add).deleteIfExists()
}
}
for (del in resourceRemoved) {
val vanilla = vanillaRoot.resolve(del)
if (vanilla.exists()) {
val out = outputRoot.resolve(del)
out.parent.createDirectories()
out.deleteIfExists()
vanilla.copyTo(out)
}
}
for (add in srcAdded) {
val p = add.removeSuffix(".java") + ".class"
val vanilla = vanillaRoot.resolve(p)
if (vanilla.exists()) {
val outFile = outputRoot.resolve(p)
val outDir = outFile.parent
val paths = outDir.listDirectoryEntries("${vanilla.name.removeSuffix(".class")}$*.class").toMutableList()
paths.add(outFile)
paths.forEach { it.deleteIfExists() }
}
}
for (del in srcRemoved) {
val p = del.removeSuffix(".java") + ".class"
val vanillaFile = vanillaRoot.resolve(p)
if (vanillaFile.exists()) {
val paths = vanillaFile.parent.listDirectoryEntries("${vanillaFile.name.removeSuffix(".class")}$*.class").toMutableList()
paths.add(vanillaFile)
paths.forEach {
val out = outputRoot.resolve(it.relativeTo(vanillaRoot))
out.parent.createDirectories()
out.deleteIfExists()
it.copyTo(out)
}
}
}
}
}
}
private fun runFull() {
val srcFiles = collectFiles(inputSrcDir.path)
val resourceFiles = collectFiles(inputResourcesDir.path)
filterJar(
vanillaJar.path,
outputJar.path,
listOf()
) {
if (!it.isRegularFile()) {
false
} else if (it.nameCount > 1) {
val path = it.subpath(0, it.nameCount - 1).resolve(it.fileName.toString().split("$")[0].removeSuffix(".class")).toString()
!srcFiles.contains("$path.java") && !resourceFiles.contains(path)
} else {
true
}
}
}
private fun collectFiles(dir: Path): Set<String> {
return Files.walk(dir).use { stream ->
stream.filter { it.isRegularFile() }
.map { it.relativeTo(dir).invariantSeparatorsPathString }
.collect(Collectors.toUnmodifiableSet())
}
}
private fun InputChanges.added(baseDir: DirectoryProperty): Set<String> {
return getFileChanges(baseDir).filter { it.changeType == ChangeType.ADDED }
.map { it.file.toPath().relativeTo(baseDir.path).invariantSeparatorsPathString }
.toSet()
}
private fun InputChanges.removed(baseDir: DirectoryProperty): Set<String> {
return getFileChanges(baseDir).filter { it.changeType == ChangeType.REMOVED }
.map { it.file.toPath().relativeTo(baseDir.path).invariantSeparatorsPathString }
.toSet()
}
}

View file

@ -1,229 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldInsnNode
import org.objectweb.asm.tree.MethodNode
@CacheableTask
abstract class FixJarForReobf : JavaLauncherTask() {
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:Optional
@get:Input
abstract val packagesToProcess: ListProperty<String>
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
outputJar.convention(defaultOutput())
jvmargs.convention(listOf("-Xmx2G"))
}
@TaskAction
fun run() {
val pack = packagesToProcess.orNull
if (pack == null) {
inputJar.path.copyTo(outputJar.path)
return
}
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmargs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
queue.submit(FixJarForReobfWorker::class) {
inputJar.set(this@FixJarForReobf.inputJar.path)
packagesToProcess.set(pack)
outputJar.set(this@FixJarForReobf.outputJar.path)
}
}
interface FixJarForReobfParams : WorkParameters {
val inputJar: RegularFileProperty
val packagesToProcess: ListProperty<String>
val outputJar: RegularFileProperty
}
abstract class FixJarForReobfWorker : WorkAction<FixJarForReobfParams> {
override fun execute() {
val packages = normalize(parameters.packagesToProcess.get())
val output = parameters.outputJar.path
output.parent.createDirectories()
output.deleteForcefully()
output.writeZip().use { out ->
parameters.inputJar.path.openZip().use { jarFile ->
JarProcessing.processJar(jarFile, out, FixForReobfProcessor(packages))
}
}
}
class FixForReobfProcessor(private val packages: List<String>) : JarProcessing.ClassProcessor.NodeBased {
override fun shouldProcess(file: Path): Boolean =
packages.any { file.toString().startsWith(it) }
override fun processClass(node: ClassNode, classNodeCache: ClassNodeCache) {
FieldAccessNormalizer(node, classNodeCache).visitNode()
}
}
private fun normalize(input: List<String>): List<String> {
return input.map { name ->
'/' + name.removePrefix("/").replace('.', '/')
}
}
}
}
/*
* This resolves issues caused by reobf prior to the reobf process. After reobf this is impossible to do - the field access become ambiguous (which is
* what this fixes).
*
* What exactly this is fixing requires some knowledge around how the JVM handles field accesses in the first place - Mumfrey described this process
* in detail with some great diagrams several years ago, you can read that here: https://github.com/MinecraftForge/MinecraftForge/pull/3055
*
* The goal of this class is to check all field access instructions (not field declarations) and follow the JVM's rules for field binding in order
* to determine the _intended_ owning class of a field access. Prior to reobf all of this works exactly as expected when looking at Java source code,
* but after reobf there are many cases that look like this:
*
* field `a` declared in class `Foo`
* field `a` declared in class `Bar` which extends `Foo`
*
* In the deobfuscated code these fields would have different names, so they won't overlap and the JVM will output field access instructions described
* in the link above. Reobf generally only changes the field's name and type (and the name of the owner class), but it doesn't actually fix the issue
* where field accesses which used to be unambiguous are now ambiguous.
*
* So with that in mind, this class will look at field access instructions and match the actual field the instruction is trying to access (even if
* it's not directly declared in the owner class) and change the owner accordingly. This will keep field accesses unambiguous even after reobf with
* conflicting field names.
*/
class FieldAccessNormalizer(private val node: ClassNode, private val classNodeCache: ClassNodeCache) : AsmUtil {
fun visitNode() {
for (method in node.methods) {
visitMethod(method)
}
}
private fun visitMethod(method: MethodNode) {
for (instruction in method.instructions) {
val fieldInst = instruction as? FieldInsnNode ?: continue
visitFieldInst(fieldInst)
}
}
private fun visitFieldInst(instruction: FieldInsnNode) {
val ownerNode = findTargetFieldDeclaration(instruction) ?: return
instruction.owner = ownerNode.name
}
private fun findTargetFieldDeclaration(instruction: FieldInsnNode): ClassNode? {
val fieldName = instruction.name
var className: String? = instruction.owner
while (className != null) {
val currentNode = classNodeCache.findClass(className) ?: return null
val fieldNode = currentNode.fields.firstOrNull { it.name == fieldName }
if (fieldNode != null) {
/*
* We need to determine if this field node can actually be accessed by the caller (the original `node`).
* For example, consider the following class hierarchy:
*
* class Foo
* public field text
* class Bar extends Foo
* private field text
* class Cat extends Bar
*
* If `Cat` contains a method which accesses `this.text` then by Java's field access rules the field access would bind to `Foo.text`
* rather than `Bar.text`, even though `Bar.text` shadows `Foo.text`. This is of course because `Cat` is not able to access `Bar.text`
* since it's a private field. Private fields are of course the easier case to handle - we also have to check protected fields if the
* original `node` does not extend the field's declaring class, and package private if the classes aren't in the same package.
*/
if (Opcodes.ACC_PRIVATE in fieldNode.access) {
// This is only legal if the field node owner and the original node match
if (currentNode.name == node.name) {
return currentNode
}
} else if (Opcodes.ACC_PROTECTED in fieldNode.access) {
var walkingNode: ClassNode? = node
while (walkingNode != null) {
if (walkingNode.name == currentNode.name) {
return currentNode
}
walkingNode = classNodeCache.findClass(walkingNode.superName)
}
} else if (Opcodes.ACC_PUBLIC in fieldNode.access) {
return currentNode
} else {
// package private field
val currentPackage = currentNode.name.substringBeforeLast('/')
val originalPackage = node.name.substringBeforeLast('/')
if (currentPackage == originalPackage) {
return currentNode
}
}
}
className = currentNode.superName
}
return null
}
}

View file

@ -1,218 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.ParameterAnnotationFixer
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkQueue
import org.gradle.workers.WorkerExecutor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
fun fixJar(
workerExecutor: WorkerExecutor,
jvmArgs: List<String> = arrayListOf("-Xmx512m"),
launcher: JavaLauncher,
vanillaJarPath: Path,
inputJarPath: Path,
outputJarPath: Path,
useLegacyParameterAnnotationFixer: Boolean = false,
): WorkQueue {
ensureParentExists(outputJarPath)
ensureDeleted(outputJarPath)
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmArgs)
forkOptions.executable(launcher.executablePath.path.absolutePathString())
}
queue.submit(FixJarTask.FixJarAction::class) {
inputJar.set(inputJarPath)
vanillaJar.set(vanillaJarPath)
outputJar.set(outputJarPath)
useLegacyParamAnnotationFixer.set(useLegacyParameterAnnotationFixer)
}
return queue
}
@CacheableTask
abstract class FixJarTask : JavaLauncherTask() {
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:Classpath
abstract val vanillaJar: RegularFileProperty
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Internal
abstract val jvmArgs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmArgs.convention(listOf("-Xmx512m"))
outputJar.convention(defaultOutput())
}
@TaskAction
fun run() {
fixJar(
workerExecutor = workerExecutor,
jvmArgs = jvmArgs.get(),
launcher = launcher.get(),
vanillaJarPath = vanillaJar.path,
inputJarPath = inputJar.path,
outputJarPath = outputJar.path
)
}
interface FixJarParams : WorkParameters {
val inputJar: RegularFileProperty
val vanillaJar: RegularFileProperty
val outputJar: RegularFileProperty
val useLegacyParamAnnotationFixer: Property<Boolean>
}
abstract class FixJarAction : WorkAction<FixJarParams> {
override fun execute() {
parameters.vanillaJar.path.openZip().use { vanillaJar ->
parameters.outputJar.path.writeZip().use { out ->
parameters.inputJar.path.openZip().use { jarFile ->
JarProcessing.processJar(
jarFile,
vanillaJar,
out,
FixJarClassProcessor(parameters.useLegacyParamAnnotationFixer.get())
)
}
}
}
}
private class FixJarClassProcessor(private val legacy: Boolean) : JarProcessing.ClassProcessor.NodeBased, AsmUtil {
override fun processClass(node: ClassNode, classNodeCache: ClassNodeCache) {
if (legacy) {
ParameterAnnotationFixer(node).visitNode()
}
OverrideAnnotationAdder(node, classNodeCache).visitNode()
if (Opcodes.ACC_RECORD in node.access) {
RecordFieldAccessFixer(node).visitNode()
}
}
}
}
}
// Fix proguard changing access of record fields
class RecordFieldAccessFixer(private val node: ClassNode) : AsmUtil {
fun visitNode() {
for (field in node.fields) {
if (Opcodes.ACC_STATIC !in field.access && Opcodes.ACC_FINAL in field.access && Opcodes.ACC_PRIVATE !in field.access) {
field.access = field.access and AsmUtil.RESET_ACCESS or Opcodes.ACC_PRIVATE
}
}
}
}
class OverrideAnnotationAdder(private val node: ClassNode, private val classNodeCache: ClassNodeCache) : AsmUtil {
fun visitNode() {
val superMethods = collectSuperMethods(node)
val disqualifiedMethods = Opcodes.ACC_STATIC or Opcodes.ACC_PRIVATE
for (method in node.methods) {
if (method.access in disqualifiedMethods) {
continue
}
if (method.name == "<init>" || method.name == "<clinit>") {
continue
}
val (name, desc) = SyntheticUtil.findBaseMethod(method, node.name)
if (method.name + method.desc in superMethods) {
val targetMethod = node.methods.firstOrNull { it.name == name && it.desc == desc } ?: method
if (targetMethod.invisibleAnnotations == null) {
targetMethod.invisibleAnnotations = arrayListOf()
}
val annoClass = "Ljava/lang/Override;"
if (targetMethod.invisibleAnnotations.none { it.desc == annoClass }) {
targetMethod.invisibleAnnotations.add(AnnotationNode(annoClass))
}
}
}
}
private fun collectSuperMethods(node: ClassNode): Set<String> {
fun collectSuperMethods(node: ClassNode, superMethods: HashSet<String>) {
val supers = listOfNotNull(node.superName, *node.interfaces.toTypedArray())
if (supers.isEmpty()) {
return
}
val disqualifiedMethods = Opcodes.ACC_STATIC or Opcodes.ACC_PRIVATE
val superNodes = supers.mapNotNull { classNodeCache.findClass(it) }
superNodes.asSequence()
.flatMap { classNode -> classNode.methods.asSequence() }
.filter { method -> method.access !in disqualifiedMethods }
.filter { method -> method.name != "<init>" && method.name != "<clinit>" }
.map { method -> method.name + method.desc }
.toCollection(superMethods)
for (superNode in superNodes) {
collectSuperMethods(superNode, superMethods)
}
}
val result = hashSetOf<String>()
collectSuperMethods(node, result)
return result
}
}

View file

@ -1,396 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import io.papermc.paperweight.util.data.*
import java.io.ByteArrayOutputStream
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Path
import java.util.Locale
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class GenerateDevBundle : DefaultTask() {
@get:InputFile
abstract val decompiledJar: RegularFileProperty
@get:InputDirectory
abstract val sourceDir: DirectoryProperty
@get:Input
abstract val minecraftVersion: Property<String>
@get:InputFile
abstract val mojangMappedPaperclipFile: RegularFileProperty
@get:Input
abstract val serverVersion: Property<String>
@get:Input
abstract val serverCoordinates: Property<String>
@get:Input
abstract val apiCoordinates: Property<String>
@get:Input
abstract val vanillaJarIncludes: ListProperty<String>
@get:Input
abstract val vanillaServerLibraries: ListProperty<String>
@get:Input
abstract val libraryRepositories: ListProperty<String>
@get:Internal
abstract val serverProject: Property<Project>
@get:Classpath
abstract val runtimeConfiguration: Property<Configuration>
@get:Input
abstract val paramMappingsUrl: Property<String>
@get:Input
abstract val paramMappingsCoordinates: Property<String>
@get:Input
abstract val decompilerUrl: Property<String>
@get:Classpath
abstract val decompilerConfig: Property<Configuration>
@get:Input
abstract val remapperUrl: Property<String>
@get:Classpath
abstract val remapperConfig: Property<Configuration>
@get:InputFile
abstract val reobfMappingsFile: RegularFileProperty
@get:InputFile
abstract val atFile: RegularFileProperty
@get:OutputFile
abstract val devBundleFile: RegularFileProperty
@get:Inject
abstract val layout: ProjectLayout
@get:Input
@get:Optional
abstract val ignoreUnsupportedEnvironment: Property<Boolean>
@TaskAction
fun run() {
checkEnvironment()
val devBundle = devBundleFile.path
devBundle.deleteForcefully()
devBundle.parent.createDirectories()
val tempPatchDir = createTempDirectory("devBundlePatches")
try {
generatePatches(tempPatchDir)
val dataDir = "data"
val patchesDir = "patches"
val config = createBundleConfig(dataDir, patchesDir)
devBundle.writeZip().use { zip ->
zip.getPath("config.json").bufferedWriter(Charsets.UTF_8).use { writer ->
gson.toJson(config, writer)
}
zip.getPath("data-version.txt").writeText(currentDataVersion.toString())
val dataZip = zip.getPath(dataDir)
dataZip.createDirectories()
reobfMappingsFile.path.copyTo(dataZip.resolve(reobfMappingsFileName))
mojangMappedPaperclipFile.path.copyTo(dataZip.resolve(mojangMappedPaperclipFileName))
atFile.path.copyTo(dataZip.resolve(atFileName))
val patchesZip = zip.getPath(patchesDir)
tempPatchDir.copyRecursivelyTo(patchesZip)
}
} finally {
tempPatchDir.deleteRecursive()
}
}
private fun generatePatches(output: Path) {
val workingDir = layout.cache.resolve(paperTaskOutput("tmpdir"))
workingDir.deleteRecursive()
workingDir.createDirectories()
sourceDir.path.copyRecursivelyTo(workingDir)
Files.walk(workingDir).use { stream ->
decompiledJar.path.openZip().use { decompJar ->
val decompRoot = decompJar.rootDirectories.single()
for (file in stream) {
if (file.isDirectory()) {
continue
}
val relativeFile = file.relativeTo(workingDir)
val relativeFilePath = relativeFile.invariantSeparatorsPathString
val decompFile = decompRoot.resolve(relativeFilePath)
if (decompFile.notExists()) {
val outputFile = output.resolve(relativeFilePath)
outputFile.parent.createDirectories()
file.copyTo(outputFile)
} else {
val diffText = diffFiles(relativeFilePath, decompFile, file)
val patchName = relativeFile.name + ".patch"
val outputFile = output.resolve(relativeFilePath).resolveSibling(patchName)
if (diffText.isNotBlank()) {
outputFile.parent.createDirectories()
outputFile.writeText(diffText)
}
}
}
}
}
workingDir.deleteRecursive()
}
private fun diffFiles(fileName: String, original: Path, patched: Path): String {
val dir = createTempDirectory("diff")
try {
val oldFile = dir.resolve("old.java")
val newFile = dir.resolve("new.java")
original.copyTo(oldFile)
patched.copyTo(newFile)
val args = listOf(
"--color=never",
"-ud",
"--label",
"a/$fileName",
oldFile.absolutePathString(),
"--label",
"b/$fileName",
newFile.absolutePathString(),
)
return runDiff(dir, args)
} finally {
dir.deleteRecursive()
}
}
private fun runDiff(dir: Path?, args: List<String>): String {
val cmd = listOf("diff") + args
val process = ProcessBuilder(cmd)
.directory(dir)
.start()
val errBytes = ByteArrayOutputStream()
val errFuture = redirect(process.errorStream, errBytes)
val outBytes = ByteArrayOutputStream()
val outFuture = redirect(process.inputStream, outBytes)
if (!process.waitFor(10L, TimeUnit.SECONDS)) {
process.destroyForcibly()
throw PaperweightException("Command '${cmd.joinToString(" ")}' did not finish after 10 seconds, killed process")
}
errFuture.get(500L, TimeUnit.MILLISECONDS)
outFuture.get(500L, TimeUnit.MILLISECONDS)
val err = asString(errBytes)
val exit = process.exitValue()
if (exit != 0 && exit != 1 || err.isNotBlank()) {
throw PaperweightException("Error (exit code $exit) executing '${cmd.joinToString(" ")}':\n$err")
}
return asString(outBytes)
}
private fun asString(out: ByteArrayOutputStream) = String(out.toByteArray(), Charset.defaultCharset())
.replace(System.lineSeparator(), "\n")
@Suppress("SameParameterValue")
private fun createBundleConfig(dataTargetDir: String, patchTargetDir: String): DevBundleConfig {
return DevBundleConfig(
minecraftVersion = minecraftVersion.get(),
mappedServerCoordinates = serverCoordinates.get(),
apiCoordinates = "${apiCoordinates.get()}:${serverVersion.get()}",
buildData = createBuildDataConfig(dataTargetDir),
decompile = createDecompileRunner(),
remapper = createRemapDep(),
patchDir = patchTargetDir
)
}
private fun createBuildDataConfig(targetDir: String): BuildData {
return BuildData(
paramMappings = MavenDep(paramMappingsUrl.get(), listOf(paramMappingsCoordinates.get())),
reobfMappingsFile = "$targetDir/$reobfMappingsFileName",
accessTransformFile = "$targetDir/$atFileName",
mojangMappedPaperclipFile = "$targetDir/$mojangMappedPaperclipFileName",
vanillaJarIncludes = vanillaJarIncludes.get(),
compileDependencies = determineLibraries(vanillaServerLibraries.get()).sorted(),
runtimeDependencies = collectRuntimeDependencies().map { it.coordinates }.sorted(),
libraryRepositories = libraryRepositories.get(),
relocations = emptyList(), // Nothing is relocated in the dev bundle as of 1.20.5
minecraftRemapArgs = TinyRemapper.minecraftRemapArgs,
pluginRemapArgs = TinyRemapper.pluginRemapArgs,
)
}
private fun determineLibraries(vanillaServerLibraries: List<String>): Set<String> {
val new = arrayListOf<ModuleId>()
// yes this is not configuration cache compatible, but the task isn't even without this,
// and what we want here are the dependencies declared in the server build file,
// not the runtime classpath, which would flatten transitive deps of the api for example.
for (dependency in serverProject.get().configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).dependencies) {
// don't want project dependencies
if (dependency !is ExternalModuleDependency) {
continue
}
val version = listOfNotNull(
dependency.versionConstraint.strictVersion,
dependency.versionConstraint.requiredVersion,
dependency.versionConstraint.preferredVersion,
dependency.version
).first { it.isNotBlank() }
new += ModuleId(dependency.group ?: error("Missing group for $dependency"), dependency.name, version)
}
for (vanillaLib in vanillaServerLibraries) {
val vanilla = ModuleId.parse(vanillaLib)
if (new.none { it.group == vanilla.group && it.name == vanilla.name && it.classifier == vanilla.classifier }) {
new += vanilla
}
}
return new.map { it.toString() }.toSet()
}
private val ResolvedArtifactResult.coordinates: String
get() = ModuleId.fromIdentifier(id).toString()
private fun collectRuntimeDependencies(): Set<ResolvedArtifactResult> =
runtimeConfiguration.get().incoming.artifacts.artifacts.filterTo(HashSet()) {
it.id.componentIdentifier is ModuleComponentIdentifier
}
private fun createDecompileRunner(): Runner {
return Runner(
dep = determineMavenDep(decompilerUrl, decompilerConfig),
args = vineFlowerArgList
)
}
private fun createRemapDep(): MavenDep =
determineMavenDep(remapperUrl, remapperConfig)
data class DevBundleConfig(
val minecraftVersion: String,
val mappedServerCoordinates: String,
val apiCoordinates: String,
val mojangApiCoordinates: String? = null,
val buildData: BuildData,
val decompile: Runner,
val remapper: MavenDep,
val patchDir: String
)
data class BuildData(
val paramMappings: MavenDep,
val reobfMappingsFile: String,
val accessTransformFile: String,
val mojangMappedPaperclipFile: String,
val vanillaJarIncludes: List<String>,
val compileDependencies: List<String>,
val runtimeDependencies: List<String>,
val libraryRepositories: List<String>,
val relocations: List<Relocation>,
val minecraftRemapArgs: List<String>,
val pluginRemapArgs: List<String>,
)
data class Runner(val dep: MavenDep, val args: List<String>)
companion object {
const val unsupportedEnvironmentPropName: String = "paperweight.generateDevBundle.ignoreUnsupportedEnvironment"
const val atFileName = "transform.at"
const val reobfMappingsFileName = "$DEOBF_NAMESPACE-$SPIGOT_NAMESPACE-reobf.tiny"
const val mojangMappedPaperclipFileName = "paperclip-$DEOBF_NAMESPACE.jar"
// Should be bumped when the dev bundle config/contents changes in a way which will require users to update paperweight
const val currentDataVersion = 5
fun createCoordinatesFor(project: Project): String =
sequenceOf(project.group, project.name.lowercase(Locale.ENGLISH), "userdev-" + project.version).joinToString(":")
}
private fun checkEnvironment() {
val diffVersion = runDiff(null, listOf("--version")) + " " // add whitespace so pattern still works even with eol
val matcher = Pattern.compile("diff \\(GNU diffutils\\) (.*?)\\s").matcher(diffVersion)
if (matcher.find()) {
logger.lifecycle("Using 'diff (GNU diffutils) {}'.", matcher.group(1))
return
}
logger.warn("Non-GNU diffutils diff detected, '--version' returned:\n{}", diffVersion)
if (this.ignoreUnsupportedEnvironment.getOrElse(false)) {
logger.warn("Ignoring unsupported environment as per user configuration.")
} else {
throw PaperweightException(
"Dev bundle generation is running in an unsupported environment (see above log messages).\n" +
"You can ignore this and attempt to generate a dev bundle anyways by setting the '$unsupportedEnvironmentPropName' Gradle " +
"property to 'true'."
)
}
}
}

View file

@ -1,156 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import dev.denwav.hypo.asm.AsmClassDataProvider
import dev.denwav.hypo.core.HypoConfig
import dev.denwav.hypo.core.HypoContext
import dev.denwav.hypo.hydrate.HydrationManager
import dev.denwav.hypo.mappings.ChangeChain
import dev.denwav.hypo.mappings.ChangeRegistry
import dev.denwav.hypo.mappings.MappingsCompletionManager
import dev.denwav.hypo.mappings.contributors.ChangeContributor
import dev.denwav.hypo.model.ClassProviderRoot
import dev.denwav.hypo.model.data.ClassData
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import javax.inject.Inject
import kotlin.io.path.*
import org.cadixdev.lorenz.model.ClassMapping
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
@CacheableTask
abstract class GenerateRelocatedReobfMappings : JavaLauncherTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val inputMappings: RegularFileProperty
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@get:Internal
abstract val jvmArgs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
@get:Input
abstract val craftBukkitPackageVersion: Property<String>
override fun init() {
super.init()
jvmArgs.convention(listOf("-Xmx2G"))
}
@TaskAction
fun run() {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmArgs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
queue.submit(Action::class) {
inputMappings.set(this@GenerateRelocatedReobfMappings.inputMappings)
inputJar.set(this@GenerateRelocatedReobfMappings.inputJar)
craftBukkitPackageVersion.set(this@GenerateRelocatedReobfMappings.craftBukkitPackageVersion)
outputMappings.set(this@GenerateRelocatedReobfMappings.outputMappings)
}
}
abstract class Action : WorkAction<Action.Parameters> {
interface Parameters : WorkParameters {
val inputMappings: RegularFileProperty
val inputJar: RegularFileProperty
val craftBukkitPackageVersion: Property<String>
val outputMappings: RegularFileProperty
}
override fun execute() {
val mappingsIn = MappingFormats.TINY.read(
parameters.inputMappings.path,
DEOBF_NAMESPACE,
SPIGOT_NAMESPACE
)
val mappingsOut = HypoContext.builder()
.withConfig(HypoConfig.builder().setRequireFullClasspath(false).withParallelism(1).build())
.withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(parameters.inputJar.path)))
.withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
.build().use { hypoContext ->
HydrationManager.createDefault().hydrate(hypoContext)
ChangeChain.create()
.addLink(CraftBukkitRelocation(parameters.craftBukkitPackageVersion.get()))
.applyChain(mappingsIn, MappingsCompletionManager.create(hypoContext))
}
MappingFormats.TINY.write(
mappingsOut,
parameters.outputMappings.path,
DEOBF_NAMESPACE,
SPIGOT_NAMESPACE
)
}
}
class CraftBukkitRelocation(packageVersion: String) : ChangeContributor {
companion object {
const val PREFIX = "org/bukkit/craftbukkit/"
const val MAIN = "${PREFIX}Main"
}
private val relocateTo: String = "$PREFIX$packageVersion"
override fun name(): String = "CraftBukkitRelocation"
override fun contribute(
currentClass: ClassData?,
classMapping: ClassMapping<*, *>?,
context: HypoContext,
registry: ChangeRegistry
) {
if (currentClass == null || classMapping != null) {
return
}
if (currentClass.name().startsWith(PREFIX) && !currentClass.name().startsWith(MAIN)) {
registry.submitChange(
GenerateReobfMappings.AddClassMappingChange(
currentClass.name(),
"$relocateTo/${currentClass.name().substring(PREFIX.length)}"
)
)
}
}
}
}

View file

@ -1,315 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import dev.denwav.hypo.asm.AsmClassDataProvider
import dev.denwav.hypo.asm.hydrate.BridgeMethodHydrator
import dev.denwav.hypo.asm.hydrate.SuperConstructorHydrator
import dev.denwav.hypo.core.HypoConfig
import dev.denwav.hypo.core.HypoContext
import dev.denwav.hypo.hydrate.HydrationManager
import dev.denwav.hypo.mappings.ChangeChain
import dev.denwav.hypo.mappings.ChangeRegistry
import dev.denwav.hypo.mappings.ClassMappingsChange
import dev.denwav.hypo.mappings.MappingsCompletionManager
import dev.denwav.hypo.mappings.changes.MemberReference
import dev.denwav.hypo.mappings.changes.RemoveMappingChange
import dev.denwav.hypo.mappings.contributors.ChangeContributor
import dev.denwav.hypo.mappings.contributors.RemoveUnusedMappings
import dev.denwav.hypo.model.ClassProviderRoot
import dev.denwav.hypo.model.data.ClassData
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import javax.inject.Inject
import kotlin.io.path.*
import org.cadixdev.lorenz.MappingSet
import org.cadixdev.lorenz.model.ClassMapping
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
@CacheableTask
abstract class GenerateReobfMappings : JavaLauncherTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val inputMappings: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val notchToSpigotMappings: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val sourceMappings: RegularFileProperty
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:OutputFile
abstract val reobfMappings: RegularFileProperty
@get:Internal
abstract val jvmArgs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val spigotRecompiledClasses: RegularFileProperty
override fun init() {
super.init()
jvmArgs.convention(listOf("-Xmx2G"))
}
@TaskAction
fun run() {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmArgs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
queue.submit(GenerateReobfMappingsAction::class) {
inputMappings.set(this@GenerateReobfMappings.inputMappings)
notchToSpigotMappings.set(this@GenerateReobfMappings.notchToSpigotMappings)
sourceMappings.set(this@GenerateReobfMappings.sourceMappings)
inputJar.set(this@GenerateReobfMappings.inputJar)
spigotRecompiles.set(spigotRecompiledClasses.path)
reobfMappings.set(this@GenerateReobfMappings.reobfMappings)
}
}
// https://github.com/PaperMC/paperweight/issues/18
class PropagateOuterClassMappings(private val mappings: MappingSet) : ChangeContributor {
override fun contribute(currentClass: ClassData?, classMapping: ClassMapping<*, *>?, context: HypoContext, registry: ChangeRegistry) {
if (currentClass == null || classMapping != null) {
return
}
val name = currentClass.name().substringAfterLast("$")
val outer = currentClass.outerClass() ?: return
val outerMappings = mappings.getClassMapping(outer.name()).orNull ?: return
if (outerMappings.innerClassMappings.any { it.deobfuscatedName == name }) {
return
}
registry.submitChange(AddClassMappingChange(currentClass.name(), name))
}
override fun name(): String = "PropagateOuterClassMappings"
}
class AddClassMappingChange(private val target: String, private val deobfName: String) : ClassMappingsChange {
override fun targetClass(): String = target
override fun applyChange(input: MappingSet) {
input.getOrCreateClassMapping(target).deobfuscatedName = deobfName
}
}
class RemoveRecompiledSyntheticMemberMappings(private val recompiledClasses: Set<String>) : ChangeContributor {
override fun contribute(
currentClass: ClassData?,
classMapping: ClassMapping<*, *>?,
context: HypoContext,
registry: ChangeRegistry
) {
if (currentClass == null || classMapping == null) {
return
}
if (currentClass.rootClass().name() !in recompiledClasses) {
return
}
for (method in currentClass.methods()) {
if (method.isSynthetic) {
registry.submitChange(RemoveMappingChange.of(MemberReference.of(method)))
}
}
for (field in currentClass.fields()) {
if (field.isSynthetic) {
registry.submitChange(RemoveMappingChange.of(MemberReference.of(field)))
}
}
}
private fun ClassData.rootClass(): ClassData =
allOuterClasses().getOrNull(0) ?: this
private fun ClassData.allOuterClasses(list: MutableList<ClassData> = ArrayList()): List<ClassData> {
val outer = outerClass() ?: return list.reversed()
list.add(outer)
return outer.allOuterClasses(list)
}
override fun name(): String = "RemoveRecompiledSyntheticMemberMappings"
}
interface GenerateReobfMappingsParams : WorkParameters {
val inputMappings: RegularFileProperty
val notchToSpigotMappings: RegularFileProperty
val sourceMappings: RegularFileProperty
val inputJar: RegularFileProperty
val spigotRecompiles: RegularFileProperty
val reobfMappings: RegularFileProperty
}
abstract class GenerateReobfMappingsAction : WorkAction<GenerateReobfMappingsParams> {
override fun execute() {
val spigotToMojang = MappingFormats.TINY.read(
parameters.inputMappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
val obfToSpigot = MappingFormats.TINY.read(
parameters.notchToSpigotMappings.path,
OBF_NAMESPACE,
SPIGOT_NAMESPACE
)
val obfToMojang = MappingFormats.TINY.read(
parameters.sourceMappings.path,
OBF_NAMESPACE,
DEOBF_NAMESPACE
)
val outputMappings = mergeSpigotWithMojangMemberMappings(obfToSpigot, obfToMojang, spigotToMojang)
val spigotRecompiles = parameters.spigotRecompiles.path.readLines().toSet()
val cleanedOutputMappings = HypoContext.builder()
.withConfig(HypoConfig.builder().setRequireFullClasspath(false).withParallelism(1).build())
.withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(parameters.inputJar.path)))
.withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
.build().use { hypoContext ->
HydrationManager.createDefault()
.register(BridgeMethodHydrator.create())
.register(SuperConstructorHydrator.create())
.hydrate(hypoContext)
ChangeChain.create()
.addLink(RemoveUnusedMappings.create())
.addLink(RemoveRecompiledSyntheticMemberMappings(spigotRecompiles))
.addLink(PropagateOuterClassMappings(outputMappings))
.applyChain(outputMappings, MappingsCompletionManager.create(hypoContext))
}
MappingFormats.TINY.write(
cleanedOutputMappings,
parameters.reobfMappings.path,
DEOBF_NAMESPACE,
SPIGOT_NAMESPACE
)
}
private fun mergeSpigotWithMojangMemberMappings(obfToSpigot: MappingSet, obfToMojang: MappingSet, spigotToMojang: MappingSet): MappingSet {
val output = MappingSet.create()
for (mojangClassMapping in obfToMojang.topLevelClassMappings) {
val spigotClassMapping = obfToSpigot.getTopLevelClassMapping(mojangClassMapping.obfuscatedName).orNull
val paperMojangClassMapping = spigotClassMapping?.deobfuscatedName?.let { spigotToMojang.getTopLevelClassMapping(it) }?.orNull
val fromClassName = mojangClassMapping.deobfuscatedName
val toClassName = if (spigotClassMapping != null && spigotClassMapping.obfuscatedName != spigotClassMapping.deobfuscatedName) {
spigotClassMapping.deobfuscatedName
} else {
mojangClassMapping.deobfuscatedName
}
// package-info and nullability annotations don't need to be reobfed
if (fromClassName.endsWith("package-info") || fromClassName.endsWith("NonnullByDefault")) {
continue
}
val newClassMapping = output.createTopLevelClassMapping(fromClassName, toClassName)
mergeSpigotWithMojangMemberMappings(spigotClassMapping, mojangClassMapping, paperMojangClassMapping, newClassMapping)
}
return output
}
private fun mergeSpigotWithMojangMemberMappings(
spigotClassMapping: ClassMapping<*, *>?,
mojangClassMapping: ClassMapping<*, *>,
paperMojangClassMapping: ClassMapping<*, *>?,
targetMappings: ClassMapping<*, *>
) {
for (mojangInnerClassMapping in mojangClassMapping.innerClassMappings) {
val spigotInnerClassMapping = spigotClassMapping?.getInnerClassMapping(mojangInnerClassMapping.obfuscatedName)?.orNull
val paperMojangInnerClassMapping = spigotInnerClassMapping?.deobfuscatedName
?.let { paperMojangClassMapping?.getInnerClassMapping(it) }?.orNull
val fromInnerClassName = mojangInnerClassMapping.deobfuscatedName
val toInnerClassName = spigotInnerClassMapping?.deobfuscatedName ?: mojangInnerClassMapping.deobfuscatedName
val newInnerClassMapping = targetMappings.createInnerClassMapping(fromInnerClassName, toInnerClassName)
mergeSpigotWithMojangMemberMappings(
spigotInnerClassMapping,
mojangInnerClassMapping,
paperMojangInnerClassMapping,
newInnerClassMapping
)
}
for (fieldMapping in mojangClassMapping.fieldMappings) {
targetMappings.createFieldMapping(fieldMapping.deobfuscatedSignature, fieldMapping.obfuscatedName)
}
for (methodMapping in mojangClassMapping.methodMappings) {
targetMappings.createMethodMapping(methodMapping.deobfuscatedSignature, methodMapping.obfuscatedName)
}
// Pick up any changes made through mappings patches
if (paperMojangClassMapping != null) {
for (fieldMapping in paperMojangClassMapping.fieldMappings) {
val obfName = mojangClassMapping.fieldMappings
.firstOrNull { it.deobfuscatedSignature == fieldMapping.deobfuscatedSignature }?.obfuscatedName ?: continue
val deobfFieldType = fieldMapping.deobfuscatedSignature.type.orNull ?: continue
targetMappings.getOrCreateFieldMapping(fieldMapping.deobfuscatedName, deobfFieldType).also {
it.deobfuscatedName = obfName
}
}
for (methodMapping in paperMojangClassMapping.methodMappings) {
val obfName = mojangClassMapping.methodMappings
.firstOrNull { it.deobfuscatedSignature == methodMapping.deobfuscatedSignature }?.obfuscatedName ?: continue
targetMappings.getOrCreateMethodMapping(methodMapping.deobfuscatedSignature).also {
it.deobfuscatedName = obfName
}
}
}
}
}
}

View file

@ -1,313 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import org.cadixdev.bombe.type.signature.FieldSignature
import org.cadixdev.bombe.type.signature.MethodSignature
import org.cadixdev.lorenz.MappingSet
import org.cadixdev.lorenz.merge.MappingSetMerger
import org.cadixdev.lorenz.merge.MappingSetMergerHandler
import org.cadixdev.lorenz.merge.MergeConfig
import org.cadixdev.lorenz.merge.MergeContext
import org.cadixdev.lorenz.merge.MergeResult
import org.cadixdev.lorenz.model.ClassMapping
import org.cadixdev.lorenz.model.FieldMapping
import org.cadixdev.lorenz.model.InnerClassMapping
import org.cadixdev.lorenz.model.MethodMapping
import org.cadixdev.lorenz.model.TopLevelClassMapping
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class GenerateSpigotMappings : DefaultTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val classMappings: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val sourceMappings: RegularFileProperty
@get:OutputFile
abstract val notchToSpigotMappings: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@get:OutputFile
abstract val spigotMemberMappings: RegularFileProperty
@TaskAction
fun run() {
val spigotClassMappings = MappingFormats.CSRG.createReader(classMappings.path).use { it.read() }
val sourceMappings = MappingFormats.TINY.read(
sourceMappings.path,
OBF_NAMESPACE,
DEOBF_NAMESPACE
)
val notchToSpigotSet = MappingSetMerger.create(
spigotClassMappings,
sourceMappings,
MergeConfig.builder()
.withMergeHandler(SpigotMappingsMergerHandler)
.build()
).merge()
val spigotToNamedSet = notchToSpigotSet.reverse().merge(sourceMappings)
MappingFormats.TINY.write(
notchToSpigotSet,
notchToSpigotMappings.path,
OBF_NAMESPACE,
SPIGOT_NAMESPACE
)
MappingFormats.TINY.write(
spigotToNamedSet,
outputMappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
val spigotMembers = createSpigotMemberMappings(sourceMappings, spigotClassMappings)
MappingFormats.CSRG.write(spigotMembers, spigotMemberMappings.path)
}
}
object SpigotMappingsMergerHandler : MappingSetMergerHandler {
//
// TOP LEVEL CLASS
//
override fun mergeTopLevelClassMappings(
left: TopLevelClassMapping,
right: TopLevelClassMapping,
target: MappingSet,
context: MergeContext
): MergeResult<TopLevelClassMapping?> {
throw IllegalStateException("Unexpectedly merged class: ${left.fullObfuscatedName}")
}
override fun mergeDuplicateTopLevelClassMappings(
left: TopLevelClassMapping,
right: TopLevelClassMapping,
rightContinuation: TopLevelClassMapping?,
target: MappingSet,
context: MergeContext
): MergeResult<TopLevelClassMapping?> {
// If both are provided, keep spigot
return MergeResult(
target.createTopLevelClassMapping(left.obfuscatedName, left.deobfuscatedName),
right
)
}
override fun addLeftTopLevelClassMapping(
left: TopLevelClassMapping,
target: MappingSet,
context: MergeContext
): MergeResult<TopLevelClassMapping?> {
throw IllegalStateException(
"Unexpected added class from Spigot: ${left.fullObfuscatedName} - ${left.fullDeobfuscatedName}"
)
}
override fun addRightTopLevelClassMapping(
right: TopLevelClassMapping,
target: MappingSet,
context: MergeContext
): MergeResult<TopLevelClassMapping?> {
// This is a mapping Spigot is totally missing
return MergeResult(
target.createTopLevelClassMapping(right.obfuscatedName, right.obfuscatedName),
right
)
}
//
// INNER CLASS
//
override fun mergeInnerClassMappings(
left: InnerClassMapping,
right: InnerClassMapping,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<InnerClassMapping?> {
throw IllegalStateException("Unexpectedly merged class: ${left.fullObfuscatedName}")
}
override fun mergeDuplicateInnerClassMappings(
left: InnerClassMapping,
right: InnerClassMapping,
rightContinuation: InnerClassMapping?,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<InnerClassMapping?> {
return MergeResult(
target.createInnerClassMapping(left.obfuscatedName, left.deobfuscatedName),
right
)
}
override fun addLeftInnerClassMapping(
left: InnerClassMapping,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<InnerClassMapping> {
throw IllegalStateException(
"Unexpected added class from Spigot: ${left.fullObfuscatedName} - ${left.fullDeobfuscatedName}"
)
}
override fun addRightInnerClassMapping(
right: InnerClassMapping,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<InnerClassMapping?> {
// We want to get all of the inner classes from mojmap, but not the mojmap names
return MergeResult(target.createInnerClassMapping(right.obfuscatedName, right.obfuscatedName), right)
}
//
// FIELD
//
override fun mergeFieldMappings(
left: FieldMapping,
strictRight: FieldMapping?,
looseRight: FieldMapping?,
target: ClassMapping<*, *>,
context: MergeContext
): FieldMapping {
throw IllegalStateException("Unexpectedly merged field: ${left.fullObfuscatedName}")
}
override fun mergeDuplicateFieldMappings(
left: FieldMapping,
strictRightDuplicate: FieldMapping?,
looseRightDuplicate: FieldMapping?,
strictRightContinuation: FieldMapping?,
looseRightContinuation: FieldMapping?,
target: ClassMapping<*, *>,
context: MergeContext
): FieldMapping {
val right = strictRightDuplicate ?: looseRightDuplicate ?: strictRightContinuation ?: looseRightContinuation ?: left
return target.createFieldMapping(right.signature, left.deobfuscatedName)
}
override fun addLeftFieldMapping(left: FieldMapping, target: ClassMapping<*, *>, context: MergeContext): FieldMapping? {
throw IllegalStateException(
"Unexpected added field from Spigot: ${left.fullObfuscatedName} - ${left.fullDeobfuscatedName}"
)
}
//
// METHOD
//
override fun mergeMethodMappings(
left: MethodMapping,
standardRight: MethodMapping?,
wiggledRight: MethodMapping?,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
throw IllegalStateException("Unexpectedly merged method: $left")
}
override fun mergeDuplicateMethodMappings(
left: MethodMapping,
strictRightDuplicate: MethodMapping?,
looseRightDuplicate: MethodMapping?,
strictRightContinuation: MethodMapping?,
looseRightContinuation: MethodMapping?,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
val right = strictRightDuplicate ?: looseRightDuplicate ?: strictRightContinuation ?: looseRightContinuation ?: left
return MergeResult(target.createMethodMapping(left.signature, left.deobfuscatedName), right)
}
override fun addLeftMethodMapping(
left: MethodMapping,
target: ClassMapping<*, *>,
context: MergeContext
): MergeResult<MethodMapping?> {
throw IllegalStateException(
"Unexpected added method from Spigot: ${left.fullObfuscatedName} - ${left.fullDeobfuscatedName}"
)
}
}
private fun createSpigotMemberMappings(mappings: MappingSet, spigotClassMappings: MappingSet): MappingSet {
val newMappings = MappingSet.create()
for (topLevelClassMapping in mappings.topLevelClassMappings) {
val name = spigotClassMappings.getTopLevelClassMapping(topLevelClassMapping.obfuscatedName).orElse(topLevelClassMapping).deobfuscatedName
val newClassMappings = newMappings.createTopLevelClassMapping(name, name)
createSpigotMemberMappings(topLevelClassMapping, newClassMappings, spigotClassMappings)
}
return newMappings
}
private fun createSpigotMemberMappings(old: ClassMapping<*, *>, new: ClassMapping<*, *>, spigotClassMappings: MappingSet) {
for (innerClassMapping in old.innerClassMappings) {
val name = spigotClassMappings.getClassMapping(innerClassMapping.fullObfuscatedName)
.map { it.deobfuscatedName }
.orElse(innerClassMapping.obfuscatedName)
val newClassMappings = new.createInnerClassMapping(name, name)
createSpigotMemberMappings(innerClassMapping, newClassMappings, spigotClassMappings)
}
for (fieldMapping in old.fieldMappings) {
new.createFieldMapping(FieldSignature(fieldMapping.obfuscatedName, fieldMapping.type.get()), fieldMapping.deobfuscatedName)
}
for (methodMapping in old.methodMappings) {
if (methodMapping.deobfuscatedName.contains("$") ||
methodMapping.deobfuscatedName == "<init>" ||
methodMapping.deobfuscatedName == "<clinit>"
) {
continue
}
val desc = spigotClassMappings.deobfuscate(methodMapping.descriptor)
new.createMethodMapping(MethodSignature(methodMapping.obfuscatedName, desc)).also {
it.deobfuscatedName = methodMapping.deobfuscatedName
}
}
}

View file

@ -1,66 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class IncludeMappings : BaseTask() {
@get:InputFile
abstract val inputJar: RegularFileProperty
@get:InputFile
abstract val mappings: RegularFileProperty
@get:Input
abstract val mappingsDest: Property<String>
@get:OutputFile
abstract val outputJar: RegularFileProperty
override fun init() {
super.init()
outputJar.convention(defaultOutput())
}
@TaskAction
private fun addMappings() {
outputJar.path.parent.createDirectories()
inputJar.path.copyTo(outputJar.path, overwrite = true)
outputJar.path.openZip().use { fs ->
val dest = fs.getPath(mappingsDest.get())
dest.parent.createDirectories()
mappings.path.copyTo(dest)
fs.modifyManifest {
mainAttributes.putValue("Included-Mappings-Hash", mappings.path.hashFile(HashingAlgorithm.SHA256).asHexString().uppercase())
}
}
}
}

View file

@ -1,52 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import javax.inject.Inject
import org.gradle.api.DefaultTask
import org.gradle.api.file.ProjectLayout
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.UntrackedTask
@Suppress("LeakingThis")
@UntrackedTask(because = "Git tracks the state")
abstract class InitSubmodules : DefaultTask() {
@get:Inject
abstract val layout: ProjectLayout
@get:Input
abstract val offlineMode: Property<Boolean>
init {
offlineMode.convention(false)
}
@TaskAction
fun run() {
layout.maybeInitSubmodules(offlineMode.get(), logger)
}
}

View file

@ -1,225 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.file.Path
import java.util.NavigableMap
import java.util.TreeMap
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import javax.inject.Inject
import kotlin.collections.set
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkQueue
import org.gradle.workers.WorkerExecutor
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.Label
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
@CacheableTask
abstract class LineMapJar : JavaLauncherTask() {
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Classpath
abstract val decompiledJar: RegularFileProperty
@get:Internal
abstract val jvmArgs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmArgs.convention(listOf("-Xmx512m"))
outputJar.convention(defaultOutput())
}
@TaskAction
fun run() {
lineMapJar(
workerExecutor = workerExecutor,
jvmArgs = jvmArgs.get(),
launcher = launcher.get(),
inputJarPath = inputJar.path,
outputJarPath = outputJar.path,
decompileJarPath = decompiledJar.path
)
}
}
fun lineMapJar(
workerExecutor: WorkerExecutor,
jvmArgs: List<String> = arrayListOf("-Xmx512m"),
launcher: JavaLauncher,
inputJarPath: Path,
outputJarPath: Path,
decompileJarPath: Path,
): WorkQueue {
ensureParentExists(outputJarPath)
ensureDeleted(outputJarPath)
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmArgs)
forkOptions.executable(launcher.executablePath.path.absolutePathString())
}
queue.submit(LineMapJarAction::class) {
inputJar.set(inputJarPath)
outputJar.set(outputJarPath)
decompileJar.set(decompileJarPath)
}
return queue
}
private abstract class LineMapJarAction : WorkAction<LineMapJarAction.Parameters> {
interface Parameters : WorkParameters {
val inputJar: RegularFileProperty
val outputJar: RegularFileProperty
val decompileJar: RegularFileProperty
}
override fun execute() {
val lineMap = readLineMap(parameters.decompileJar.path)
parameters.outputJar.path.writeZip().use { out ->
parameters.inputJar.path.openZip().use { jarFile ->
JarProcessing.processJar(jarFile, out, LineMappingClassProcessor(lineMap))
}
}
}
class LineMappingClassProcessor(private val lineMap: Map<String, NavigableMap<Int, Int>>) : JarProcessing.ClassProcessor.VisitorBased {
override fun processClass(node: ClassNode, parent: ClassVisitor, classNodeCache: ClassNodeCache): ClassVisitor? {
val map = lineMap[node.name.substringBefore('$')]
?: return null // No line maps for class?
return LineMappingVisitor(parent, map)
}
override fun shouldProcess(file: Path): Boolean {
val name = file.toString()
.substring(1) // remove leading /
.substringBefore(".class")
.substringBefore('$')
return name in lineMap
}
}
}
private class LineMappingVisitor(
parent: ClassVisitor?,
private val lineMapping: NavigableMap<Int, Int>
) : ClassVisitor(Opcodes.ASM9, parent) {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<String>?
): MethodVisitor =
MethodLineFixer(super.visitMethod(access, name, descriptor, signature, exceptions), lineMapping)
private class MethodLineFixer(
parent: MethodVisitor?,
private val lineMapping: NavigableMap<Int, Int>
) : MethodVisitor(Opcodes.ASM9, parent) {
override fun visitLineNumber(line: Int, start: Label?) {
var mapped = lineMapping[line]
if (mapped == null) {
val entry = lineMapping.ceilingEntry(line)
if (entry != null) {
mapped = entry.value
}
}
super.visitLineNumber(mapped ?: line, start)
}
}
}
private fun readLineMap(decompileJar: Path): Map<String, NavigableMap<Int, Int>> {
val classes: MutableMap<String, NavigableMap<Int, Int>> = HashMap()
try {
decompileJar.inputStream().use { fis ->
ZipInputStream(fis).use { zip ->
var entry: ZipEntry? = zip.nextEntry
while (entry != null) {
val extra: ByteArray? = entry.extra
if (extra == null || !entry.name.endsWith(".java")) {
entry = zip.nextEntry
continue
}
val buf: ByteBuffer = ByteBuffer.wrap(extra)
buf.order(ByteOrder.LITTLE_ENDIAN)
while (buf.hasRemaining()) {
val id: Short = buf.short
val len: Short = buf.short
if (id.toInt() == 0x4646) { // FF
val cls: String = entry.name.substring(0, entry.name.length - 5)
val ver: Byte = buf.get()
if (ver != 1.toByte()) {
throw PaperweightException("Wrong FF code line version for " + entry.name + " (got $ver, expected 1)")
}
val count = (len - 1) / 4
val lines: NavigableMap<Int, Int> = TreeMap()
for (x in 0 until count) {
val oldLine: Int = buf.short.toInt()
val newLine: Int = buf.short.toInt()
lines[oldLine] = newLine
}
classes[cls] = lines
} else {
buf.position(buf.position() + len)
}
}
entry = zip.nextEntry
}
}
}
} catch (ex: Exception) {
throw PaperweightException("Could not read line maps from decompiled jar: $decompileJar", ex)
}
return classes
}

View file

@ -1,109 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import net.fabricmc.lorenztiny.TinyMappingFormat
import org.cadixdev.lorenz.MappingSet
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
@CacheableTask
abstract class PatchMappings : BaseTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val inputMappings: RegularFileProperty
@get:Optional
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val patch: RegularFileProperty
@get:Input
abstract val fromNamespace: Property<String>
@get:Input
abstract val toNamespace: Property<String>
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@TaskAction
fun run() {
appendPatch(
inputMappings.path,
patch.pathOrNull,
outputMappings.path
)
}
private fun appendPatch(input: Path, patch: Path?, output: Path) {
val mappings = MappingFormats.TINY.readCommented(
input,
fromNamespace.get(),
toNamespace.get()
)
patch?.let {
MappingFormats.TINY.readCommented(
it,
fromNamespace.get(),
toNamespace.get(),
mappings
)
}
MappingFormats.TINY.write(mappings, output, fromNamespace.get(), toNamespace.get())
}
private fun TinyMappingFormat.readCommented(
mappings: Path,
fromNamespace: String,
toNamespace: String,
into: MappingSet? = null
): MappingSet {
val temp = createTempFile("patch", "tiny")
try {
val comment = commentRegex()
// tiny format doesn't allow comments, so we manually remove them
// The tiny mappings reader also doesn't have a InputStream or Reader input...
mappings.useLines { lines ->
temp.bufferedWriter().use { writer ->
for (line in lines) {
val newLine = comment.replace(line, "")
if (newLine.isNotBlank()) {
writer.appendLine(newLine)
}
}
}
}
return into?.let { read(it, temp, fromNamespace, toNamespace) }
?: read(temp, fromNamespace, toNamespace)
} finally {
temp.deleteForcefully()
}
}
}

View file

@ -1,145 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.Action
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.*
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
@Suppress("LeakingThis")
abstract class RelocateClassNameConstants : BaseTask() {
@get:InputFile
abstract val inputJar: RegularFileProperty
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Nested
@get:Optional
abstract val relocations: ListProperty<RelocationInput>
@get:Input
@get:Optional
abstract val processOnly: ListProperty<String>
fun relocate(fromPackage: String, toPackage: String, op: Action<RelocationInput>) {
relocations.add(
objects.newInstance<RelocationInput>().apply {
this.fromPackage.set(fromPackage)
this.toPackage.set(toPackage)
op.execute(this)
}
)
}
init {
outputJar.convention(defaultOutput())
processOnly.convention(
listOf(
"org/bukkit/craftbukkit/**/*.class",
"org/bukkit/craftbukkit/*.class"
)
)
}
@TaskAction
fun run() {
outputJar.path.deleteForcefully()
outputJar.path.parent.createDirectories()
val relocations = relocations.get().map {
RelocationWrapper(Relocation(null, it.fromPackage.get(), it.toPackage.get(), emptyList()))
}
outputJar.path.writeZip().use { outputFs ->
inputJar.path.openZip().use { inputFs ->
val includes = processOnly.getOrElse(emptyList()).map {
inputFs.getPathMatcher("glob:${if (it.startsWith('/')) it else "/$it"}")
}
JarProcessing.processJar(
inputFs,
outputFs,
object : JarProcessing.ClassProcessor.VisitorBased {
override fun shouldProcess(file: Path): Boolean =
includes.isEmpty() || includes.any { it.matches(file) }
override fun processClass(node: ClassNode, parent: ClassVisitor, classNodeCache: ClassNodeCache): ClassVisitor =
ConstantRelocatingClassVisitor(parent, relocations)
}
)
}
}
}
private class ConstantRelocatingClassVisitor(
parent: ClassVisitor,
private val relocations: List<RelocationWrapper>
) : ClassVisitor(Opcodes.ASM9, parent) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
return object : MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) {
override fun visitLdcInsn(value: Any?) {
if (value is String) {
var v: String = value
for (relocation in relocations) {
if (v.startsWith(relocation.fromDot)) {
v = v.replace(relocation.fromDot, relocation.toDot)
} else if (v.startsWith(relocation.fromSlash)) {
v = v.replace(relocation.fromSlash, relocation.toSlash)
}
}
super.visitLdcInsn(v)
} else {
super.visitLdcInsn(value)
}
}
}
}
}
abstract class RelocationInput {
@get:Input
abstract val fromPackage: Property<String>
@get:Input
abstract val toPackage: Property<String>
}
}

View file

@ -1,229 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.jvm.toolchain.JavaLauncher
@CacheableTask
abstract class RemapJar : JavaLauncherTask() {
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val mappingsFile: RegularFileProperty
@get:Input
abstract val fromNamespace: Property<String>
@get:Input
abstract val toNamespace: Property<String>
@get:CompileClasspath
abstract val remapClasspath: ConfigurableFileCollection
@get:Classpath
abstract val remapper: ConfigurableFileCollection
@get:Input
abstract val remapperArgs: ListProperty<String>
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Internal
abstract val jvmArgs: ListProperty<String>
override fun init() {
super.init()
outputJar.convention(defaultOutput())
jvmArgs.convention(listOf("-Xmx1G"))
remapperArgs.convention(TinyRemapper.createArgsList())
}
@TaskAction
fun run() {
if (inputJar.path.absolute().normalize() == outputJar.path.absolute().normalize()) {
throw PaperweightException(
"Invalid configuration, inputJar and outputJar point to the same path: ${inputJar.path}\n" +
"Consider removing customization of output locations, following the default Gradle conventions."
)
}
if (toNamespace.get() != fromNamespace.get()) {
val logFile = layout.cache.resolve(paperTaskOutput("log"))
TinyRemapper.run(
argsList = remapperArgs.get(),
logFile = logFile,
inputJar = inputJar.path,
mappingsFile = mappingsFile.path,
fromNamespace = fromNamespace.get(),
toNamespace = toNamespace.get(),
remapClasspath = remapClasspath.files.map { it.toPath() },
remapper = remapper,
outputJar = outputJar.path,
launcher = launcher.get(),
workingDir = layout.cache,
jvmArgs = jvmArgs.get()
)
} else {
outputJar.path.deleteForcefully()
outputJar.path.parent.createDirectories()
inputJar.path.copyTo(outputJar.path)
}
outputJar.path.openZip().use { fs ->
fs.modifyManifest {
mainAttributes.putValue(MAPPINGS_NAMESPACE_MANIFEST_KEY, toNamespace.get())
}
}
}
}
object TinyRemapper {
private const val minecraftLvPattern = "\\$\\$\\d+"
private const val fixPackageAccessArg = "--fixpackageaccess"
private const val rebuildSourceFileNamesArg = "--rebuildsourcefilenames"
private const val renameInvalidLocalsArg = "--renameinvalidlocals"
private fun invalidLvNamePatternArg(pattern: String) = "--invalidlvnamepattern=$pattern"
private fun threadsArg(num: Int) = "--threads=$num"
private val baseArgs: List<String> = listOf(
"{input}",
"{output}",
"{mappings}",
"{from}",
"{to}",
"{classpath}",
)
val minecraftRemapArgs: List<String> = createArgsList(
fixPackageAccess = true,
renameInvalidLocals = true,
invalidLvNamePattern = minecraftLvPattern,
rebuildSourceFileNames = true,
)
val pluginRemapArgs: List<String> = createArgsList()
fun createArgsList(
fixPackageAccess: Boolean = false,
renameInvalidLocals: Boolean = false,
invalidLvNamePattern: String? = null,
threads: Int = 1,
rebuildSourceFileNames: Boolean = false,
): List<String> {
val args = baseArgs.toMutableList()
args += threadsArg(threads)
if (fixPackageAccess) {
args += fixPackageAccessArg
}
if (renameInvalidLocals) {
args += renameInvalidLocalsArg
}
invalidLvNamePattern?.let { pattern ->
args += invalidLvNamePatternArg(pattern)
}
if (rebuildSourceFileNames) {
args += rebuildSourceFileNamesArg
}
return args
}
private fun List<String>.expandArgs(
input: String,
output: String,
mappings: String,
fromNamespace: String,
toNamespace: String,
classpath: Array<String>,
): List<String> {
val args = mutableListOf<String>()
for (arg in this) {
val mapped = when (arg) {
"{input}" -> input
"{output}" -> output
"{mappings}" -> mappings
"{from}" -> fromNamespace
"{to}" -> toNamespace
"{classpath}" -> classpath
else -> arg
}
when (mapped) {
is String -> args += mapped
is Array<*> -> mapped.mapTo(args) { it as? String ?: throw PaperweightException("Expected String! Got: '$it'.") }
else -> throw PaperweightException("Don't know what to do with '$mapped'!")
}
}
return args
}
fun run(
argsList: List<String>,
logFile: Path,
inputJar: Path,
mappingsFile: Path,
fromNamespace: String,
toNamespace: String,
remapClasspath: List<Path>,
remapper: FileCollection,
outputJar: Path,
launcher: JavaLauncher,
workingDir: Path,
jvmArgs: List<String> = listOf("-Xmx1G"),
) {
ensureDeleted(logFile)
ensureDeleted(outputJar)
val args = argsList.expandArgs(
input = inputJar.absolutePathString(),
output = outputJar.absolutePathString(),
mappings = mappingsFile.absolutePathString(),
fromNamespace = fromNamespace,
toNamespace = toNamespace,
classpath = remapClasspath.map { it.absolutePathString() }.toTypedArray(),
)
ensureParentExists(logFile)
ensureParentExists(outputJar)
launcher.runJar(remapper, workingDir, logFile, jvmArgs = jvmArgs, args = args.toTypedArray())
}
}

View file

@ -1,430 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Files
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import kotlin.streams.asSequence
import org.cadixdev.at.AccessTransformSet
import org.cadixdev.at.io.AccessTransformFormats
import org.cadixdev.mercury.Mercury
import org.cadixdev.mercury.RewriteContext
import org.cadixdev.mercury.SourceProcessor
import org.cadixdev.mercury.SourceRewriter
import org.cadixdev.mercury.at.AccessTransformerRewriter
import org.cadixdev.mercury.extra.AccessAnalyzerProcessor
import org.cadixdev.mercury.remapper.MercuryRemapper
import org.eclipse.jdt.core.JavaCore
import org.eclipse.jdt.core.dom.*
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
@CacheableTask
abstract class RemapSources : JavaLauncherTask() {
@get:CompileClasspath
abstract val vanillaJar: RegularFileProperty
@get:CompileClasspath
abstract val mojangMappedVanillaJar: RegularFileProperty
@get:CompileClasspath
abstract val vanillaRemappedSpigotJar: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val mappings: RegularFileProperty
@get:CompileClasspath
abstract val spigotDeps: ConfigurableFileCollection
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val spigotServerDir: DirectoryProperty
@get:InputDirectory
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val spigotApiDir: DirectoryProperty
@get:Optional
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val additionalAts: RegularFileProperty
@get:OutputFile
abstract val generatedAt: RegularFileProperty
@get:OutputFile
abstract val sourcesOutputZip: RegularFileProperty
@get:OutputFile
abstract val testsOutputZip: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
@get:OutputFile
abstract val spigotRecompiledClasses: RegularFileProperty
@get:Input
abstract val sourceCompatibility: Property<Int>
override fun init() {
super.init()
jvmargs.convention(listOf("-Xmx2G"))
sourcesOutputZip.convention(defaultOutput("$name-sources", "jar"))
testsOutputZip.convention(defaultOutput("$name-tests", "jar"))
generatedAt.convention(defaultOutput("at"))
spigotRecompiledClasses.convention(defaultOutput("spigotRecompiledClasses", "txt"))
sourceCompatibility.convention(21)
}
@TaskAction
fun run() {
val srcOut = findOutputDir(sourcesOutputZip.path).apply { createDirectories() }
val testOut = findOutputDir(testsOutputZip.path).apply { createDirectories() }
try {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmargs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
val srcDir = spigotServerDir.path.resolve("src/main/java")
// Remap sources
queue.submit(RemapAction::class) {
classpath.from(vanillaRemappedSpigotJar.path)
classpath.from(mojangMappedVanillaJar.path)
classpath.from(vanillaJar.path)
classpath.from(spigotApiDir.dir("src/main/java").path)
classpath.from(spigotDeps.files.filter { it.toPath().isLibraryJar })
additionalAts.set(this@RemapSources.additionalAts.pathOrNull)
mappings.set(this@RemapSources.mappings.path)
inputDir.set(srcDir)
cacheDir.set(this@RemapSources.layout.cache)
outputDir.set(srcOut)
generatedAtOutput.set(generatedAt.path)
sourceCompat.set(sourceCompatibility.orNull)
}
val testSrc = spigotServerDir.path.resolve("src/test/java")
// Remap tests
queue.submit(RemapAction::class) {
classpath.from(vanillaRemappedSpigotJar.path)
classpath.from(mojangMappedVanillaJar.path)
classpath.from(vanillaJar.path)
classpath.from(spigotApiDir.dir("src/main/java").path)
classpath.from(spigotDeps.files.filter { it.toPath().isLibraryJar })
classpath.from(srcDir)
additionalAts.set(this@RemapSources.additionalAts.pathOrNull)
mappings.set(this@RemapSources.mappings.path)
inputDir.set(testSrc)
cacheDir.set(this@RemapSources.layout.cache)
outputDir.set(testOut)
sourceCompat.set(sourceCompatibility.orNull)
}
queue.await()
zip(srcOut, sourcesOutputZip)
zip(testOut, testsOutputZip)
writeSpigotRecompiledFiles(srcOut)
} finally {
srcOut.deleteRecursive()
testOut.deleteRecursive()
}
}
private fun writeSpigotRecompiledFiles(srcOut: Path) {
// Write list of java files spigot recompiles
val spigotRecompiled = Files.walk(srcOut).use { stream ->
stream.asSequence().mapNotNull {
if (!it.isRegularFile()) {
return@mapNotNull null
}
if (!it.fileName.pathString.endsWith(".java")) {
return@mapNotNull null
}
val path = srcOut.relativize(it).pathString
if (!path.startsWith("net/minecraft")) {
return@mapNotNull null
}
path.replace(".java", "")
}.sorted().joinToString("\n")
}
spigotRecompiledClasses.path.parent.createDirectories()
spigotRecompiledClasses.path.writeText(spigotRecompiled)
}
abstract class RemapAction : WorkAction<RemapParams> {
override fun execute() {
val mappingSet = MappingFormats.TINY.read(
parameters.mappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
val additionalAt = parameters.additionalAts.pathOrNull?.let { AccessTransformFormats.FML.read(it) }
val processAt = AccessTransformSet.create()
val generatedAtOutPath = parameters.generatedAtOutput.pathOrNull
// Remap any references Spigot maps to mojmap+yarn
Mercury().let { merc ->
merc.sourceCompatibility = parameters.sourceCompat.map { it.toString() }.orNull ?: JavaCore.VERSION_17
merc.isGracefulClasspathChecks = true
merc.classPath.addAll(parameters.classpath.map { it.toPath() })
if (generatedAtOutPath != null) {
merc.processors += AccessAnalyzerProcessor.create(processAt, mappingSet)
}
merc.process(parameters.inputDir.path)
val tempOut = Files.createTempDirectory(parameters.cacheDir.path, "remap")
try {
merc.processors.clear()
merc.processors.addAll(
listOf(
ExplicitThisAdder,
MercuryRemapper.create(mappingSet),
AccessTransformerRewriter.create(processAt)
)
)
if (generatedAtOutPath != null) {
merc.processors.add(AccessTransformerRewriter.create(processAt))
}
merc.rewrite(parameters.inputDir.path, tempOut)
if (additionalAt != null) {
merc.processors.clear()
merc.processors += AccessTransformerRewriter.create(additionalAt)
merc.rewrite(tempOut, parameters.outputDir.path)
} else {
tempOut.copyRecursivelyTo(parameters.outputDir.path)
}
} finally {
tempOut.deleteRecursive()
}
}
if (generatedAtOutPath != null) {
AccessTransformFormats.FML.write(generatedAtOutPath, processAt)
}
}
}
interface RemapParams : WorkParameters {
val classpath: ConfigurableFileCollection
val mappings: RegularFileProperty
val inputDir: RegularFileProperty
val additionalAts: RegularFileProperty
val cacheDir: RegularFileProperty
val generatedAtOutput: RegularFileProperty
val outputDir: RegularFileProperty
val sourceCompat: Property<Int>
}
object ExplicitThisAdder : SourceRewriter {
override fun getFlags(): Int = SourceProcessor.FLAG_RESOLVE_BINDINGS
override fun rewrite(context: RewriteContext) {
context.compilationUnit.accept(ExplicitThisAdderVisitor(context))
}
}
class ExplicitThisAdderVisitor(private val context: RewriteContext) : ASTVisitor() {
override fun visit(node: SimpleName): Boolean {
val binding = node.resolveBinding() ?: return false
val name = when (val declaringNode = context.compilationUnit.findDeclaringNode(binding)) {
is VariableDeclarationFragment -> declaringNode.name
is MethodDeclaration -> declaringNode.name
null -> null
else -> return false
}
if (name != null && name === node) {
// this is the actual declaration
return false
}
visit(node, binding)
return false
}
private fun visit(node: SimpleName, binding: IBinding) {
if (binding.kind != IBinding.VARIABLE && binding.kind != IBinding.METHOD) {
return
}
val referringClass = when (binding) {
is IVariableBinding -> {
if (!binding.isField || binding.isEnumConstant) {
return
}
binding.declaringClass
}
is IMethodBinding -> {
if (binding.isConstructor || binding.isSynthetic) {
return
}
binding.declaringClass
}
else -> return
}
val modifiers = when (binding) {
is IVariableBinding -> binding.modifiers
is IMethodBinding -> binding.modifiers
else -> return
}
when (val p = node.parent) {
is FieldAccess, is SuperFieldAccess, is ThisExpression, is MethodReference, is SuperMethodInvocation, is MemberValuePair -> return
is MethodInvocation -> {
if (!p.arguments().contains(node)) {
if (p.expression != null && p.expression !== node) {
return
}
}
}
is QualifiedName -> {
if (p.qualifier !== node) {
return
}
}
is ClassInstanceCreation -> {
if (!p.arguments().contains(node)) {
return
}
}
}
val rewrite = context.createASTRewrite()
val fieldAccess = rewrite.ast.newFieldAccess()
val expr: Expression = if (!Modifier.isStatic(modifiers)) {
val accessible = mutableListOf<ITypeBinding>()
var curr: ASTNode = node
while (true) {
if (curr is TypeDeclaration) {
accessible += curr.resolveBinding() ?: break
} else if (curr is AnonymousClassDeclaration) {
accessible += curr.resolveBinding() ?: break
}
val m = when (curr) {
is MethodDeclaration -> curr.modifiers
is FieldDeclaration -> curr.modifiers
is Initializer -> curr.modifiers
is TypeDeclaration -> curr.modifiers
else -> null
}
if (m != null && Modifier.isStatic(m)) {
break
}
curr = curr.parent ?: break
}
rewrite.ast.newThisExpression().also { thisExpr ->
if (accessible.size == 1) {
return@also
}
val accessibleTargetCls = accessible.find { referringClass.isCastCompatible(it) }
?: return@also
if (accessibleTargetCls.isAnonymous) {
if (accessible.indexOf(accessibleTargetCls) == 0) {
return@also
}
return
}
if (!accessibleTargetCls.isCastCompatible(accessible[0])) {
val name = getNameNode(accessibleTargetCls)
?: throw PaperweightException("Could not find name node for ${accessibleTargetCls.qualifiedName}")
thisExpr.qualifier = rewrite.createCopyTarget(name) as Name
}
}
} else {
// find declaring method
var parentNode: ASTNode? = node
loop@ while (parentNode != null) {
when (parentNode) {
is MethodDeclaration, is AnonymousClassDeclaration, is LambdaExpression, is Initializer -> break@loop
}
parentNode = parentNode.parent
}
if (parentNode is Initializer && Modifier.isStatic(parentNode.modifiers)) {
// Can't provide explicit static receiver here
return
}
val name = getNameNode(referringClass) ?: return
rewrite.createCopyTarget(name) as Name
}
fieldAccess.expression = expr
fieldAccess.name = rewrite.createMoveTarget(node) as SimpleName
rewrite.replace(node, fieldAccess, null)
}
private fun getNameNode(dec: ITypeBinding): Name? {
val typeDec = context.compilationUnit.findDeclaringNode(dec) as? TypeDeclaration ?: return null
return typeDec.name
}
}
}

View file

@ -1,169 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import java.util.jar.Attributes
import java.util.jar.Manifest
import kotlin.io.path.*
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import org.gradle.jvm.toolchain.JavaLauncher
val vineFlowerArgList: List<String> = listOf(
"--synthetic-not-set=true",
"--ternary-constant-simplification=true",
"--include-runtime=current",
"--decompile-complex-constant-dynamic=true",
"--indent-string= ",
"--decompile-inner=true", // is default
"--remove-bridge=true", // is default
"--decompile-generics=true", // is default
"--ascii-strings=false", // is default
"--remove-synthetic=true", // is default
"--include-classpath=true",
"--inline-simple-lambdas=true", // is default
"--ignore-invalid-bytecode=false", // is default
"--bytecode-source-mapping=true",
"--dump-code-lines=true",
"--override-annotation=false", // We add override annotations ourselves. Vineflower's impl doesn't work as well yet and conflicts
"-cfg", // Pass the libraries as an argument file to avoid command line length limits
"{libraries}",
"{input}",
"{output}"
)
private fun List<String>.createDecompilerArgs(
libraries: String,
input: String,
output: String,
): List<String> = map {
when (it) {
"{libraries}" -> libraries
"{input}" -> input
"{output}" -> output
else -> it
}
}
fun runDecompiler(
argsList: List<String>,
logFile: Path,
workingDir: Path,
executable: FileCollection,
inputJar: Path,
libraries: List<Path>,
outputJar: Path,
javaLauncher: JavaLauncher,
jvmArgs: List<String> = listOf("-Xmx4G")
) {
val libs = ArrayList(libraries)
libs.sort()
val tempFile = createTempFile("paperweight", "txt")
try {
val vineflower = isVineflower(executable)
tempFile.bufferedWriter().use { writer ->
for (lib in libs) {
if (lib.isLibraryJar) {
if (vineflower) {
writer.appendLine("--add-external=${lib.absolutePathString()}")
} else {
writer.appendLine("-e=${lib.absolutePathString()}")
}
}
}
}
val argList = argsList.createDecompilerArgs(
tempFile.absolutePathString(),
inputJar.absolutePathString(),
outputJar.absolutePathString(),
)
outputJar.deleteForcefully()
logFile.deleteForcefully()
outputJar.parent.createDirectories()
javaLauncher.runJar(executable, workingDir, logFile, jvmArgs = jvmArgs, args = argList.toTypedArray())
} finally {
tempFile.deleteForcefully()
}
}
private fun isVineflower(executable: FileCollection) = executable.files.any {
it.toPath().openZip().use { fs ->
val manifest = fs.getPath("META-INF/MANIFEST.MF").takeIf { f -> f.isRegularFile() }?.inputStream()?.buffered()?.use { reader ->
Manifest(reader)
}
manifest != null &&
manifest.mainAttributes.containsKey(Attributes.Name("Implementation-Name")) &&
manifest.mainAttributes.getValue("Implementation-Name").equals("Vineflower", ignoreCase = true)
}
}
@CacheableTask
abstract class RunVineFlower : JavaLauncherTask() {
@get:Classpath
abstract val executable: ConfigurableFileCollection
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:CompileClasspath
abstract val libraries: ConfigurableFileCollection
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
override fun init() {
super.init()
jvmargs.convention(listOf("-Xmx4G"))
outputJar.convention(defaultOutput())
}
@TaskAction
fun run() {
runDecompiler(
vineFlowerArgList,
layout.cache.resolve(paperTaskOutput("log")),
layout.cache,
executable,
inputJar.path,
libraries.files.map { it.toPath() },
outputJar.path,
launcher.get(),
jvmargs.get()
)
}
}

View file

@ -1,170 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.FileSystem
import java.nio.file.Files
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Internal
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 org.objectweb.asm.tree.ClassNode
abstract class ScanJar : JavaLauncherTask() {
companion object {
private val logger: Logger = Logging.getLogger(ScanJar::class.java)
}
@get:Classpath
abstract val jarToScan: RegularFileProperty
@get:Classpath
abstract val classpath: ConfigurableFileCollection
@get:OutputFile
abstract val log: RegularFileProperty
@get:Internal
abstract val jvmArgs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmArgs.convention(listOf("-Xmx768m"))
log.set(layout.cache.resolve(paperTaskOutput("txt")))
}
@TaskAction
fun run() {
val launcher = launcher.get()
val jvmArgs = jvmArgs.get()
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmArgs)
forkOptions.executable(launcher.executablePath.path.absolutePathString())
}
this.queue(queue)
}
abstract fun queue(queue: WorkQueue)
abstract class ScanJarAction<P : ScanJarAction.BaseParameters> : WorkAction<P>, AsmUtil {
interface BaseParameters : WorkParameters {
val jarToScan: RegularFileProperty
val classpath: ConfigurableFileCollection
val log: RegularFileProperty
}
protected val log = mutableListOf<String>()
final override fun execute() {
parameters.jarToScan.path.openZip().use { scan ->
var fail: Exception? = null
val classPathDirs = mutableListOf<Path>()
val classPathJars = mutableListOf<FileSystem>()
parameters.classpath.forEach {
if (it.isDirectory) {
classPathDirs.add(it.toPath())
return@forEach
}
if (!it.isFile || !it.name.endsWith(".jar")) {
return@forEach
}
try {
classPathJars += it.toPath().openZip()
} catch (ex: Exception) {
logger.error("Failed to open zip $it", ex)
if (fail == null) {
fail = ex
} else {
fail!!.addSuppressed(ex)
}
}
}
try {
if (fail != null) {
throw PaperweightException("Failed to read classpath jars", fail)
}
val classNodeCache = ClassNodeCache.create(scan, classPathJars, classPathDirs)
scan(scan, classNodeCache)
} finally {
var err: Exception? = null
classPathJars.forEach {
try {
it.close()
} catch (ex: Exception) {
logger.error("Failed to close zip $it", ex)
if (err == null) {
err = ex
} else {
err!!.addSuppressed(ex)
}
}
}
if (err != null) {
throw PaperweightException("Failed to close classpath jars", err)
}
}
}
if (!Files.exists(parameters.log.path.parent)) {
Files.createDirectories(parameters.log.path.parent)
}
parameters.log.path.writeLines(log)
if (log.isNotEmpty()) {
throw PaperweightException("Bad code was found, see log file at ${parameters.log.path.toAbsolutePath()}")
}
}
private fun scan(scan: FileSystem, classNodeCache: ClassNodeCache) {
scan.walk().use { stream ->
stream.forEach { file ->
if (!Files.isRegularFile(file) || !file.fileName.toString().endsWith(".class")) {
return@forEach
}
val classNode = classNodeCache.findClass(file.toString()) ?: return@forEach
this.handleClass(classNode, classNodeCache)
}
}
}
protected abstract fun handleClass(classNode: ClassNode, classNodeCache: ClassNodeCache)
}
}

View file

@ -1,115 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkQueue
import org.objectweb.asm.Handle
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.InvokeDynamicInsnNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
@CacheableTask
abstract class ScanJarForBadCalls : ScanJar() {
companion object {
private val logger: Logger = Logging.getLogger(ScanJarForBadCalls::class.java)
}
@get:Input
abstract val badAnnotations: SetProperty<String>
override fun queue(queue: WorkQueue) {
queue.submit(ScanJarForBadCallsAction::class) {
jarToScan.set(this@ScanJarForBadCalls.jarToScan)
classpath.from(this@ScanJarForBadCalls.classpath)
log.set(this@ScanJarForBadCalls.log)
badAnnotations.set(this@ScanJarForBadCalls.badAnnotations)
}
}
abstract class ScanJarForBadCallsAction : ScanJarAction<ScanJarForBadCallsAction.Parameters>(), AsmUtil {
interface Parameters : BaseParameters {
val badAnnotations: SetProperty<String>
}
override fun handleClass(classNode: ClassNode, classNodeCache: ClassNodeCache) {
for (method in classNode.methods) {
method.instructions.forEach { handleInstruction(classNode, method, it, classNodeCache) }
}
}
private fun handleInstruction(classNode: ClassNode, method: MethodNode, absIsnNode: AbstractInsnNode, classNodeCache: ClassNodeCache) {
when (absIsnNode) {
is InvokeDynamicInsnNode -> handleInvokeDynamic(classNode, method, absIsnNode, classNodeCache)
is MethodInsnNode -> handleMethodInvocation(classNode, method, absIsnNode, classNodeCache)
}
}
private fun handleInvokeDynamic(
classNode: ClassNode,
method: MethodNode,
invokeDynamicInsnNode: InvokeDynamicInsnNode,
classNodeCache: ClassNodeCache
) {
if (invokeDynamicInsnNode.bsm.owner == "java/lang/invoke/LambdaMetafactory" && invokeDynamicInsnNode.bsmArgs.size > 1) {
when (val methodHandle = invokeDynamicInsnNode.bsmArgs[1]) {
is Handle -> checkMethod(classNode, method, methodHandle.owner, methodHandle.name, methodHandle.desc, classNodeCache)
}
}
}
private fun handleMethodInvocation(classNode: ClassNode, method: MethodNode, methodIsnNode: MethodInsnNode, classNodeCache: ClassNodeCache) {
checkMethod(classNode, method, methodIsnNode.owner, methodIsnNode.name, methodIsnNode.desc, classNodeCache)
}
private fun checkMethod(classNode: ClassNode, method: MethodNode, owner: String, name: String, desc: String, classNodeCache: ClassNodeCache) {
val targetOwner = classNodeCache.findClass(owner) ?: return
val target = targetOwner.methods.find {
it.name == name && it.desc == desc
} ?: return
val annotations = (target.visibleAnnotations ?: emptyList()) + (target.invisibleAnnotations ?: emptyList())
annotations.find { it.desc in parameters.badAnnotations.get() } ?: return
val msg = warnMsg(classNode, method, targetOwner, target)
log += msg
logger.error(msg)
}
private fun warnMsg(classNode: ClassNode, method: MethodNode, targetOwner: ClassNode, target: MethodNode): String {
val methodDelimiter = if (Opcodes.ACC_STATIC in method.access) '.' else '#'
val targetMethodDelimiter = if (Opcodes.ACC_STATIC in target.access) '.' else '#'
return "Method ${classNode.name}$methodDelimiter${method.name}${method.desc} " +
"includes reference to bad method ${targetOwner.name}$targetMethodDelimiter${target.name}${target.desc}"
}
}
}

View file

@ -1,86 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkQueue
import org.objectweb.asm.tree.ClassNode
@CacheableTask
abstract class ScanJarForOldGeneratedCode : ScanJar() {
companion object {
private val logger: Logger = Logging.getLogger(ScanJarForOldGeneratedCode::class.java)
}
@get:Input
abstract val mcVersion: Property<String>
@get:Input
abstract val annotation: Property<String>
override fun queue(queue: WorkQueue) {
queue.submit(ScanJarForOldGeneratedCodeAction::class) {
jarToScan.set(this@ScanJarForOldGeneratedCode.jarToScan)
classpath.from(this@ScanJarForOldGeneratedCode.classpath)
log.set(this@ScanJarForOldGeneratedCode.log)
mcVersion.set(this@ScanJarForOldGeneratedCode.mcVersion)
annotation.set(this@ScanJarForOldGeneratedCode.annotation)
}
}
abstract class ScanJarForOldGeneratedCodeAction : ScanJarAction<ScanJarForOldGeneratedCodeAction.Parameters>() {
interface Parameters : BaseParameters {
val mcVersion: Property<String>
val annotation: Property<String>
}
override fun handleClass(classNode: ClassNode, classNodeCache: ClassNodeCache) {
val annotations = (classNode.visibleAnnotations ?: emptyList()) + (classNode.invisibleAnnotations ?: emptyList())
val generatedAnnotation = annotations
.find { it.desc == parameters.annotation.get() }
?.values
?.chunked(2)
?.find { it[0] == "value" } ?: return
val generatedVersion = generatedAnnotation[1].toString()
val mcVersion = parameters.mcVersion.get()
if (generatedVersion != mcVersion) {
val msg = errorMsg(classNode, generatedVersion, mcVersion)
log += msg
logger.error(msg)
}
}
private fun errorMsg(classNode: ClassNode, generatedVersion: String, mcVersion: String): String {
return "Class ${classNode.name} is marked as being generated in version $generatedVersion when the set version is $mcVersion"
}
}
}

View file

@ -1,417 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import io.papermc.paperweight.util.data.*
import java.nio.file.Path
import javax.inject.Inject
import javax.xml.parsers.DocumentBuilderFactory
import kotlin.io.path.*
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.component.ComponentIdentifier
import org.gradle.api.artifacts.dsl.DependencyFactory
import org.gradle.api.attributes.java.TargetJvmEnvironment
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.internal.project.ProjectInternal.DetachedResolver
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.work.DisableCachingByDefault
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkQueue
import org.gradle.workers.WorkerExecutor
import org.w3c.dom.Document
import org.w3c.dom.Element
// Not cached since these are Mojang's files
abstract class DownloadTask : DefaultTask() {
@get:Input
abstract val url: Property<String>
@get:OutputFile
abstract val outputFile: RegularFileProperty
@get:Internal
abstract val downloader: Property<DownloadService>
@get:Nested
@get:Optional
abstract val expectedHash: Property<Hash>
@TaskAction
fun run() = downloader.get().download(url, outputFile, expectedHash.orNull)
}
@CacheableTask
abstract class DownloadMcLibraries : BaseTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val mcLibrariesFile: RegularFileProperty
@get:Input
abstract val repositories: ListProperty<String>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Internal
abstract val downloader: Property<DownloadService>
@get:Inject
abstract val workerExecutor: WorkerExecutor
@get:Input
abstract val sources: Property<Boolean>
override fun init() {
super.init()
sources.convention(false)
}
@TaskAction
fun run() {
downloadMinecraftLibraries(
downloader,
workerExecutor,
outputDir.path,
repositories.get(),
mcLibrariesFile.path.readLines(),
sources.get()
)
}
}
fun downloadMinecraftLibraries(
download: Provider<DownloadService>,
workerExecutor: WorkerExecutor,
targetDir: Path,
repositories: List<String>,
mcLibraries: List<String>,
sources: Boolean
): WorkQueue {
val excludes = listOf(targetDir.fileSystem.getPathMatcher("glob:*.etag"))
targetDir.deleteRecursive(excludes)
val queue = workerExecutor.noIsolation()
for (lib in mcLibraries) {
if (sources) {
queue.submit(DownloadSourcesToDirAction::class) {
repos.set(repositories)
artifact.set(lib)
target.set(targetDir)
downloader.set(download)
}
} else {
queue.submit(DownloadWorker::class) {
repos.set(repositories)
artifact.set(lib)
target.set(targetDir)
downloadToDir.set(true)
downloader.set(download)
}
}
}
return queue
}
@DisableCachingByDefault(because = "Gradle handles caching")
abstract class DownloadSpigotDependencies : BaseTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val apiPom: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val serverPom: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val mcLibrariesFile: RegularFileProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:OutputDirectory
abstract val outputSourcesDir: DirectoryProperty
@get:Internal
abstract val downloader: Property<DownloadService>
@get:Inject
abstract val workerExecutor: WorkerExecutor
@get:Inject
abstract val dependencyFactory: DependencyFactory
private val detachedResolver: DetachedResolver = (project as ProjectInternal).newDetachedResolver()
@TaskAction
fun run() {
val apiSetup = parsePom(apiPom.path)
val serverSetup = parsePom(serverPom.path)
val mcLibraries = mcLibrariesFile.path.readLines()
val out = outputDir.path
out.deleteRecursive()
val outSources = outputSourcesDir.path
outSources.deleteRecursive()
val spigotRepos = mutableSetOf<String>()
spigotRepos += apiSetup.repos
spigotRepos += serverSetup.repos
val artifacts = mutableSetOf<MavenArtifact>()
artifacts += apiSetup.artifacts
artifacts += serverSetup.artifacts
val resolver = detachedResolver
for (repo in spigotRepos) {
resolver.repositories.maven(repo)
}
val config = resolver.configurations.create("spigotDependencies") {
attributes {
attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named(TargetJvmEnvironment.STANDARD_JVM))
}
}
for (artifact in artifacts) {
val gav = artifact.gav.let {
if (it == "com.google.guava:guava:32.1.2-jre") {
// https://github.com/google/guava/issues/6657
"com.google.guava:guava:32.1.3-jre"
} else {
it
}
}
config.dependencies.add(
dependencyFactory.create(gav).also {
it.artifact {
artifact.classifier?.let { s -> classifier = s }
artifact.extension?.let { s -> extension = s }
}
}
)
}
// The source variants don't have transitives
val flatComponents = mutableSetOf<ComponentIdentifier>()
for (artifact in config.incoming.artifacts.artifacts) {
artifact.file.toPath().copyTo(outputDir.path.resolve(artifact.file.name).also { it.parent.createDirectories() }, true)
flatComponents += artifact.id.componentIdentifier
}
val sourcesConfig = resolver.configurations.create("spigotDependenciesSources") {
attributes {
// Mojang libs & Guava don't resolve metadata correctly, so we set the classifier below instead...
// attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
// attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType.SOURCES))
// attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
// attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
// Needed since we set the classifier instead of using above attributes
attribute(TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, objects.named(TargetJvmEnvironment.STANDARD_JVM))
}
}
for (component in flatComponents) {
sourcesConfig.dependencies.add(
dependencyFactory.create(component.displayName).also {
it.artifact {
classifier = "sources"
}
}
)
}
val sourcesView = sourcesConfig.incoming.artifactView {
componentFilter {
mcLibraries.none { l -> l == it.displayName } &&
// This is only needed since we don't use variant-aware resolution properly
it.displayName != "com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava"
}
}
for (artifact in sourcesView.artifacts.artifacts) {
artifact.file.toPath().copyTo(outputSourcesDir.path.resolve(artifact.file.name).also { it.parent.createDirectories() }, true)
}
}
private fun parsePom(pomFile: Path): MavenSetup {
val depList = arrayListOf<MavenArtifact>()
// todo dum
depList += MavenArtifact(
"com.google.code.findbugs",
"jsr305",
"3.0.2"
)
depList += MavenArtifact(
"org.apache.logging.log4j",
"log4j-api",
"2.17.0"
)
depList += MavenArtifact(
"org.jetbrains",
"annotations",
"23.0.0"
)
val repoList = arrayListOf<String>()
// Maven Central is implicit
repoList += MAVEN_CENTRAL_URL
val builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
val doc = pomFile.inputStream().buffered().use { stream ->
stream.buffered().use { buffered ->
builder.parse(buffered)
}
}
doc.documentElement.normalize()
depList += doc.extractDependencies()
repoList += doc.extractRepos()
return MavenSetup(repos = repoList, artifacts = depList)
}
private fun Document.extractDependencies(): List<MavenArtifact> {
val depList = arrayListOf<MavenArtifact>()
val list = getElementsByTagName("dependencies")
val node = list.item(0) as Element // Only want the first dependencies element
val depNode = node.getElementsByTagName("dependency")
for (j in 0 until depNode.length) {
val dependency = depNode.item(j) as? Element ?: continue
val artifact = getDependency(dependency) ?: continue
depList += artifact
}
return depList
}
private fun Document.extractRepos(): List<String> {
val repoList = arrayListOf<String>()
val repos = getElementsByTagName("repositories")
for (i in 0 until repos.length) {
val node = repos.item(i) as? Element ?: continue
val depNode = node.getElementsByTagName("repository")
for (j in 0 until depNode.length) {
val repo = depNode.item(j) as? Element ?: continue
val repoUrl = repo.getElementsByTagName("url").item(0).textContent
repoList += repoUrl
}
}
return repoList
}
private fun getDependency(node: Element): MavenArtifact? {
val scopeNode = node.getElementsByTagName("scope")
val scope = if (scopeNode.length == 0) {
"compile"
} else {
scopeNode.item(0).textContent
}
if (scope != "compile") {
return null
}
val group = node.getElementsByTagName("groupId").item(0).textContent
val artifact = node.getElementsByTagName("artifactId").item(0).textContent
val version = node.getElementsByTagName("version").item(0).textContent
if (version.contains("\${")) {
// Don't handle complicated things
// We don't need to (for now anyways)
return null
}
return MavenArtifact(
group = group,
artifact = artifact,
version = version
)
}
}
data class MavenSetup(
val repos: List<String>,
val artifacts: List<MavenArtifact>
)
interface DownloadParams : WorkParameters {
val repos: ListProperty<String>
val artifact: Property<String>
val target: RegularFileProperty
val downloadToDir: Property<Boolean>
val downloader: Property<DownloadService>
}
abstract class DownloadWorker : WorkAction<DownloadParams> {
override fun execute() {
val target = parameters.target.path
val artifact = MavenArtifact.parse(parameters.artifact.get())
if (parameters.downloadToDir.get()) {
artifact.downloadToDir(parameters.downloader.get(), target, parameters.repos.get())
} else {
artifact.downloadToFile(parameters.downloader.get(), target, parameters.repos.get())
}
}
}
abstract class DownloadSourcesToDirAction : WorkAction<DownloadSourcesToDirAction.Params> {
interface Params : WorkParameters {
val repos: ListProperty<String>
val artifact: Property<String>
val target: RegularFileProperty
val downloader: Property<DownloadService>
}
override fun execute() {
val sourceArtifact = MavenArtifact.parse(parameters.artifact.get())
.copy(classifier = "sources")
try {
sourceArtifact.downloadToDir(
parameters.downloader.get(),
parameters.target.path,
parameters.repos.get()
)
} catch (ignored: Exception) {
// Ignore failures because not every artifact we attempt to download actually has sources
}
}
}

View file

@ -1,61 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.*
import javax.inject.Inject
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Nested
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.jvm.toolchain.JavaToolchainService
private fun JavaLauncherTaskBase.defaultJavaLauncher(project: Project): Provider<JavaLauncher> =
javaToolchainService.defaultJavaLauncher(project)
interface JavaLauncherTaskBase {
@get:Nested
val launcher: Property<JavaLauncher>
@get:Inject
val javaToolchainService: JavaToolchainService
}
abstract class JavaLauncherTask : BaseTask(), JavaLauncherTaskBase {
override fun init() {
super.init()
launcher.convention(defaultJavaLauncher(project))
}
}
abstract class JavaLauncherZippedTask : ZippedTask(), JavaLauncherTaskBase {
override fun init() {
super.init()
launcher.convention(defaultJavaLauncher(project))
}
}

View file

@ -1,296 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.tasks.patchremap
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import org.cadixdev.at.io.AccessTransformFormats
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.gradle.kotlin.dsl.*
abstract class RemapPatches : BaseTask() {
@get:InputDirectory
abstract val inputPatchDir: DirectoryProperty
@get:InputDirectory
abstract val apiPatchDir: DirectoryProperty
@get:InputFile
abstract val mappingsFile: RegularFileProperty
@get:InputFile
abstract val ats: RegularFileProperty
@get:Classpath
abstract val classpathJars: ConfigurableFileCollection
@get:InputDirectory
abstract val spigotApiDir: DirectoryProperty
@get:InputDirectory
abstract val spigotServerDir: DirectoryProperty
@get:InputFile
abstract val spigotDecompJar: RegularFileProperty
@get:InputDirectory
abstract val mcLibrarySourcesDir: DirectoryProperty
@get:InputFile
abstract val devImports: RegularFileProperty
@get:Input
abstract val ignoreGitIgnore: Property<Boolean>
@get:OutputDirectory
abstract val outputPatchDir: DirectoryProperty
@get:Internal
@get:Option(
option = "continue-remap",
description = "For resuming, don't recreate remap dir and pick up where last run left off"
)
abstract val continueRemapping: Property<Boolean>
@get:Internal
@get:Option(
option = "limit-patches",
description = "For testing, you can limit the # of patches (e.g. --limit-patches=10)"
)
abstract val limitPatches: Property<String>
@get:Inject
abstract val providers: ProviderFactory
override fun init() {
continueRemapping.convention(false)
ignoreGitIgnore.convention(Git.ignoreProperty(providers)).finalizeValueOnRead()
}
@TaskAction
fun run() {
val metaFile = layout.cache / "paperweight" / "remap-meta"
val meta = if (metaFile.exists()) {
if (continueRemapping.get()) {
gson.fromJson<RemapMeta>(metaFile)
} else {
metaFile.deleteForcefully()
null
}
} else {
null
}
// Check patches
val inputElements = inputPatchDir.path.listDirectoryEntries().sorted()
if (inputElements.any { it.isRegularFile() }) {
println("Remap patch input directory must only contain directories or patch files, not both")
return
}
if (inputElements.size == 1) {
println("No patches to remap, only 1 patch set found")
return
}
val patchesToSkip = inputElements.dropLast(1).flatMap { it.listDirectoryEntries("*.patch").sorted() }
val patchesToRemap = inputElements.last().listDirectoryEntries("*.patch").sorted()
if (patchesToRemap.isEmpty()) {
println("No input patches to remap found")
return
}
val limit = limitPatches.map { it.toInt() }.orElse(patchesToRemap.size).get()
val mappings = MappingFormats.TINY.read(mappingsFile.path, SPIGOT_NAMESPACE, DEOBF_NAMESPACE)
// This should pull in any libraries needed for type bindings
val configFiles = project.project(":Paper-Server").configurations["runtimeClasspath"].resolve().map { it.toPath() }
val classpathFiles = classpathJars.map { it.toPath() } + configFiles
// Remap output directory, after each output this directory will be re-named to the input directory below for
// the next remap operation
println("setting up repo")
val tempApiDir = createWorkDir("patch-remap-api", source = spigotApiDir.path, recreate = !continueRemapping.get())
val tempInputDir = createWorkDirByCloning(
"patch-remap-input",
source = spigotServerDir.path,
recreate = !continueRemapping.get()
)
val tempOutputDir = createWorkDir("patch-remap-output")
val sourceInputDir = tempInputDir.resolve("src/main/java")
PatchSourceRemapWorker(
mappings,
AccessTransformFormats.FML.read(ats.path),
listOf(*classpathFiles.toTypedArray(), tempApiDir.resolve("src/main/java")),
sourceInputDir,
tempOutputDir
).let { remapper ->
val patchApplier = PatchApplier("remapped", "old", ignoreGitIgnore.get(), tempInputDir)
if (!continueRemapping.get()) {
// first run
patchApplier.createBranches()
// We need to include any missing classes for the patches later on
McDev.importMcDev(
patches = patchesToSkip + patchesToRemap,
decompJar = spigotDecompJar.path,
importsFile = devImports.path,
targetDir = tempInputDir.resolve("src/main/java"),
librariesDirs = listOf(mcLibrarySourcesDir.path)
)
patchApplier.commitPlain("McDev imports")
}
if (meta == null || meta.stage == RemapStage.PRE_REMAP) {
var foundResume = false
val patchesToApply = patchesToSkip.dropWhile { patch ->
when {
meta == null -> false
meta.patchSet == patch.parent.name && meta.patchName == patch.name -> {
foundResume = true
true
}
else -> !foundResume
}
}
println("Applying ${patchesToApply.size} patches before remapping")
for (patch in patchesToApply) {
metaFile.deleteForcefully()
metaFile.bufferedWriter().use { writer ->
gson.toJson(RemapMeta(RemapStage.PRE_REMAP, patch.parent.name, patch.name), writer)
}
patchApplier.applyPatch(patch)
}
patchApplier.checkoutRemapped() // Switch to remapped branch without checking out files
remapper.remap() // Remap to new mappings
patchApplier.commitInitialRemappedSource() // Initial commit of pre-remap sources mapped to new mappings
patchApplier.checkoutOld() // Normal checkout back to pre-remap mappings branch
} else if (patchApplier.isUnfinishedPatch()) {
println("===========================")
println("Finishing current patch")
println("===========================")
patchApplier.recordCommit()
patchApplier.checkoutRemapped()
remapper.remap()
patchApplier.commitChanges()
patchApplier.checkoutOld()
println("===========================")
println("done with current patch")
println("===========================")
}
// Repo setup is done, we can begin the patch loop now
var counter = 0
var remapSkip = meta != null && meta.stage == RemapStage.REMAP
for (patch in patchesToRemap) {
if (remapSkip && meta != null) {
if (meta.patchSet == patch.parent.name && meta.patchName == patch.name) {
remapSkip = false
continue
}
continue
}
metaFile.deleteForcefully()
metaFile.bufferedWriter().use { writer ->
gson.toJson(RemapMeta(RemapStage.REMAP, patch.parent.name, patch.name), writer)
}
println("===========================")
println("attempting to remap $patch")
println("===========================")
patchApplier.applyPatch(patch) // Apply patch on Spigot mappings
patchApplier.recordCommit() // Keep track of commit author, message, and time
patchApplier.checkoutRemapped() // Switch to remapped branch without checkout out files
remapper.remap() // Remap to new mappings
patchApplier.commitChanges() // Commit the changes
patchApplier.checkoutOld() // Normal checkout back to Spigot mappings branch
println("===========================")
println("done remapping patch $patch")
println("===========================")
counter++
if (counter >= limit) {
break
}
}
patchApplier.generatePatches(outputPatchDir.path)
}
}
private fun createWorkDir(name: String, source: Path? = null, recreate: Boolean = true): Path {
return layout.cache.resolve("paperweight").resolve(name).apply {
if (recreate) {
deleteRecursive()
createDirectories()
source?.copyRecursivelyTo(this)
}
}
}
private fun createWorkDirByCloning(name: String, source: Path, recreate: Boolean = true): Path {
val workDir = layout.cache.resolve("paperweight")
return workDir.resolve(name).apply {
if (recreate) {
deleteRecursive()
createDirectories()
Git(workDir)("clone", source.absolutePathString(), this.absolutePathString()).executeSilently()
}
}
}
data class RemapMeta(
val stage: RemapStage,
val patchSet: String,
val patchName: String
)
enum class RemapStage {
PRE_REMAP,
REMAP
}
}

View file

@ -1,48 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import java.nio.file.FileSystem
import java.nio.file.Path
import org.objectweb.asm.tree.ClassNode
interface ClassNodeCache {
fun findClass(name: String?): ClassNode?
companion object {
fun create(
jarFile: FileSystem,
vararg fallbackJars: FileSystem?
): ClassNodeCache {
return ClassNodeCacheImpl(jarFile, fallbackJars.toList())
}
fun create(
jarFile: FileSystem,
fallbackJars: List<FileSystem>,
fallbackDirs: List<Path>
): ClassNodeCache {
return ClassNodeCacheImpl(jarFile, fallbackJars.toList(), fallbackDirs)
}
}
}

View file

@ -1,93 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import java.nio.file.FileSystem
import java.nio.file.Path
import kotlin.io.path.*
import org.objectweb.asm.ClassReader
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
class ClassNodeCacheImpl(
private val jarFile: FileSystem,
private val fallbackJars: List<FileSystem?>,
private val fallbackDirectories: List<Path>? = null
) : ClassNodeCache {
private val classNodeMap = hashMapOf<String, ClassNode?>()
override fun findClass(name: String?): ClassNode? {
if (name == null) {
return null
}
return classNodeMap.computeIfAbsent(normalize(name)) { fileName ->
val classData = findClassData(fileName) ?: return@computeIfAbsent null
val classReader = ClassReader(classData)
val node = ClassNode(Opcodes.ASM9)
classReader.accept(node, 0)
return@computeIfAbsent node
}
}
private fun findClassData(className: String): ByteArray? {
jarFile.getPath(className).let { remappedClass ->
if (remappedClass.exists()) {
return remappedClass.readBytes()
}
}
for (fallbackJar in fallbackJars) {
fallbackJar?.getPath(className)?.let { libraryClass ->
if (libraryClass.exists()) {
return libraryClass.readBytes()
}
}
}
fallbackDirectories?.let { dirs ->
for (path in dirs) {
path.resolve(className).takeIf { it.exists() }
?.let { libraryClass ->
if (libraryClass.exists()) {
return libraryClass.readBytes()
}
}
}
}
return ClassLoader.getSystemResourceAsStream(className)?.readBytes() // JDK class
}
private fun normalize(name: String): String {
val workingName = name.removeSuffix(".class")
var startIndex = 0
var endIndex = workingName.length
if (workingName.startsWith('L')) {
startIndex = 1
}
if (workingName.endsWith(';')) {
endIndex--
}
return workingName.substring(startIndex, endIndex).replace('.', '/') + ".class"
}
}

View file

@ -1,101 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import java.nio.file.FileSystem
import java.nio.file.Path
import kotlin.io.path.*
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.tree.ClassNode
object JarProcessing {
interface ClassProcessor {
fun shouldProcess(file: Path): Boolean = true
interface NodeBased : ClassProcessor {
fun processClass(node: ClassNode, classNodeCache: ClassNodeCache)
}
interface VisitorBased : ClassProcessor {
fun processClass(node: ClassNode, parent: ClassVisitor, classNodeCache: ClassNodeCache): ClassVisitor?
}
}
fun processJar(
jarFile: FileSystem,
output: FileSystem,
processor: ClassProcessor
) = processJar(jarFile, null, output, processor)
fun processJar(
jarFile: FileSystem,
fallbackJar: FileSystem?,
output: FileSystem,
processor: ClassProcessor
) {
val classNodeCache = ClassNodeCache.create(jarFile, fallbackJar)
jarFile.walk().use { stream ->
stream.forEach { file ->
processFile(file, output, classNodeCache, processor)
}
}
}
private fun processFile(file: Path, output: FileSystem, classNodeCache: ClassNodeCache, processor: ClassProcessor) {
val outFile = output.getPath(file.absolutePathString())
if (file.isDirectory()) {
outFile.createDirectories()
return
}
if (!file.name.endsWith(".class")) {
file.copyTo(outFile)
return
}
if (processor.shouldProcess(file)) {
processClass(file, outFile, classNodeCache, processor)
} else {
file.copyTo(outFile)
}
}
private fun processClass(file: Path, outFile: Path, classNodeCache: ClassNodeCache, processor: ClassProcessor) {
val node = classNodeCache.findClass(file.toString()) ?: error("No ClassNode found for known entry: ${file.name}")
val writer = ClassWriter(0)
val visitor = when (processor) {
is ClassProcessor.VisitorBased -> processor.processClass(node, writer, classNodeCache) ?: writer
is ClassProcessor.NodeBased -> {
processor.processClass(node, classNodeCache)
writer
}
else -> error("Unknown class processor type: ${processor::class.java.name}")
}
node.accept(visitor)
outFile.writeBytes(writer.toByteArray())
}
}

View file

@ -1,63 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import kotlin.io.path.*
fun makeMcDevSrc(
cache: Path,
decompileJar: Path,
target: Path,
paperProject: Path,
paperSource: Path = paperProject.resolve("src/main/java"),
dataSource: Path = paperProject.resolve("src/main/resources")
) {
val lockFile = cache.resolve(applyPatchesLock(paperProject))
val alreadyHave = acquireProcessLockWaiting(lockFile)
try {
ensureDeleted(target)
decompileJar.openZip().use { fs ->
val root = fs.getPath("/")
fs.walk().use { stream ->
stream.forEach { sourceFile ->
if (sourceFile.isRegularFile()) {
val sourceFilePath = sourceFile.relativeTo(root).invariantSeparatorsPathString
if (!paperSource.resolve(sourceFilePath).isRegularFile() && !dataSource.resolve(sourceFilePath).isRegularFile()) {
val targetFile = target.resolve(sourceFilePath)
targetFile.parent.createDirectories()
sourceFile.copyTo(targetFile)
}
}
}
}
}
} finally {
if (!alreadyHave) {
lockFile.deleteForcefully()
}
}
}

View file

@ -1,267 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import io.papermc.paperweight.PaperweightException
import java.nio.file.FileSystem
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
object McDev {
private val logger: Logger = Logging.getLogger(McDev::class.java)
fun importMcDev(
patches: Iterable<Path>,
decompJar: Path,
importsFile: Path?,
targetDir: Path,
dataTargetDir: Path? = null,
librariesDirs: List<Path> = listOf(),
printOutput: Boolean = true
) {
val (javaPatchLines, dataPatchLines) = readPatchLines(patches)
decompJar.openZip().use { zipFile ->
val decompSourceFiles = mutableSetOf<String>()
val decompDataFiles = mutableSetOf<String>()
zipFile.walk().use { stream ->
for (zipEntry in stream) {
// substring(1) trims the leading /
val path = zipEntry.invariantSeparatorsPathString.substring(1)
if (path.endsWith(".java")) {
decompSourceFiles += path
}
if (path.startsWith("data/")) {
decompDataFiles += path
}
// pull in all package-info classes
if (zipEntry.toString().endsWith("package-info.java")) {
val targetFile = targetDir.resolve(path)
if (targetFile.exists()) {
continue
}
if (!targetFile.parent.exists()) {
targetFile.parent.createDirectories()
}
zipEntry.copyTo(targetFile)
}
}
}
val exactJavaImports = javaPatchLines.filter { decompSourceFiles.contains(it) }
.map { targetDir.resolve(it) }
val exactDataImports = if (dataTargetDir != null) {
dataPatchLines.map { "data/minecraft/$it" }.filter { decompDataFiles.contains(it) }
.map { dataTargetDir.resolve(it) }
} else {
listOf()
}
val (additionalSrcImports, additionalDataImports) = readAdditionalImports(importsFile)
val srcMatcherImports = additionalSrcImports.distinct()
.map { zipFile.getPathMatcher("glob:/$it.java") }
val dataMatcherImports = additionalDataImports.distinct()
.map { zipFile.getPathMatcher("glob:/data/minecraft/$it") }
val (srcImportMcDev, dataImportMcDev) = zipFile.walk().use { stream ->
val src = hashSetOf<Path>()
val data = hashSetOf<Path>()
stream.forEach { file ->
if (srcMatcherImports.any { it.matches(file) }) {
src.add(targetDir.resolve(file.invariantSeparatorsPathString.substring(1)))
} else if (dataTargetDir != null && dataMatcherImports.any { it.matches(file) }) {
data.add(dataTargetDir.resolve(file.invariantSeparatorsPathString.substring(1)))
}
}
Pair((src + exactJavaImports).filterNot { it.exists() }, (data + exactDataImports).filterNot { it.exists() })
}
logger.log(if (printOutput) LogLevel.LIFECYCLE else LogLevel.DEBUG, "Importing {} classes from vanilla...", srcImportMcDev.size)
importFiles(srcImportMcDev, targetDir, zipFile, printOutput)
if (dataTargetDir != null) {
logger.log(
if (printOutput) LogLevel.LIFECYCLE else LogLevel.DEBUG,
"Importing {} data files from vanilla...",
dataImportMcDev.size
)
importFiles(dataImportMcDev, dataTargetDir, zipFile, printOutput, true)
}
}
if (librariesDirs.isEmpty()) {
return
}
val libFiles = librariesDirs.flatMap { it.listDirectoryEntries("*-sources.jar") }
if (libFiles.isEmpty()) {
throw PaperweightException("No library files found")
}
// Import library classes
val imports = findLibraries(importsFile, libFiles, javaPatchLines)
logger.log(if (printOutput) LogLevel.LIFECYCLE else LogLevel.DEBUG, "Importing {} classes from library sources...", imports.size)
for ((libraryFileName, importFilePath) in imports) {
val libFile = libFiles.firstOrNull { it.name == libraryFileName }
?: throw PaperweightException("Failed to find library: $libraryFileName for class $importFilePath")
val outputFile = targetDir.resolve(importFilePath)
if (outputFile.exists()) {
continue
}
outputFile.parent.createDirectories()
libFile.openZip().use { zipFile ->
val libEntry = zipFile.getPath(importFilePath)
libEntry.copyTo(outputFile)
}
}
}
private fun importFiles(files: List<Path>, targetDir: Path, zipFile: FileSystem, printOutput: Boolean, checkFinalNewline: Boolean = false) {
for (file in files) {
if (!file.parent.exists()) {
file.parent.createDirectories()
}
val vanillaFile = file.relativeTo(targetDir).toString()
val zipPath = zipFile.getPath(vanillaFile)
if (zipPath.notExists()) {
logger.log(if (printOutput) LogLevel.WARN else LogLevel.DEBUG, "Skipped importing '{}': File not found", file.toString())
continue
}
zipPath.copyTo(file)
if (checkFinalNewline) {
var content = file.readText(Charsets.UTF_8)
if (!content.endsWith("\n")) {
content += "\n"
file.writeText(content, Charsets.UTF_8)
}
}
}
}
private fun readPatchLines(patches: Iterable<Path>): Pair<Set<String>, Set<String>> {
val srcResult = hashSetOf<String>()
val dataResult = hashSetOf<String>()
val javaPrefix = "+++ b/src/main/java/"
val dataPrefix = "+++ b/src/main/resources/data/minecraft/"
for (patch in patches) {
patch.useLines { lines ->
val matches = lines.partition {
it.startsWith(javaPrefix)
}
matches.first
.mapTo(srcResult) { it.substring(javaPrefix.length, it.length) }
matches.second
.filter { it.startsWith(dataPrefix) }
.mapTo(dataResult) { it.substring(dataPrefix.length, it.length) }
}
}
return Pair(srcResult, dataResult)
}
private fun readAdditionalImports(
additionalClasses: Path?
): Pair<Set<String>, Set<String>> {
val srcResult = hashSetOf<String>()
val dataResult = hashSetOf<String>()
val suffix = ".java"
additionalClasses?.useLines { lines ->
lines.filterNot { it.startsWith("#") }
.forEach {
val parts = it.split(" ")
if (parts[0] == "minecraft") {
srcResult += parts[1].removeSuffix(suffix).replace('.', '/')
} else if (parts[0] == "mc_data") {
dataResult += parts[1]
}
}
}
return Pair(srcResult, dataResult)
}
private fun findLibraries(libraryImports: Path?, libFiles: List<Path>, patchLines: Set<String>): Set<LibraryImport> {
val result = hashSetOf<LibraryImport>()
// Imports from library-imports.txt
libraryImports?.useLines { lines ->
lines.filterNot { it.startsWith('#') }
.map { it.split(' ') }
.filter { it.size == 2 }
.filter { it[0] != "minecraft" && it[0] != "mc_data" }
.mapTo(result) { parts ->
val libFileName = libFiles.firstOrNull { it.name.startsWith(parts[0]) }?.name
?: throw PaperweightException("Failed to read library line '${parts[0]} ${parts[1]}', no library file was found.")
LibraryImport(libFileName, parts[1].removeSuffix(".java").replace('.', '/') + ".java")
}
}
// Scan patches for necessary imports
result += findNeededLibraryImports(patchLines, libFiles)
return result
}
private fun findNeededLibraryImports(patchLines: Set<String>, libFiles: List<Path>): Set<LibraryImport> {
val knownImportMap = findPossibleLibraryImports(libFiles)
.associateBy { it.importFilePath }
val prefix = "+++ b/src/main/java/"
return patchLines.map { it.substringAfter(prefix) }
.mapNotNull { knownImportMap[it] }
.toSet()
}
private fun findPossibleLibraryImports(libFiles: List<Path>): Collection<LibraryImport> {
val found = hashSetOf<LibraryImport>()
val suffix = ".java"
libFiles.map { libFile ->
libFile.openZip().use { zipFile ->
zipFile.walk()
.filter { it.isRegularFile() && it.name.endsWith(suffix) }
.map { sourceFile ->
LibraryImport(libFile.name, sourceFile.toString().substring(1))
}
.forEach(found::add)
}
}
return found
}
private data class LibraryImport(val libraryFileName: String, val importFilePath: String)
}

View file

@ -1,88 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import java.nio.file.Path
import org.cadixdev.at.io.AccessTransformFormats
import org.cadixdev.mercury.Mercury
import org.cadixdev.mercury.at.AccessTransformerRewriter
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
object PaperAt {
fun apply(workerExecutor: WorkerExecutor, apiDir: Path, serverDir: Path, atFile: Path?) {
if (atFile == null) {
return
}
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs("-Xmx2G")
}
val srcDir = serverDir.resolve("src/main/java")
// Remap sources
queue.submit(AtAction::class) {
classpath.from(apiDir.resolve("src/main/java"))
inputDir.set(srcDir)
outputDir.set(srcDir)
ats.set(atFile)
}
queue.await()
}
interface AtParams : WorkParameters {
val classpath: ConfigurableFileCollection
val inputDir: DirectoryProperty
val outputDir: DirectoryProperty
val ats: RegularFileProperty
}
abstract class AtAction : WorkAction<AtParams> {
override fun execute() {
Mercury().let { merc ->
merc.classPath.addAll(parameters.classpath.map { it.toPath() })
merc.isGracefulClasspathChecks = true
merc.process(parameters.inputDir.path)
merc.processors.clear()
merc.processors.addAll(
listOf(
AccessTransformerRewriter.create(AccessTransformFormats.FML.read(parameters.ats.path))
)
)
merc.rewrite(parameters.inputDir.path, parameters.outputDir.path)
}
}
}
}

View file

@ -1,108 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
/*
* This was adapted from code originally written by Pokechu22 in MCInjector
* Link: https://github.com/ModCoderPack/MCInjector/pull/3
*/
class ParameterAnnotationFixer(private val node: ClassNode) : AsmUtil {
fun visitNode() {
val expected = expectedSyntheticParams() ?: return
for (method in node.methods) {
if (method.name == "<init>") {
processConstructor(method, expected)
}
}
}
private fun expectedSyntheticParams(): List<Type>? {
if (Opcodes.ACC_ENUM in node.access) {
return listOf(Type.getObjectType("java/lang/String"), Type.INT_TYPE)
}
val innerNode = node.innerClasses.firstOrNull { it.name == node.name } ?: return null
if (innerNode.innerName == null || (Opcodes.ACC_STATIC or Opcodes.ACC_INTERFACE) in innerNode.access) {
return null
}
if (innerNode.outerName == null) {
// method local class/other complex case
return null
}
return listOf(Type.getObjectType(innerNode.outerName))
}
private fun processConstructor(method: MethodNode, synthParams: List<Type>) {
val params = Type.getArgumentTypes(method.desc).asList()
if (!params.beginsWith(synthParams)) {
return
}
method.visibleParameterAnnotations = process(params.size, synthParams.size, method.visibleParameterAnnotations)
method.invisibleParameterAnnotations =
process(params.size, synthParams.size, method.invisibleParameterAnnotations)
method.visibleParameterAnnotations?.let {
method.visibleAnnotableParameterCount = it.size
}
method.invisibleParameterAnnotations?.let {
method.invisibleAnnotableParameterCount = it.size
}
}
private fun process(
paramCount: Int,
synthCount: Int,
annotations: Array<List<AnnotationNode>>?
): Array<List<AnnotationNode>>? {
if (annotations == null) {
return null
}
if (paramCount == annotations.size) {
return annotations.copyOfRange(synthCount, paramCount)
}
return annotations
}
private fun <T> List<T>.beginsWith(other: List<T>): Boolean {
if (this.size < other.size) {
return false
}
for (i in other.indices) {
if (this[i] != other[i]) {
return false
}
}
return true
}
}

View file

@ -1,124 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util.constants
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.Task
const val PAPERWEIGHT_EXTENSION = "paperweight"
const val PAPERWEIGHT_DEBUG = "paperweight.debug"
const val PAPERWEIGHT_VERBOSE_APPLY_PATCHES = "paperweight.verboseApplyPatches"
const val MC_LIBRARY_URL = "https://libraries.minecraft.net/"
const val MC_MANIFEST_URL = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
const val PAPER_MAVEN_REPO_URL = "https://repo.papermc.io/repository/maven-public/"
const val MAVEN_CENTRAL_URL = "https://repo.maven.apache.org/maven2/"
const val PARAM_MAPPINGS_CONFIG = "paramMappings"
const val REMAPPER_CONFIG = "remapper"
const val PLUGIN_REMAPPER_CONFIG = "pluginRemapper"
const val DECOMPILER_CONFIG = "decompiler"
const val PAPERCLIP_CONFIG = "paperclip"
const val DEV_BUNDLE_CONFIG = "paperweightDevelopmentBundle"
const val MOJANG_MAPPED_SERVER_CONFIG = "mojangMappedServer"
const val MOJANG_MAPPED_SERVER_RUNTIME_CONFIG = "mojangMappedServerRuntime"
const val REOBF_CONFIG = "reobf"
const val CONSUMABLE_RUNTIME_CLASSPATH = "consumableRuntimeClasspath"
const val SERVER_RUNTIME_CLASSPATH = "serverRuntimeClasspath"
const val PARAM_MAPPINGS_REPO_NAME = "paperweightParamMappingsRepository"
const val DECOMPILER_REPO_NAME = "paperweightDecompilerRepository"
const val REMAPPER_REPO_NAME = "paperweightRemapperRepository"
const val CACHE_PATH = "caches"
private const val PAPER_PATH = "paperweight"
const val LOCK_DIR = "$PAPER_PATH/lock"
const val USERDEV_SETUP_LOCK = "$LOCK_DIR/userdev/setup.lock"
const val APPLY_PATCHES_LOCK_DIR = "$LOCK_DIR/apply-patches"
fun applyPatchesLock(targetDir: Path): String = APPLY_PATCHES_LOCK_DIR + '/' +
targetDir.absolutePathString().hash(HashingAlgorithm.SHA256).asHexString() + ".lock"
const val UPSTREAMS = "$PAPER_PATH/upstreams"
const val UPSTREAM_WORK_DIR_PROPERTY = "paperweightUpstreamWorkDir"
const val PAPERWEIGHT_PREPARE_DOWNSTREAM = "prepareForDownstream"
const val PAPERWEIGHT_DOWNSTREAM_FILE_PROPERTY = "paperweightDownstreamDataFile"
private const val JARS_PATH = "$PAPER_PATH/jars"
const val MINECRAFT_JARS_PATH = "$JARS_PATH/minecraft"
const val MINECRAFT_SOURCES_PATH = "$JARS_PATH/minecraft-sources"
const val SPIGOT_JARS_PATH = "$JARS_PATH/spigot"
const val SPIGOT_SOURCES_JARS_PATH = "$JARS_PATH/spigot-sources"
private const val MAPPINGS_DIR = "$PAPER_PATH/mappings"
const val SERVER_MAPPINGS = "$MAPPINGS_DIR/server_mappings.txt"
const val MOJANG_YARN_MAPPINGS = "$MAPPINGS_DIR/official-mojang+yarn.tiny"
const val SPIGOT_MOJANG_YARN_MAPPINGS = "$MAPPINGS_DIR/spigot-mojang+yarn.tiny"
const val OBF_SPIGOT_MAPPINGS = "$MAPPINGS_DIR/official-spigot.tiny"
const val SPIGOT_MEMBER_MAPPINGS = "$MAPPINGS_DIR/spigot-members.csrg"
const val CLEANED_SPIGOT_MOJANG_YARN_MAPPINGS = "$MAPPINGS_DIR/spigot-mojang+yarn-cleaned.tiny"
const val PATCHED_SPIGOT_MOJANG_YARN_MAPPINGS = "$MAPPINGS_DIR/spigot-mojang+yarn-patched.tiny"
const val PATCHED_SPIGOT_MOJANG_YARN_SOURCE_MAPPINGS = "$MAPPINGS_DIR/spigot-mojang+yarn-patched-source.tiny"
const val REOBF_MOJANG_SPIGOT_MAPPINGS = "$MAPPINGS_DIR/mojang+yarn-spigot-reobf.tiny"
const val PATCHED_REOBF_MOJANG_SPIGOT_MAPPINGS = "$MAPPINGS_DIR/mojang+yarn-spigot-reobf-patched.tiny"
const val RELOCATED_PATCHED_REOBF_MOJANG_SPIGOT_MAPPINGS = "$MAPPINGS_DIR/mojang+yarn-spigot-reobf-patched-relocated.tiny"
const val OBF_NAMESPACE = "official"
const val SPIGOT_NAMESPACE = "spigot"
const val DEOBF_NAMESPACE = "mojang+yarn"
const val MAPPINGS_NAMESPACE_MANIFEST_KEY = "paperweight-mappings-namespace"
private const val DATA_PATH = "$PAPER_PATH/data"
const val MC_MANIFEST = "$DATA_PATH/McManifest.json"
const val VERSION_JSON = "$DATA_PATH/McVersion.json"
private const val BUNDLER_PATH = "$DATA_PATH/bundler"
const val SERVER_VERSION_JSON = "$BUNDLER_PATH/version.json"
const val SERVER_LIBRARIES_TXT = "$BUNDLER_PATH/ServerLibraries.txt"
const val SERVER_LIBRARIES_LIST = "$BUNDLER_PATH/libraries.list"
const val SERVER_VERSIONS_LIST = "$BUNDLER_PATH/versions.list"
private const val SETUP_CACHE = "$PAPER_PATH/setupCache"
private const val TASK_CACHE = "$PAPER_PATH/taskCache"
const val FINAL_REMAPPED_JAR = "$TASK_CACHE/minecraft.jar"
const val FINAL_FILTERED_REMAPPED_JAR = "$TASK_CACHE/filteredMinecraft.jar"
const val FINAL_DECOMPILE_JAR = "$TASK_CACHE/decompileJar.jar"
const val MC_DEV_SOURCES_DIR = "$PAPER_PATH/mc-dev-sources"
const val IVY_REPOSITORY = "$PAPER_PATH/ivyRepository"
const val DOWNLOAD_SERVICE_NAME = "paperweightDownloadService"
fun paperSetupOutput(name: String, ext: String) = "$SETUP_CACHE/$name.$ext"
fun Task.paperTaskOutput(ext: String) = paperTaskOutput(name, ext)
fun paperTaskOutput(name: String, ext: String) = "$TASK_CACHE/$name.$ext"

View file

@ -1,59 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util.data
import java.nio.file.Path
import kotlin.io.path.*
data class FileEntry<T>(
val hash: String,
val id: T,
val path: String
) {
override fun toString(): String {
return "$hash\t$id\t$path"
}
companion object {
const val VERSION_JSON = "version.json"
const val VERSIONS_DIR = "META-INF/versions"
const val LIBRARIES_DIR = "META-INF/libraries"
const val VERSIONS_LIST = "META-INF/versions.list"
const val LIBRARIES_LIST = "META-INF/libraries.list"
fun parse(file: Path): List<FileEntry<String>> {
return parse(file) { it }
}
fun <T> parse(file: Path, transform: (String) -> T): List<FileEntry<T>> {
return file.readLines().mapNotNull { line ->
if (line.isBlank() || line.startsWith("#")) {
return@mapNotNull null
}
val (hash, id, path) = line.split("\t")
FileEntry(hash, transform(id), path)
}
}
}
}

View file

@ -1,30 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util.data
data class LibraryChange(
val inputId: ModuleId,
val inputPath: String,
val outputId: ModuleId,
val outputPath: String
)

View file

@ -1,60 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util.data
import io.papermc.paperweight.util.*
data class MinecraftManifest(
val latest: Map<String, *>,
val versions: List<ManifestVersion>
)
data class ManifestVersion(
val id: String,
val type: String,
val time: String,
val releaseTime: String,
val url: String,
val sha1: String,
) {
fun hash(): Hash = Hash(sha1, HashingAlgorithm.SHA1)
}
data class MinecraftVersionManifest(
val downloads: Map<String, Download>,
) {
data class Download(
val sha1: String,
val url: String,
) {
fun hash(): Hash = Hash(sha1, HashingAlgorithm.SHA1)
}
fun download(name: String): Download {
return downloads[name] ?: error("No such download '$name' in version manifest")
}
fun serverDownload(): Download = download("server")
fun serverMappingsDownload(): Download = download("server_mappings")
}

View file

@ -1,62 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util.data
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.internal.component.external.model.DefaultModuleComponentArtifactIdentifier
data class ModuleId(val group: String, val name: String, val version: String, val classifier: String? = null) : Comparable<ModuleId> {
fun toPath(): String {
val fileName = listOfNotNull(name, version, classifier).joinToString("-") + ".jar"
return "${group.replace('.', '/')}/$name/$version/$fileName"
}
override fun compareTo(other: ModuleId): Int {
return comparator.compare(this, other)
}
override fun toString(): String = listOfNotNull(group, name, version, classifier).joinToString(":")
companion object {
private val comparator = compareBy<ModuleId>({ it.group }, { it.name }, { it.version }, { it.classifier })
fun parse(text: String): ModuleId {
val split = text.split(":")
val (group, name, version) = split
return ModuleId(group, name, version, split.getOrNull(3))
}
fun fromIdentifier(id: ComponentArtifactIdentifier): ModuleId {
if (id is DefaultModuleComponentArtifactIdentifier) {
val idx = id.componentIdentifier
return ModuleId(idx.group, idx.module, idx.version, id.name.classifier)
}
val compId = id.componentIdentifier
if (compId is ModuleComponentIdentifier) {
return ModuleId(compId.group, compId.module, compId.version)
}
error("Could not create ModuleId from ComponentArtifactIdentifier $id")
}
}
}

View file

@ -1,63 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util.data
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
data class UpstreamData(
val vanillaJar: Path,
val remappedJar: Path,
val decompiledJar: Path,
val mcVersion: String,
val libDir: Path,
val libSourceDir: Path,
val libFile: Path,
val spigotLibSourcesDir: Path,
val mappings: Path,
val notchToSpigotMappings: Path,
val sourceMappings: Path,
val reobfPackagesToFix: List<String>,
val reobfMappingsPatch: Path,
val vanillaIncludes: List<String>,
val paramMappings: MavenDep,
val accessTransform: Path,
val spigotRecompiledClasses: Path,
val bundlerVersionJson: Path,
val serverLibrariesTxt: Path,
val serverLibrariesList: Path
)
fun readUpstreamData(inputFile: Any): UpstreamData = inputFile.convertToPath().let { file ->
try {
if (file.isRegularFile()) {
gson.fromJson(file)
} else {
throw PaperweightException("Upstream data file does not exist.")
}
} catch (ex: Exception) {
throw PaperweightException("Failed to read upstream data.", ex)
}
}

View file

@ -1,59 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.repositories.RepositoryContentDescriptor
import org.gradle.api.provider.Provider
data class MavenDep(val url: String, val coordinates: List<String>)
fun determineMavenDep(url: Provider<String>, configuration: Provider<Configuration>): MavenDep {
return MavenDep(url.get(), determineArtifactCoordinates(configuration.get()))
}
fun determineArtifactCoordinates(configuration: Configuration): List<String> {
return configuration.dependencies.filterIsInstance<ModuleDependency>().map { dep ->
sequenceOf(
"group" to dep.group,
"name" to dep.name,
"version" to dep.version,
"classifier" to (dep.artifacts.singleOrNull()?.classifier ?: "")
).filter {
if (it.second == null) error("No ${it.first}: $dep")
it.second?.isNotEmpty() ?: false
}.map {
it.second
}.joinToString(":")
}
}
fun RepositoryContentDescriptor.includeFromDependencyNotation(dependencyNotation: String) {
val split = dependencyNotation.split(':')
when {
split.size == 1 -> includeGroup(split[0])
split.size == 2 -> includeModule(split[0], split[1])
split.size >= 3 -> includeVersion(split[0], split[1], split[2])
}
}

View file

@ -1,86 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import java.time.Duration
import java.time.temporal.ChronoUnit
/**
* Map of accepted abbreviation [Char]s to [ChronoUnit].
*/
private val units = mapOf(
'd' to ChronoUnit.DAYS,
'h' to ChronoUnit.HOURS,
'm' to ChronoUnit.MINUTES,
's' to ChronoUnit.SECONDS
)
/**
* Parses a [Duration] from [input].
*
* Accepted format is a number followed by a unit abbreviation.
* See [units] for possible units.
* Example input strings: `["1d", "12h", "1m", "30s"]`
*
* @param input formatted input string
* @throws InvalidDurationException when [input] is improperly formatted
*/
@Throws(InvalidDurationException::class)
fun parseDuration(input: String): Duration {
if (input.isBlank()) {
throw InvalidDurationException.noInput(input)
}
if (input.length < 2) {
throw InvalidDurationException.invalidInput(input)
}
val unitAbbreviation = input.last()
val unit = units[unitAbbreviation] ?: throw InvalidDurationException.invalidInput(input)
val length = try {
input.substring(0, input.length - 1).toLong()
} catch (ex: NumberFormatException) {
throw InvalidDurationException.invalidInput(input, ex)
}
return Duration.of(length, unit)
}
private class InvalidDurationException private constructor(
message: String,
cause: Throwable? = null
) : IllegalArgumentException(message, cause) {
companion object {
private val infoMessage = """
Accepted format is a number followed by a unit abbreviation.
Possible units: $units
Example input strings: ["1d", "12h", "1m", "30s"]
""".trimIndent()
fun noInput(input: String): InvalidDurationException =
InvalidDurationException("Cannot parse a Duration from a blank input string '$input'.\n$infoMessage")
fun invalidInput(input: String, cause: Throwable? = null) =
InvalidDurationException("Cannot parse a Duration from input '$input'.\n$infoMessage", cause)
}
}

View file

@ -1,259 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import io.papermc.paperweight.PaperweightException
import java.io.InputStream
import java.net.URI
import java.nio.file.FileSystem
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.PathMatcher
import java.nio.file.attribute.DosFileAttributeView
import java.util.Arrays
import java.util.stream.Collectors
import java.util.stream.Stream
import java.util.stream.StreamSupport
import kotlin.io.path.*
import kotlin.streams.asSequence
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.file.FileSystemLocationProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Provider
// utils for dealing with java.nio.file.Path and java.io.File
val FileSystemLocation.path: Path
get() = asFile.toPath()
val Provider<out FileSystemLocation>.path: Path
get() = get().path
val Provider<out FileSystemLocation>.pathOrNull: Path?
get() = orNull?.path
fun FileSystemLocationProperty<*>.set(path: Path?) = set(path?.toFile())
fun <P : FileSystemLocationProperty<*>> P.pathProvider(path: Provider<Path>) = apply { fileProvider(path.map { it.toFile() }) }
fun DirectoryProperty.convention(project: Project, path: Provider<Path>) = convention(project.layout.dir(path.map { it.toFile() }))
fun RegularFileProperty.convention(project: Project, path: Provider<Path>) = convention(project.layout.file(path.map { it.toFile() }))
fun DirectoryProperty.convention(project: Project, path: Path) = convention(project.layout.dir(project.provider { path.toFile() }))
val Path.isLibraryJar: Boolean
get() = name.endsWith(".jar") && !name.endsWith("-sources.jar")
fun Path.deleteForcefully() {
fixWindowsPermissionsForDeletion()
deleteIfExists()
}
fun Path.deleteRecursive(excludes: Iterable<PathMatcher> = emptyList()) {
if (!exists()) {
return
}
if (!isDirectory()) {
if (excludes.any { it.matches(this) }) {
return
}
fixWindowsPermissionsForDeletion()
deleteIfExists()
return
}
val fileList = Files.walk(this).use { stream ->
stream.asSequence().filterNot { file -> excludes.any { it.matches(file) } }.toList()
}
fileList.forEach { f -> f.fixWindowsPermissionsForDeletion() }
fileList.asReversed().forEach { f ->
// Don't try to delete directories where the excludes glob has caused files to not get deleted inside it
if (f.isRegularFile()) {
f.deleteIfExists()
} else if (f.isDirectory() && f.listDirectoryEntries().isEmpty()) {
f.deleteIfExists()
}
}
}
private val isWindows = System.getProperty("os.name").contains("windows", ignoreCase = true)
private fun Path.fixWindowsPermissionsForDeletion() {
if (!isWindows || notExists()) {
return
}
runCatching {
val dosAttr = fileAttributesView<DosFileAttributeView>()
dosAttr.setHidden(false)
dosAttr.setReadOnly(false)
}
}
fun Path.copyRecursivelyTo(target: Path) {
target.createDirectories()
if (!exists()) {
return
}
Files.walk(this).use { stream ->
for (f in stream) {
val targetPath = target.resolve(f.relativeTo(this).invariantSeparatorsPathString)
if (f.isDirectory()) {
targetPath.createDirectories()
} else {
f.copyTo(targetPath)
}
}
}
}
fun Path.filesMatchingRecursive(glob: String = "*"): List<Path> {
val matcher = fileSystem.getPathMatcher("glob:$glob")
return Files.walk(this).use { stream ->
stream.filter {
it.isRegularFile() && matcher.matches(it.fileName)
}.collect(Collectors.toList())
}
}
private fun Path.jarUri(): URI {
return URI.create("jar:${toUri()}")
}
fun Path.openZip(): FileSystem {
return FileSystems.newFileSystem(jarUri(), emptyMap<String, Any>())
}
fun Path.writeZip(): FileSystem {
return FileSystems.newFileSystem(jarUri(), mapOf("create" to "true"))
}
fun FileSystem.walk(): Stream<Path> {
return StreamSupport.stream(rootDirectories.spliterator(), false)
.flatMap { Files.walk(it) }
}
fun ProcessBuilder.directory(path: Path?): ProcessBuilder = directory(path?.toFile())
fun Path.hashFile(algorithm: HashingAlgorithm): ByteArray = inputStream().use { input -> input.hash(algorithm) }
fun Path.sha256asHex(): String = hashFile(HashingAlgorithm.SHA256).asHexString()
fun Path.contentEquals(two: InputStream, bufferSizeBytes: Int = 8192): Boolean {
inputStream().use { one ->
val bufOne = ByteArray(bufferSizeBytes)
val bufTwo = ByteArray(bufferSizeBytes)
while (true) {
val readOne = one.read(bufOne)
val readTwo = two.read(bufTwo)
if (readOne != readTwo) {
// length differs
return false
}
if (readOne == -1) {
// end of content
break
}
if (!Arrays.equals(bufOne, 0, readOne, bufTwo, 0, readOne)) {
// content differs
return false
}
}
}
return true
}
fun Path.contentEquals(file: Path, bufferSizeBytes: Int = 8192): Boolean = file.inputStream().use { two ->
contentEquals(two, bufferSizeBytes)
}
fun Path.withDifferentExtension(ext: String): Path = resolveSibling("$nameWithoutExtension.$ext")
// Returns true if our process already owns the lock
fun acquireProcessLockWaiting(
lockFile: Path,
timeoutMs: Long = 1000L * 60 * 60 // one hour
): Boolean {
val logger = Logging.getLogger("paperweight lock file")
val currentPid = ProcessHandle.current().pid()
if (lockFile.exists()) {
val lockingProcessId = lockFile.readText().toLong()
if (lockingProcessId == currentPid) {
return true
}
logger.lifecycle("Lock file '$lockFile' is currently held by pid '$lockingProcessId'.")
if (ProcessHandle.of(lockingProcessId).isEmpty) {
logger.lifecycle("Locking process does not exist, assuming abrupt termination and deleting lock file.")
lockFile.deleteIfExists()
} else {
logger.lifecycle("Waiting for lock to be released...")
var sleptMs: Long = 0
while (lockFile.exists()) {
Thread.sleep(100)
sleptMs += 100
if (sleptMs >= 1000 * 60 && sleptMs % (1000 * 60) == 0L) {
logger.lifecycle(
"Have been waiting on lock file '$lockFile' held by pid '$lockingProcessId' for ${sleptMs / 1000 / 60} minute(s).\n" +
"If this persists for an unreasonable length of time, kill this process, run './gradlew --stop' and then try again.\n" +
"If the problem persists, the lock file may need to be deleted manually."
)
}
if (sleptMs >= timeoutMs) {
throw PaperweightException("Have been waiting on lock file '$lockFile' for $sleptMs ms. Giving up as timeout is $timeoutMs ms.")
}
}
}
}
if (!lockFile.parent.exists()) {
lockFile.parent.createDirectories()
}
lockFile.writeText(currentPid.toString())
return false
}
fun relativeCopy(baseDir: Path, file: Path, outputDir: Path) {
relativeCopyOrMove(baseDir, file, outputDir, false)
}
fun relativeMove(baseDir: Path, file: Path, outputDir: Path) {
relativeCopyOrMove(baseDir, file, outputDir, true)
}
private fun relativeCopyOrMove(baseDir: Path, file: Path, outputDir: Path, move: Boolean) {
val destination = outputDir.resolve(file.relativeTo(baseDir).invariantSeparatorsPathString)
destination.parent.createDirectories()
if (move) {
file.moveTo(destination, overwrite = true)
} else {
file.copyTo(destination, overwrite = true)
}
}

View file

@ -1,50 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import java.nio.file.Path
import kotlin.io.path.*
fun filterJar(
inputJar: Path,
outputJar: Path,
includes: List<String>,
predicate: (Path) -> Boolean = { false }
) {
ensureDeleted(outputJar)
outputJar.writeZip().use { outputFs ->
inputJar.openZip().use { inputFs ->
val matchers = includes.map { inputFs.getPathMatcher("glob:$it") }
inputFs.walk().use { stream ->
stream.filter { p -> predicate(p) || matchers.any { matcher -> matcher.matches(p) } }
.forEach { p ->
val targetFile = outputFs.getPath(p.toAbsolutePath().invariantSeparatorsPathString.substring(1))
targetFile.parent?.createDirectories()
p.copyTo(targetFile)
}
}
}
}
}

View file

@ -1,161 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import java.io.ByteArrayOutputStream
import java.nio.file.Path
import javax.xml.XMLConstants
import javax.xml.stream.XMLOutputFactory
import kotlin.io.path.*
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
import org.gradle.kotlin.dsl.*
fun RepositoryHandler.setupIvyRepository(
url: Any,
configuration: IvyArtifactRepository.() -> Unit
) = ivy(url) {
patternLayout {
artifact(IvyArtifactRepository.MAVEN_ARTIFACT_PATTERN)
ivy(IvyArtifactRepository.MAVEN_IVY_PATTERN)
setM2compatible(true)
}
metadataSources(IvyArtifactRepository.MetadataSources::ivyDescriptor)
isAllowInsecureProtocol = true
resolve.isDynamicMode = false
configuration(this)
}
fun installToIvyRepo(
repo: Path,
artifactCoordinates: String,
dependencies: List<String>,
binaryJar: Path,
sourcesJar: Path?,
): Boolean {
val (module, versionDir) = parseModuleLocation(artifactCoordinates, repo)
val (_, name, version) = module
versionDir.createDirectories()
val sourcesDestination = versionDir.resolve("$name-$version-sources.jar")
val jarDestination = versionDir.resolve("$name-$version.jar")
val ivy = versionDir.resolve("ivy-$version.xml")
val xml = writeIvyModule(module, dependencies.map { parseModule(it) })
val upToDate = upToDate(sourcesDestination, jarDestination, ivy, sourcesJar, binaryJar, xml)
if (upToDate) {
return false
}
if (sourcesJar == null) {
sourcesDestination.deleteIfExists()
} else {
sourcesJar.copyTo(sourcesDestination, overwrite = true)
}
binaryJar.copyTo(jarDestination, overwrite = true)
ivy.writeText(xml, Charsets.UTF_8)
return true
}
private fun upToDate(
sourcesDest: Path,
binDest: Path,
ivyXml: Path,
sourcesIn: Path?,
binaryIn: Path,
xmlIn: String
): Boolean {
val bin = binDest.isRegularFile() && binDest.contentEquals(binaryIn)
val xml = ivyXml.isRegularFile() && ivyXml.contentEquals(xmlIn.byteInputStream())
val sources = if (sourcesIn == null) {
sourcesDest.notExists()
} else {
sourcesDest.isRegularFile() && sourcesDest.contentEquals(sourcesIn)
}
return bin && xml && sources
}
private val OUTPUT_FACTORY = XMLOutputFactory.newInstance()
private const val XSI = XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI
private const val IVY = "http://ant.apache.org/ivy/schemas/ivy.xsd"
private fun writeIvyModule(
module: Module,
dependencies: List<Module>
): String = ByteArrayOutputStream().use { outputStream ->
val writer = OUTPUT_FACTORY.createXMLStreamWriter(outputStream, Charsets.UTF_8.name())
writer.writeStartDocument("UTF-8", "1.0")
writer.writeStartElement("ivy-module")
writer.writeNamespace("xsi", XSI)
writer.writeAttribute(XSI, "noNamespaceSchemaLocation", IVY)
writer.writeAttribute("version", "2.0")
writer.writeEmptyElement("info")
writer.writeAttribute("organisation", module.group)
writer.writeAttribute("module", module.name)
writer.writeAttribute("revision", module.version)
writer.writeAttribute("status", "release")
writer.writeStartElement("dependencies")
for (dep in dependencies) {
writer.writeEmptyElement("dependency")
writer.writeAttribute("org", dep.group)
writer.writeAttribute("name", dep.name)
writer.writeAttribute("rev", dep.version)
}
writer.writeEndElement()
writer.writeEndElement()
writer.writeEndDocument()
String(outputStream.toByteArray(), Charsets.UTF_8)
}
private fun parseModule(coordinatesString: String): Module {
val parts = coordinatesString.split(":")
val group = parts[0]
val name = parts[1]
val version = parts[2]
return Module(group, name, version)
}
private fun parseModuleLocation(coordinatesString: String, root: Path): ModuleLocation {
val (group, name, version) = parseModule(coordinatesString)
val versionDir = root / group.replace(".", "/") / name / version
return ModuleLocation(Module(group, name, version), versionDir)
}
private data class Module(
val group: String,
val name: String,
val version: String,
)
private data class ModuleLocation(
val module: Module,
val versionDir: Path,
)

View file

@ -1,102 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import io.papermc.paperweight.PaperweightException
import java.io.OutputStream
import java.util.concurrent.TimeUnit
import java.util.jar.JarFile
import kotlin.io.path.*
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Provider
import org.gradle.jvm.toolchain.JavaLauncher
fun JavaLauncher.runJar(
classpath: FileCollection,
workingDir: Any,
logFile: Any?,
jvmArgs: List<String> = listOf(),
vararg args: String
) {
var mainClass: String? = null
for (file in classpath.files) {
mainClass = JarFile(file).use { jarFile ->
jarFile.manifest.mainAttributes.getValue("Main-Class")
} ?: continue
break
}
if (mainClass == null) {
throw PaperweightException("Could not determine main class name for ${classpath.asPath}")
}
val dir = workingDir.convertToPath()
val (logFilePath, output) = when {
logFile is OutputStream -> Pair(null, logFile)
logFile != null -> {
val log = logFile.convertToPath()
log.parent.createDirectories()
Pair(log, log.outputStream().buffered())
}
else -> Pair(null, UselessOutputStream)
}
val processBuilder = ProcessBuilder(
this.executablePath.path.absolutePathString(),
*jvmArgs.toTypedArray(),
"-classpath",
classpath.asPath,
mainClass,
*args
).directory(dir)
output.writer().let {
it.appendLine("Command: ${processBuilder.command().joinToString(" ")}")
it.flush()
}
val process = processBuilder.start()
output.use {
val outFuture = redirect(process.inputStream, it)
val errFuture = redirect(process.errorStream, it)
val exit = process.waitFor()
outFuture.get(500L, TimeUnit.MILLISECONDS)
errFuture.get(500L, TimeUnit.MILLISECONDS)
if (exit != 0) {
val logMsg = logFilePath?.let { p -> " Log file: ${p.absolutePathString()}" } ?: ""
throw PaperweightException("Execution of '$mainClass' failed with exit code $exit.$logMsg Classpath: ${classpath.asPath}")
}
}
}
fun Provider<JavaLauncher>.runJar(
classpath: FileCollection,
workingDir: Any,
logFile: Any?,
jvmArgs: List<String> = listOf(),
vararg args: String
) {
get().runJar(classpath, workingDir, logFile, jvmArgs, *args)
}

View file

@ -1,230 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import io.papermc.paperweight.extension.PaperweightServerExtension
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.attributes.Usage
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.*
import org.gradle.plugins.ide.idea.model.IdeaModel
fun Project.setupServerProject(
parent: Project,
remappedJar: Provider<*>,
remappedJarSources: Any,
mcDevSourceDir: Path,
libsFile: Any,
packagesToFix: Provider<List<String>?>,
relocatedReobfMappings: TaskProvider<GenerateRelocatedReobfMappings>,
serverJar: TaskProvider<Zip>
): ServerTasks? {
if (!projectDir.exists()) {
return null
}
plugins.apply("java")
val serverExt = extensions.create<PaperweightServerExtension>(PAPERWEIGHT_EXTENSION, objects)
relocatedReobfMappings {
craftBukkitPackageVersion.set(serverExt.craftBukkitPackageVersion)
}
exportRuntimeClasspathTo(parent)
@Suppress("UNUSED_VARIABLE", "KotlinRedundantDiagnosticSuppress")
val filterPatchedFiles by tasks.registering<FilterPatchedFiles> {
inputSrcDir.set(file("src/main/java"))
inputResourcesDir.set(file("src/main/resources"))
vanillaJar.set(
// unlink dependency on upstream clone task for patcher (hack); it's implicitly handled when we get upstream data
parent.layout.file(parent.files(remappedJar).elements.map { it.single().asFile })
)
outputJar.set(parent.layout.cache.resolve(FINAL_FILTERED_REMAPPED_JAR))
}
val vanillaServer: Configuration by configurations.creating {
withDependencies {
dependencies {
// update mc-dev sources on dependency resolution
makeMcDevSrc(
parent.layout.cache,
remappedJarSources.convertToPath(),
mcDevSourceDir,
layout.projectDirectory.path
)
add(create(parent.files(filterPatchedFiles.flatMap { it.outputJar })))
}
}
}
configurations.named(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME) {
extendsFrom(vanillaServer)
withDependencies {
dependencies {
val libs = libsFile.convertToPathOrNull()
if (libs != null && libs.exists()) {
libs.forEachLine { line ->
add(create(line))
}
}
}
}
}
addMcDevSourcesRoot(mcDevSourceDir)
return createBuildTasks(parent, serverExt, serverJar, vanillaServer, packagesToFix, relocatedReobfMappings)
}
private fun Project.exportRuntimeClasspathTo(parent: Project) {
configurations.create(CONSUMABLE_RUNTIME_CLASSPATH) {
isCanBeConsumed = true
isCanBeResolved = false
attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
extendsFrom(configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME))
}
parent.configurations.create(SERVER_RUNTIME_CLASSPATH) {
isCanBeConsumed = false
isCanBeResolved = true
attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
}
parent.dependencies {
add(SERVER_RUNTIME_CLASSPATH, parent.dependencies.project(path, configuration = CONSUMABLE_RUNTIME_CLASSPATH))
}
afterEvaluate {
val old = parent.repositories.toList()
parent.repositories.clear()
repositories.filterIsInstance<MavenArtifactRepository>().forEach {
parent.repositories.maven(it.url) {
name = "serverRuntimeClasspath repo ${it.url}"
content { onlyForConfigurations(SERVER_RUNTIME_CLASSPATH) }
}
}
parent.repositories.addAll(old)
}
}
private fun Project.createBuildTasks(
parent: Project,
serverExt: PaperweightServerExtension,
serverJar: TaskProvider<Zip>,
vanillaServer: Configuration,
packagesToFix: Provider<List<String>?>,
relocatedReobfMappings: TaskProvider<GenerateRelocatedReobfMappings>
): ServerTasks {
serverJar {
destinationDirectory.set(layout.buildDirectory.dir("libs"))
archiveExtension.set("jar")
archiveClassifier.set("mojang-mapped")
from(zipTree(tasks.named<Jar>("jar").flatMap { it.archiveFile }.map { it.asFile }))
from(vanillaServer.elements.map { it.map { f -> zipTree(f.asFile) } }) {
exclude("META-INF/MANIFEST.MF")
}
isReproducibleFileOrder = true
isPreserveFileTimestamps = false
}
val fixJarForReobf by tasks.registering<FixJarForReobf> {
inputJar.set(serverJar.flatMap { it.archiveFile })
packagesToProcess.set(packagesToFix)
}
val includeMappings by tasks.registering<IncludeMappings> {
inputJar.set(fixJarForReobf.flatMap { it.outputJar })
mappings.set(relocatedReobfMappings.flatMap { it.outputMappings })
mappingsDest.set("META-INF/mappings/reobf.tiny")
}
// We only need to manually relocate references to CB class names in string constants, the actual relocation is handled by the mappings
val relocateConstants by tasks.registering<RelocateClassNameConstants> {
inputJar.set(includeMappings.flatMap { it.outputJar })
}
afterEvaluate {
relocateConstants {
relocate("org.bukkit.craftbukkit", "org.bukkit.craftbukkit.${serverExt.craftBukkitPackageVersion.get()}") {
// This is not actually needed as there are no string constant references to Main
// exclude("org.bukkit.craftbukkit.Main*")
}
}
}
val reobfJar by tasks.registering<RemapJar> {
group = "paperweight"
description = "Re-obfuscate the built jar to obf mappings"
inputJar.set(relocateConstants.flatMap { it.outputJar })
mappingsFile.set(relocatedReobfMappings.flatMap { it.outputMappings })
fromNamespace.set(DEOBF_NAMESPACE)
toNamespace.set(SPIGOT_NAMESPACE)
remapper.from(parent.configurations.named(REMAPPER_CONFIG))
remapperArgs.set(TinyRemapper.minecraftRemapArgs)
outputJar.set(layout.buildDirectory.map { it.dir("libs").file("${project.name}-${project.version}-reobf.jar") })
}
return ServerTasks(includeMappings, reobfJar)
}
data class ServerTasks(
val includeMappings: TaskProvider<IncludeMappings>,
val reobfJar: TaskProvider<RemapJar>,
)
private fun Project.addMcDevSourcesRoot(mcDevSourceDir: Path) {
plugins.apply("idea")
val dir = mcDevSourceDir.toFile()
extensions.getByType<JavaPluginExtension>().sourceSets.named(SourceSet.MAIN_SOURCE_SET_NAME) {
java {
srcDirs(dir)
val pathString = dir.invariantSeparatorsPath
exclude {
it.file.absoluteFile.invariantSeparatorsPath.contains(pathString)
}
}
}
extensions.configure<IdeaModel> {
module {
generatedSourceDirs.add(dir)
}
}
}

View file

@ -1,42 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
data class Relocation(
val owningLibraryCoordinates: String?,
val fromPackage: String,
val toPackage: String,
val excludes: List<String>
) {
fun exclude(exclude: String) {
(excludes as MutableList) += exclude
}
}
data class RelocationWrapper(
val relocation: Relocation,
val fromSlash: String = relocation.fromPackage.replace('.', '/'),
val fromDot: String = relocation.fromPackage.replace('/', '.'),
val toSlash: String = relocation.toPackage.replace('.', '/'),
val toDot: String = relocation.toPackage.replace('/', '.')
)

View file

@ -1,418 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.*
import dev.denwav.hypo.model.ClassProviderRoot
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.constants.*
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.lang.management.ManagementFactory
import java.lang.reflect.Type
import java.net.URI
import java.net.URL
import java.nio.file.FileSystem
import java.nio.file.Path
import java.nio.file.Paths
import java.security.MessageDigest
import java.util.Collections
import java.util.IdentityHashMap
import java.util.Locale
import java.util.Optional
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ThreadLocalRandom
import java.util.concurrent.atomic.AtomicLong
import java.util.jar.Attributes
import java.util.jar.Manifest
import kotlin.io.path.*
import org.cadixdev.lorenz.merge.MergeResult
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.LogLevel
import org.gradle.api.logging.Logger
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.*
val gson: Gson = GsonBuilder().disableHtmlEscaping().setPrettyPrinting().registerTypeHierarchyAdapter(Path::class.java, PathJsonConverter()).create()
class PathJsonConverter : JsonDeserializer<Path?>, JsonSerializer<Path?> {
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): Path? {
return if (json != null) Paths.get(json.asString) else null
}
override fun serialize(src: Path?, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
return JsonPrimitive(src.toString())
}
}
inline fun <reified T> Gson.fromJson(any: Any): T = when (any) {
is String -> fromJson(any)
else -> any.convertToPath().bufferedReader(Charsets.UTF_8).use { fromJson(it) }
}
val ProjectLayout.cache: Path
get() = projectDirectory.dir(".gradle/$CACHE_PATH").path
fun ProjectLayout.cacheDir(path: String) = projectDirectory.dir(".gradle/$CACHE_PATH").dir(path)
fun ProjectLayout.maybeInitSubmodules(offline: Boolean, logger: Logger) {
if (offline) {
Git.checkForGit()
logger.lifecycle("Offline mode enabled, not initializing submodules. This may cause problems if submodules are not already initialized.")
} else {
initSubmodules()
}
}
fun ProjectLayout.initSubmodules() {
Git.checkForGit()
Git(projectDirectory.path)("submodule", "update", "--init").executeOut()
}
fun Project.offlineMode(): Boolean = gradle.startParameter.isOffline
fun <T : FileSystemLocation> Provider<out T>.fileExists(project: Project): Provider<out T?> {
return flatMap { project.provider { it.takeIf { f -> f.path.exists() } } }
}
inline fun <reified T : Task> TaskContainer.providerFor(name: String): TaskProvider<T> {
return if (names.contains(name)) {
named<T>(name)
} else {
register<T>(name)
}
}
inline fun <reified T : Task> TaskContainer.configureTask(name: String, noinline configure: T.() -> Unit): TaskProvider<T> {
return if (names.contains(name)) {
named(name, configure)
} else {
register(name, configure)
}
}
@Suppress("UNCHECKED_CAST")
val Project.download: Provider<DownloadService>
get() = gradle.sharedServices.registrations.getByName(DOWNLOAD_SERVICE_NAME).service as Provider<DownloadService>
fun commentRegex(): Regex {
return Regex("\\s*#.*")
}
val ProviderFactory.isBaseExecution: Provider<Boolean>
get() = gradleProperty(PAPERWEIGHT_DOWNSTREAM_FILE_PROPERTY)
.orElse(provider { "false" })
.map { it == "false" }
val Project.isBaseExecution: Boolean
get() = providers.isBaseExecution.get()
fun ProviderFactory.verboseApplyPatches(): Provider<Boolean> =
gradleProperty(PAPERWEIGHT_VERBOSE_APPLY_PATCHES)
.map { it.toBoolean() }
.orElse(false)
val redirectThreadCount: AtomicLong = AtomicLong(0)
fun redirect(input: InputStream, out: OutputStream): CompletableFuture<Unit> {
val future = CompletableFuture<Unit>()
val thread = Thread {
try {
input.copyTo(out)
future.complete(Unit)
} catch (e: Throwable) {
future.completeExceptionally(PaperweightException("Failed to copy $input to $out", e))
}
}
thread.name = "paperweight stream redirect thread #${redirectThreadCount.getAndIncrement()}"
thread.isDaemon = true
thread.start()
return future
}
object UselessOutputStream : OutputStream() {
override fun write(b: Int) {
}
}
class DelegatingOutputStream(vararg delegates: OutputStream) : OutputStream() {
private val delegates: MutableSet<OutputStream> = Collections.newSetFromMap(IdentityHashMap())
init {
this.delegates.addAll(delegates)
}
override fun write(b: Int) {
for (delegate in delegates) {
delegate.write(b)
}
}
}
fun Any.convertToPath(): Path {
return when (this) {
is Path -> this
is File -> this.toPath()
is FileSystemLocation -> this.path
is Provider<*> -> this.get().convertToPath()
else -> throw PaperweightException("Unknown type representing a file: ${this.javaClass.name}")
}
}
fun Any.convertToFileProvider(layout: ProjectLayout, providers: ProviderFactory): Provider<RegularFile> {
return when (this) {
is Path -> layout.file(providers.provider { toFile() })
is File -> layout.file(providers.provider { this })
is FileSystemLocation -> layout.file(providers.provider { asFile })
is Provider<*> -> flatMap { it.convertToFileProvider(layout, providers) }
else -> throw PaperweightException("Unknown type representing a file: ${this.javaClass.name}")
}
}
fun Any?.convertToPathOrNull(): Path? {
if (this == null) {
return null
}
return this.convertToPath()
}
fun Any.convertToUrl(): URL {
return when (this) {
is URL -> this
is URI -> this.toURL()
is String -> URI.create(this).toURL()
is Provider<*> -> this.get().convertToUrl()
else -> throw PaperweightException("Unknown URL type: ${this.javaClass.name}")
}
}
fun String.capitalized(): String {
return replaceFirstChar(Char::uppercase)
}
fun ensureParentExists(vararg files: Any) {
for (file in files) {
val parent = file.convertToPath().parent
try {
parent.createDirectories()
} catch (e: Exception) {
throw PaperweightException("Failed to create directory $parent", e)
}
}
}
fun ensureDeleted(vararg files: Any) {
for (file in files) {
val f = file.convertToPath()
try {
f.deleteRecursive()
} catch (e: Exception) {
throw PaperweightException("Failed to delete file or directory $f", e)
}
}
}
fun BaseTask.defaultOutput(name: String, ext: String): RegularFileProperty {
return objects.fileProperty().convention {
layout.cache.resolve(paperTaskOutput(name, ext)).toFile()
}
}
fun BaseTask.defaultOutput(ext: String): RegularFileProperty {
return defaultOutput(name, ext)
}
fun BaseTask.defaultOutput(): RegularFileProperty {
return defaultOutput("jar")
}
val <T> Optional<T>.orNull: T?
get() = orElse(null)
inline fun <reified T : Any> Project.contents(contentFile: RegularFileProperty, crossinline convert: (String) -> T): Provider<T> {
return providers.fileContents(contentFile)
.asText
.map { convert(it) }
}
fun findOutputDir(baseFile: Path): Path {
var dir: Path
do {
dir = baseFile.resolveSibling("${baseFile.name}-" + ThreadLocalRandom.current().nextInt())
} while (dir.exists())
return dir
}
private val emptyMergeResult = MergeResult(null)
fun <T> emptyMergeResult(): MergeResult<T?> {
@Suppress("UNCHECKED_CAST")
return emptyMergeResult as MergeResult<T?>
}
inline fun <reified T : Task> TaskContainer.registering(noinline configuration: T.() -> Unit) = registering(T::class, configuration)
inline fun <reified T : Task> TaskContainer.registering() = registering(T::class)
enum class HashingAlgorithm(val algorithmName: String) {
SHA256("SHA-256"),
SHA1("SHA-1");
private val threadLocalMessageDigest = ThreadLocal.withInitial { createDigest() }
fun createDigest(): MessageDigest = MessageDigest.getInstance(algorithmName)
val threadLocalDigest: MessageDigest
get() = threadLocalMessageDigest.get()
}
class Hash(
@get:Input
val value: String,
@get:Input
val algorithm: HashingAlgorithm
) {
@get:Internal
val valueLower: String by lazy { value.lowercase(Locale.ENGLISH) }
}
fun String.hash(algorithm: HashingAlgorithm): ByteArray = algorithm.threadLocalDigest.let {
it.update(toByteArray())
it.digest()
}
fun InputStream.hash(algorithm: HashingAlgorithm, bufferSize: Int = 8192): ByteArray {
val digest = algorithm.threadLocalDigest
val buffer = ByteArray(bufferSize)
while (true) {
val count = read(buffer)
if (count == -1) {
break
}
digest.update(buffer, 0, count)
}
return digest.digest()
}
private val hexChars = "0123456789abcdef".toCharArray()
fun ByteArray.asHexString(): String {
val chars = CharArray(2 * size)
forEachIndexed { i, byte ->
val unsigned = byte.toInt() and 0xFF
chars[2 * i] = hexChars[unsigned / 16]
chars[2 * i + 1] = hexChars[unsigned % 16]
}
return String(chars)
}
fun JavaToolchainService.defaultJavaLauncher(project: Project): Provider<JavaLauncher> {
return launcherFor(project.extensions.getByType<JavaPluginExtension>().toolchain).orElse(
launcherFor {
// If the java plugin isn't applied, or no toolchain value was set
languageVersion.set(JavaLanguageVersion.of(21))
}
)
}
fun <P : Property<*>> P.withDisallowChanges(): P = apply { disallowChanges() }
fun <P : Property<*>> P.withDisallowUnsafeRead(): P = apply { disallowUnsafeRead() }
fun FileCollection.toJarClassProviderRoots(): List<ClassProviderRoot> = files.asSequence()
.map { f -> f.toPath() }
.filter { p -> p.isLibraryJar }
.map { p -> ClassProviderRoot.fromJar(p) }
.toList()
private fun javaVersion(): Int {
val version = System.getProperty("java.specification.version")
val parts = version.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val errorMsg = "Could not determine version of the current JVM"
check(parts.isNotEmpty()) { errorMsg }
return if (parts[0] == "1") {
check(parts.size >= 2) { errorMsg }
parts[1].toInt()
} else {
parts[0].toInt()
}
}
fun checkJavaVersion() {
val minimumJava = 11
val ver = javaVersion()
if (ver < minimumJava) {
var msg = "paperweight requires Gradle to be run with a Java $minimumJava runtime or newer."
val runtimeMX = ManagementFactory.getRuntimeMXBean()
if (runtimeMX != null) {
msg += " Current runtime: Java ${runtimeMX.specVersion} (${runtimeMX.vmName} ${runtimeMX.vmVersion})"
}
throw PaperweightException(msg)
}
}
inline fun <reified P> printId(pluginId: String, gradle: Gradle) {
if (gradle.startParameter.logLevel == LogLevel.QUIET) {
return
}
println("$pluginId v${P::class.java.`package`.implementationVersion} (running on '${System.getProperty("os.name")}')")
}
fun FileSystem.modifyManifest(create: Boolean = true, op: Manifest.() -> Unit) {
modifyManifest(getPath("META-INF/MANIFEST.MF"), create, op)
}
fun modifyManifest(path: Path, create: Boolean = true, op: Manifest.() -> Unit) {
val exists = path.exists()
if (exists || create) {
val mf = if (exists) {
path.inputStream().buffered().use { Manifest(it) }
} else {
path.parent.createDirectories()
val manifest = Manifest()
manifest.mainAttributes[Attributes.Name.MANIFEST_VERSION] = "1.0"
manifest
}
op(mf)
path.outputStream().buffered().use { mf.write(it) }
}
}

View file

@ -1,65 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.util
import java.nio.file.Path
import kotlin.io.path.*
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import org.junit.jupiter.api.io.TempDir
class FileUtilsTest {
@Test
fun testContentEquals(@TempDir tempDir: Path) {
val someBytes = classBytes<FileUtilsTest>()
// write same data to two files
val one = tempDir.resolve("${FileUtilsTest::class.simpleName}.class")
one.writeBytes(someBytes)
val two = tempDir.resolve("${FileUtilsTest::class.simpleName}.class_1")
two.writeBytes(someBytes)
// assert the content equals what we just wrote
assertTrue(one.contentEquals(someBytes.inputStream()), "File content doesn't equal what was written to the file")
// assert the files have matching content
assertTrue(one.contentEquals(two), "These files have the same content")
assertTrue(two.contentEquals(one), "These files have the same content")
val someDifferentBytes = classBytes<Map<*, *>>()
// write some different data to a third file
val three = tempDir.resolve("Map.class")
three.writeBytes(someDifferentBytes)
// assert it's content is different from the previously written files
assertFalse(one.contentEquals(three), "These files are different")
}
private inline fun <reified C> classBytes(): ByteArray {
val resourceName = C::class.java.name.replace(".", "/") + ".class"
return this::class.java.classLoader.getResource(resourceName)
?.openStream()?.readAllBytes() ?: error("Couldn't get resource $resourceName")
}
}

View file

@ -1,16 +0,0 @@
plugins {
`config-kotlin`
`config-publish`
}
dependencies {
shade(projects.nuggetLib)
implementation(libs.bundles.kotson)
}
gradlePlugin {
plugins.all {
description = "Gradle plugin for developing Paper derivatives"
implementationClass = "io.papermc.paperweight.patcher.PaperweightPatcher"
}
}

View file

@ -1,314 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.patcher.tasks.*
import io.papermc.paperweight.patcher.upstream.PatchTaskConfig
import io.papermc.paperweight.patcher.upstream.PatcherUpstream
import io.papermc.paperweight.patcher.upstream.RepoPatcherUpstream
import io.papermc.paperweight.taskcontainers.BundlerJarTasks
import io.papermc.paperweight.taskcontainers.DevBundleTasks
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import io.papermc.paperweight.util.data.*
import java.io.File
import java.util.concurrent.atomic.AtomicReference
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.gradle.kotlin.dsl.*
import org.gradle.kotlin.dsl.registering
class PaperweightPatcher : Plugin<Project> {
override fun apply(target: Project) {
checkJavaVersion()
Git.checkForGit()
printId<PaperweightPatcher>("paperweight-patcher", target.gradle)
val patcher = target.extensions.create(PAPERWEIGHT_EXTENSION, PaperweightPatcherExtension::class, target)
target.gradle.sharedServices.registerIfAbsent(DOWNLOAD_SERVICE_NAME, DownloadService::class) {}
target.tasks.register<Delete>("cleanCache") {
group = "paperweight"
description = "Delete the project setup cache and task outputs."
delete(target.layout.cache)
}
target.configurations.create(DECOMPILER_CONFIG)
target.configurations.create(REMAPPER_CONFIG)
target.configurations.create(PAPERCLIP_CONFIG)
val workDirProp = target.providers.gradleProperty(UPSTREAM_WORK_DIR_PROPERTY)
val dataFileProp = target.providers.gradleProperty(PAPERWEIGHT_DOWNSTREAM_FILE_PROPERTY)
val applyPatches by target.tasks.registering { group = "paperweight" }
val rebuildPatches by target.tasks.registering { group = "paperweight" }
val generateReobfMappings by target.tasks.registering(GenerateReobfMappings::class)
val mergeReobfMappingsPatches by target.tasks.registering<PatchMappings> {
patch.set(patcher.reobfMappingsPatch.fileExists(target))
outputMappings.convention(defaultOutput("tiny"))
fromNamespace.set(DEOBF_NAMESPACE)
toNamespace.set(SPIGOT_NAMESPACE)
}
val patchReobfMappings by target.tasks.registering<PatchMappings> {
inputMappings.set(generateReobfMappings.flatMap { it.reobfMappings })
patch.set(mergeReobfMappingsPatches.flatMap { it.outputMappings })
outputMappings.set(target.layout.cache.resolve(PATCHED_REOBF_MOJANG_SPIGOT_MAPPINGS))
fromNamespace.set(DEOBF_NAMESPACE)
toNamespace.set(SPIGOT_NAMESPACE)
}
val generateRelocatedReobfMappings by target.tasks.registering<GenerateRelocatedReobfMappings> {
inputMappings.set(patchReobfMappings.flatMap { it.outputMappings })
outputMappings.set(target.layout.cache.resolve(RELOCATED_PATCHED_REOBF_MOJANG_SPIGOT_MAPPINGS))
}
val prepareForDownstream = target.tasks.register<PaperweightPatcherPrepareForDownstream>(PAPERWEIGHT_PREPARE_DOWNSTREAM) {
dataFile.fileProvider(dataFileProp.map { File(it) })
reobfMappingsPatch.set(mergeReobfMappingsPatches.flatMap { it.outputMappings })
}
val upstreamDataTaskRef = AtomicReference<TaskProvider<PaperweightPatcherUpstreamData>>(null)
patcher.upstreams.all {
val taskPair = target.createUpstreamTask(this, patcher, workDirProp, upstreamDataTaskRef)
patchTasks.all {
val createdPatchTask = target.createPatchTask(this, patcher, taskPair, applyPatches)
prepareForDownstream {
dependsOn(createdPatchTask)
}
target.rebuildPatchTask(this, rebuildPatches)
}
}
val devBundleTasks = DevBundleTasks(target)
val bundlerJarTasks = BundlerJarTasks(
target,
patcher.bundlerJarName,
patcher.mainClass
)
target.afterEvaluate {
target.repositories {
maven(patcher.remapRepo) {
name = REMAPPER_REPO_NAME
content { onlyForConfigurations(REMAPPER_CONFIG) }
}
maven(patcher.decompileRepo) {
name = DECOMPILER_REPO_NAME
content { onlyForConfigurations(DECOMPILER_CONFIG) }
}
}
val upstreamDataTask = upstreamDataTaskRef.get() ?: return@afterEvaluate
val upstreamData = upstreamDataTask.map { readUpstreamData(it.dataFile) }
mergeReobfMappingsPatches {
inputMappings.pathProvider(upstreamData.map { it.reobfMappingsPatch })
}
val mergedReobfPackagesToFix = upstreamData.zip(patcher.reobfPackagesToFix) { data, pkgs ->
data.reobfPackagesToFix + pkgs
}
prepareForDownstream {
upstreamDataFile.set(upstreamDataTask.flatMap { it.dataFile })
reobfPackagesToFix.set(mergedReobfPackagesToFix)
}
for (upstream in patcher.upstreams) {
for (patchTask in upstream.patchTasks) {
patchTask.patchTask {
sourceMcDevJar.convention(target, upstreamData.map { it.decompiledJar })
mcLibrariesDir.convention(target, upstreamData.map { it.libSourceDir })
spigotLibrariesSourceDir.convention(target, upstreamData.map { it.spigotLibSourcesDir })
}
}
}
val serverProj = patcher.serverProject.orNull ?: return@afterEvaluate
val serverJar = serverProj.tasks.register("serverJar", Zip::class)
generateReobfMappings {
inputMappings.pathProvider(upstreamData.map { it.mappings })
notchToSpigotMappings.pathProvider(upstreamData.map { it.notchToSpigotMappings })
sourceMappings.pathProvider(upstreamData.map { it.sourceMappings })
inputJar.set(serverJar.flatMap { it.archiveFile })
spigotRecompiledClasses.pathProvider(upstreamData.map { it.spigotRecompiledClasses })
reobfMappings.set(target.layout.cache.resolve(REOBF_MOJANG_SPIGOT_MAPPINGS))
}
generateRelocatedReobfMappings {
inputJar.set(serverJar.flatMap { it.archiveFile })
}
val (includeMappings, reobfJar) = serverProj.setupServerProject(
target,
upstreamData.map { it.remappedJar },
upstreamData.map { it.decompiledJar },
patcher.mcDevSourceDir.path,
upstreamData.map { it.libFile },
mergedReobfPackagesToFix,
generateRelocatedReobfMappings,
serverJar
) ?: return@afterEvaluate
devBundleTasks.configure(
patcher.serverProject.get(),
patcher.bundlerJarName.get(),
patcher.mainClass,
upstreamData.map { it.mcVersion },
upstreamData.map { it.decompiledJar },
upstreamData.map { it.serverLibrariesTxt },
upstreamData.map { it.serverLibrariesList },
upstreamData.map { it.vanillaJar },
upstreamData.map { it.accessTransform },
upstreamData.map { it.bundlerVersionJson }.convertToFileProvider(layout, providers)
) {
vanillaJarIncludes.set(upstreamData.map { it.vanillaIncludes })
reobfMappingsFile.set(generateRelocatedReobfMappings.flatMap { it.outputMappings })
paramMappingsCoordinates.set(upstreamData.map { it.paramMappings.coordinates.single() })
paramMappingsUrl.set(upstreamData.map { it.paramMappings.url })
}
devBundleTasks.configureAfterEvaluate()
bundlerJarTasks.configureBundlerTasks(
upstreamData.map { it.bundlerVersionJson }.convertToFileProvider(target.layout, target.providers),
upstreamData.map { it.serverLibrariesList }.convertToFileProvider(target.layout, target.providers),
upstreamData.map { it.vanillaJar }.convertToFileProvider(target.layout, target.providers),
includeMappings.flatMap { it.outputJar },
reobfJar,
upstreamData.map { it.mcVersion }
)
}
}
private fun Project.createUpstreamTask(
upstream: PatcherUpstream,
ext: PaperweightPatcherExtension,
workDirProp: Provider<String>,
upstreamDataTaskRef: AtomicReference<TaskProvider<PaperweightPatcherUpstreamData>>
): Pair<TaskProvider<CheckoutRepo>?, TaskProvider<PaperweightPatcherUpstreamData>> {
val workDirFromProp = layout.dir(workDirProp.map { File(it) }).orElse(ext.upstreamsDir)
val upstreamData = tasks.configureTask<PaperweightPatcherUpstreamData>(upstream.upstreamDataTaskName) {
workDir.convention(workDirFromProp)
dataFile.convention(workDirFromProp.map { it.file("upstreamData${upstream.name.capitalized()}.json") })
}
val cloneTask = (upstream as? RepoPatcherUpstream)?.let { repo ->
val cloneTask = tasks.configureTask<CheckoutRepo>(repo.cloneTaskName) {
repoName.convention(repo.name)
url.convention(repo.url)
ref.convention(repo.ref)
workDir.convention(workDirFromProp)
}
upstreamData {
dependsOn(cloneTask)
projectDir.convention(cloneTask.flatMap { it.outputDir })
}
return@let cloneTask
}
if (upstream.useForUpstreamData.getOrElse(false)) {
upstreamDataTaskRef.set(upstreamData)
} else {
upstreamDataTaskRef.compareAndSet(null, upstreamData)
}
return cloneTask to upstreamData
}
private fun Project.createPatchTask(
config: PatchTaskConfig,
ext: PaperweightPatcherExtension,
upstreamTaskPair: Pair<TaskProvider<CheckoutRepo>?, TaskProvider<PaperweightPatcherUpstreamData>>,
applyPatches: TaskProvider<Task>
): TaskProvider<PatcherApplyGitPatches> {
val project = this
val patchTask = tasks.configureTask<PatcherApplyGitPatches>(config.patchTaskName) {
group = "paperweight"
if (isBaseExecution) {
doNotTrackState("$name should always run when requested as part of the base execution.")
}
printOutput.set(isBaseExecution)
val (cloneTask, upstreamDataTask) = upstreamTaskPair
dependsOn(upstreamDataTask)
if (cloneTask != null) {
upstreamDir.convention(cloneTask.flatMap { it.outputDir.dir(config.upstreamDirPath) })
} else {
upstreamDir.convention(config.upstreamDir)
}
patchDir.convention(config.patchDir.fileExists(project))
outputDir.convention(config.outputDir)
mcDevSources.set(ext.mcDevSourceDir)
bareDirectory.convention(config.isBareDirectory)
importMcDev.convention(config.importMcDev)
devImports.convention(ext.devImports.fileExists(project))
}
applyPatches {
dependsOn(patchTask)
}
return patchTask
}
private fun Project.rebuildPatchTask(config: PatchTaskConfig, rebuildPatches: TaskProvider<Task>): TaskProvider<RebuildGitPatches> {
val rebuildTask = tasks.configureTask<RebuildGitPatches>(config.rebuildTaskName) {
group = "paperweight"
patchDir.convention(config.patchDir)
inputDir.convention(config.outputDir)
baseRef.convention("base")
}
rebuildPatches {
dependsOn(rebuildTask)
}
return rebuildTask
}
}

View file

@ -1,104 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher
import io.papermc.paperweight.patcher.upstream.DefaultPaperRepoPatcherUpstream
import io.papermc.paperweight.patcher.upstream.DefaultPatcherUpstream
import io.papermc.paperweight.patcher.upstream.DefaultRepoPatcherUpstream
import io.papermc.paperweight.patcher.upstream.PaperRepoPatcherUpstream
import io.papermc.paperweight.patcher.upstream.PatcherUpstream
import io.papermc.paperweight.patcher.upstream.RepoPatcherUpstream
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.util.Locale
import org.gradle.api.Action
import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.*
@Suppress("unused", "MemberVisibilityCanBePrivate")
open class PaperweightPatcherExtension(project: Project, private val objects: ObjectFactory, layout: ProjectLayout, tasks: TaskContainer) {
val serverProject: Property<Project> = objects.property()
val mcDevSourceDir: DirectoryProperty = objects.directoryProperty().convention(serverProject.map { it.layout.cacheDir(MC_DEV_SOURCES_DIR) })
val buildDataDir: DirectoryProperty = objects.dirWithDefault(layout, "build-data")
val devImports: RegularFileProperty = objects.fileFrom(buildDataDir, "dev-imports.txt")
val reobfMappingsPatch: RegularFileProperty = objects.fileFrom(buildDataDir, "reobf-mappings-patch.tiny")
val reobfPackagesToFix: ListProperty<String> = objects.listProperty()
val mainClass: Property<String> = objects.property<String>().convention("org.bukkit.craftbukkit.Main")
val bundlerJarName: Property<String> = objects.property<String>().convention(project.name.lowercase(Locale.ENGLISH))
val decompileRepo: Property<String> = objects.property()
val remapRepo: Property<String> = objects.property()
val upstreams: ExtensiblePolymorphicDomainObjectContainer<PatcherUpstream> = objects.polymorphicDomainObjectContainer(PatcherUpstream::class)
/**
* The directory upstreams should be checked out in. Paperweight will use the directory specified in the
* following order, whichever is set first:
*
* 1. The value of the Gradle property `paperweightUpstreamWorkDir`.
* 2. The value of this [upstreamsDir] property.
* 3. The default location of <project_root>/.gradle/caches/paperweight/upstreams
*
* This means a project which is several upstreams deep will all use the upstreams directory defined by the root project.
*/
val upstreamsDir: Property<Directory> = objects.directoryProperty().convention(layout.cacheDir(UPSTREAMS))
init {
upstreams.registerFactory(PatcherUpstream::class.java) { name -> DefaultPatcherUpstream(name, objects, tasks) }
upstreams.registerFactory(RepoPatcherUpstream::class.java) { name -> DefaultRepoPatcherUpstream(name, objects, tasks, layout) }
upstreams.registerFactory(PaperRepoPatcherUpstream::class.java) { name -> DefaultPaperRepoPatcherUpstream(name, objects, tasks, layout) }
}
fun usePaperUpstream(refProvider: Provider<String>, action: Action<PaperRepoPatcherUpstream>) {
upstreams {
register<PaperRepoPatcherUpstream>("paper") {
url.set(github("PaperMC", "Paper"))
ref.set(refProvider)
action.execute(this)
}
}
}
fun useStandardUpstream(name: String, action: Action<RepoPatcherUpstream>) {
upstreams {
register<RepoPatcherUpstream>(name) {
action.execute(this)
}
}
}
}

View file

@ -1,119 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.tasks
import io.papermc.paperweight.util.*
import kotlin.io.path.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.UntrackedTask
@UntrackedTask(because = "Git tracks the state")
abstract class CheckoutRepo : DefaultTask() {
@get:Input
abstract val repoName: Property<String>
@get:Input
abstract val url: Property<String>
@get:Input
abstract val ref: Property<String>
@get:Input
abstract val shallowClone: Property<Boolean>
@get:Input
abstract val initializeSubmodules: Property<Boolean>
@get:Input
abstract val initializeSubmodulesShallow: Property<Boolean>
@get:Internal
abstract val workDir: DirectoryProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
init {
@Suppress("LeakingThis")
run {
repoName.finalizeValueOnRead()
url.finalizeValueOnRead()
ref.finalizeValueOnRead()
shallowClone.convention(true).finalizeValueOnRead()
initializeSubmodules.convention(true).finalizeValueOnRead()
initializeSubmodulesShallow.convention(false).finalizeValueOnRead()
outputDir.convention(workDir.dir(repoName)).finalizeValueOnRead()
}
}
@TaskAction
fun run() {
Git.checkForGit()
val dir = outputDir.path
val urlText = url.get().trim()
if (dir.resolve(".git").notExists()) {
dir.deleteRecursive()
dir.createDirectories()
Git(dir)("init", "--quiet").executeSilently()
}
val git = Git(dir)
git("remote", "remove", "origin").runSilently(silenceErr = true) // can fail
git("remote", "add", "origin", urlText).executeSilently(silenceErr = true)
git.fetch()
git("checkout", "-f", "FETCH_HEAD").executeSilently(silenceErr = true)
git("clean", "-fqd").executeSilently(silenceErr = true)
if (initializeSubmodules.get()) {
git.updateSubmodules()
}
}
private fun Git.fetch() {
if (shallowClone.get()) {
this("fetch", "--depth", "1", "origin", ref.get()).executeSilently(silenceErr = true)
} else {
this("fetch", "origin", ref.get()).executeSilently(silenceErr = true)
}
}
private fun Git.updateSubmodules() {
if (initializeSubmodulesShallow.get()) {
this("submodule", "update", "--init", "--recursive", "--depth", "1").executeSilently(silenceErr = true)
} else {
this("submodule", "update", "--init", "--recursive").executeSilently(silenceErr = true)
}
}
}

View file

@ -1,66 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.tasks
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.data.readUpstreamData
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.UntrackedTask
@UntrackedTask(because = "PaperweightPatcherPrepareForDownstream should always run when requested")
abstract class PaperweightPatcherPrepareForDownstream : BaseTask() {
@get:InputFile
abstract val upstreamDataFile: RegularFileProperty
@get:Input
abstract val reobfPackagesToFix: ListProperty<String>
@get:InputFile
abstract val reobfMappingsPatch: RegularFileProperty
@get:OutputFile
abstract val dataFile: RegularFileProperty
@TaskAction
fun run() {
val upstreamData = readUpstreamData(upstreamDataFile)
val ourData = upstreamData.copy(
reobfPackagesToFix = reobfPackagesToFix.get(),
reobfMappingsPatch = reobfMappingsPatch.path,
)
dataFile.path.parent.createDirectories()
dataFile.path.bufferedWriter().use { writer ->
gson.toJson(ourData, writer)
}
}
}

View file

@ -1,69 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.tasks
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import kotlin.collections.set
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.internal.StartParameterInternal
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.UntrackedTask
import org.gradle.internal.build.NestedRootBuildRunner
@UntrackedTask(because = "Nested build does it's own up-to-date checking")
abstract class PaperweightPatcherUpstreamData : BaseTask() {
@get:InputDirectory
abstract val projectDir: DirectoryProperty
@get:Internal
abstract val workDir: DirectoryProperty
@get:OutputFile
abstract val dataFile: RegularFileProperty
@TaskAction
fun run() {
val params = NestedRootBuildRunner.createStartParameterForNewBuild(services)
params.projectDir = projectDir.get().asFile
val upstreamDataFile = dataFile.path
upstreamDataFile.deleteForcefully() // We won't be the ones to create this file
params.setTaskNames(listOf(PAPERWEIGHT_PREPARE_DOWNSTREAM))
params.projectProperties[UPSTREAM_WORK_DIR_PROPERTY] = workDir.path.absolutePathString()
params.projectProperties[PAPERWEIGHT_DOWNSTREAM_FILE_PROPERTY] = upstreamDataFile.absolutePathString()
params.systemPropertiesArgs[PAPERWEIGHT_DEBUG] = System.getProperty(PAPERWEIGHT_DEBUG, "false")
NestedRootBuildRunner.runNestedRootBuild(null, params as StartParameterInternal, services)
}
}

View file

@ -1,168 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.tasks
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
abstract class PatcherApplyGitPatches : ControllableOutputTask() {
@get:InputDirectory
abstract val upstreamDir: DirectoryProperty
@get:Input
abstract val upstreamBranch: Property<String>
@get:Optional
@get:InputDirectory
abstract val patchDir: DirectoryProperty
@get:Input
abstract val bareDirectory: Property<Boolean>
@get:Input
abstract val importMcDev: Property<Boolean>
@get:Optional
@get:InputFile
abstract val sourceMcDevJar: RegularFileProperty
@get:Optional
@get:InputFile
abstract val devImports: RegularFileProperty
@get:Optional
@get:InputDirectory
abstract val mcLibrariesDir: DirectoryProperty
@get:Optional
@get:InputDirectory
abstract val spigotLibrariesSourceDir: DirectoryProperty
@get:Input
abstract val ignoreGitIgnore: Property<Boolean>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Inject
abstract val providers: ProviderFactory
@get:OutputDirectory
abstract val mcDevSources: DirectoryProperty
@get:Input
abstract val verbose: Property<Boolean>
override fun init() {
upstreamBranch.convention("master")
importMcDev.convention(false)
printOutput.convention(true).finalizeValueOnRead()
ignoreGitIgnore.convention(Git.ignoreProperty(providers)).finalizeValueOnRead()
verbose.convention(providers.verboseApplyPatches())
}
@TaskAction
fun runLocking() {
val lockFile = layout.cache.resolve(applyPatchesLock(outputDir.path))
acquireProcessLockWaiting(lockFile)
try {
run()
} finally {
lockFile.deleteForcefully()
}
}
private fun run() {
Git.checkForGit()
val output = outputDir.path
recreateCloneDirectory(output)
val target = output.name
if (printOutput.get()) {
logger.lifecycle("Creating $target from patch source...")
}
if (bareDirectory.get()) {
val up = upstreamDir.path
up.resolve(".git").deleteRecursive()
Git(up).let { upstreamGit ->
upstreamGit("init", "--quiet").executeSilently(silenceErr = true)
upstreamGit("checkout", "-b", upstreamBranch.get()).executeSilently(silenceErr = true)
upstreamGit.disableAutoGpgSigningInRepo()
upstreamGit("add", ".").executeSilently(silenceErr = true)
upstreamGit("commit", "-m", "Initial Source", "--author=Initial <auto@mated.null>").executeSilently(silenceErr = true)
}
}
val git = Git(output)
checkoutRepoFromUpstream(git, upstreamDir.path, upstreamBranch.get())
git.disableAutoGpgSigningInRepo()
val srcDir = output.resolve("src/main/java")
val dataDir = output.resolve("src/main/resources")
val patches = patchDir.pathOrNull?.listDirectoryEntries("*.patch") ?: listOf()
val librarySources = ArrayList<Path>()
spigotLibrariesSourceDir.pathOrNull?.let { librarySources.add(it) }
mcLibrariesDir.pathOrNull?.let { librarySources.add(it) }
if (sourceMcDevJar.isPresent && importMcDev.get()) {
McDev.importMcDev(
patches = patches,
decompJar = sourceMcDevJar.path,
importsFile = devImports.pathOrNull,
targetDir = srcDir,
dataTargetDir = dataDir,
librariesDirs = librarySources,
printOutput = printOutput.get()
)
}
git(*Git.add(ignoreGitIgnore, ".")).executeSilently()
git("commit", "--allow-empty", "-m", "Initial", "--author=Initial Source <auto@mated.null>").executeSilently()
git("tag", "-d", "base").runSilently(silenceErr = true)
git("tag", "base").executeSilently()
applyGitPatches(git, target, output, patchDir.pathOrNull, printOutput.get(), verbose.get())
makeMcDevSrc(layout.cache, sourceMcDevJar.path, mcDevSources.path, outputDir.path, srcDir, dataDir)
}
}

View file

@ -1,64 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.upstream
import io.papermc.paperweight.patcher.tasks.PatcherApplyGitPatches
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import javax.inject.Inject
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.*
open class DefaultPatchTaskConfig @Inject constructor(
private val name: String,
private val tasks: TaskContainer,
objects: ObjectFactory,
) : PatchTaskConfig {
override val upstreamDirPath: Property<String> = objects.property()
override val upstreamDir: DirectoryProperty = objects.directoryProperty()
override val patchDir: DirectoryProperty = objects.directoryProperty()
override val outputDir: DirectoryProperty = objects.directoryProperty()
override val isBareDirectory: Property<Boolean> = objects.property<Boolean>().convention(false)
override val importMcDev: Property<Boolean> = objects.property<Boolean>().convention(false)
override val patchTaskName: String
get() = "apply${name.capitalized()}Patches"
override val rebuildTaskName: String
get() = "rebuild${name.capitalized()}Patches"
override val patchTask: TaskProvider<PatcherApplyGitPatches>
get() = tasks.providerFor(patchTaskName)
override val rebuildTask: TaskProvider<RebuildGitPatches>
get() = tasks.providerFor(rebuildTaskName)
override fun getName(): String {
return name
}
}

View file

@ -1,52 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.upstream
import io.papermc.paperweight.patcher.tasks.PaperweightPatcherUpstreamData
import io.papermc.paperweight.util.*
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.*
open class DefaultPatcherUpstream(
private val name: String,
protected val objects: ObjectFactory,
protected val tasks: TaskContainer
) : PatcherUpstream {
override val patchTasks: NamedDomainObjectContainer<PatchTaskConfig> = objects.domainObjectContainer(PatchTaskConfig::class) { name ->
objects.newInstance<DefaultPatchTaskConfig>(name)
}
override val upstreamDataTaskName: String
get() = "get${name.capitalized()}UpstreamData"
override val upstreamDataTask: TaskProvider<PaperweightPatcherUpstreamData>
get() = tasks.providerFor(upstreamDataTaskName)
override val useForUpstreamData: Property<Boolean> = objects.property()
override fun getName(): String = name
}

View file

@ -1,34 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.upstream
import org.gradle.api.file.DirectoryProperty
interface MinimalPatcherConfig {
val apiPatchDir: DirectoryProperty
val apiOutputDir: DirectoryProperty
val serverPatchDir: DirectoryProperty
val serverOutputDir: DirectoryProperty
}

View file

@ -1,30 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.upstream
import org.gradle.api.Action
interface PaperRepoPatcherUpstream : RepoPatcherUpstream {
fun withPaperPatcher(action: Action<MinimalPatcherConfig>)
}

View file

@ -1,47 +0,0 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
package io.papermc.paperweight.patcher.upstream
import io.papermc.paperweight.patcher.tasks.PatcherApplyGitPatches
import io.papermc.paperweight.tasks.*
import org.gradle.api.Named
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.TaskProvider
interface PatchTaskConfig : Named {
val upstreamDirPath: Property<String>
val upstreamDir: DirectoryProperty
val patchDir: DirectoryProperty
val outputDir: DirectoryProperty
val isBareDirectory: Property<Boolean>
val importMcDev: Property<Boolean>
val patchTaskName: String
val rebuildTaskName: String
val patchTask: TaskProvider<PatcherApplyGitPatches>
val rebuildTask: TaskProvider<RebuildGitPatches>
}

Some files were not shown because too many files have changed in this diff Show more