Compare commits

..

1 commit

Author SHA1 Message Date
MiniDigger
210a8346a2 initial try to add a importmcdev task 2021-06-14 19:39:23 +02:00
189 changed files with 4418 additions and 13406 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

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

@ -0,0 +1,22 @@
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
~/.gradle/jdks
~/.gradle/native
~/.gradle/wrapper
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,28 @@
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
~/.gradle/jdks
~/.gradle/native
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
restore-keys: |
${{ runner.os }}-gradle-
- uses: gradle/wrapper-validation-action@v1
- run: ./gradlew publish --no-daemon --stacktrace
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_wavJfrogUsername: ${{ secrets.DEPLOY_USER }}
ORG_GRADLE_PROJECT_wavJfrogPassword: ${{ secrets.DEPLOY_PASS }}

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

View file

@ -1,9 +0,0 @@
tasks.register("printVersion") {
doFirst {
println(version)
}
}
tasks.wrapper {
distributionType = Wrapper.DistributionType.ALL
}

View file

@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion
plugins {
`kotlin-dsl`
`kotlin-dsl-precompiled-script-plugins`
@ -10,11 +12,10 @@ repositories {
dependencies {
implementation(libs.gradle.licenser)
implementation(libs.gradle.spotless)
implementation(libs.gradle.ktlint)
implementation(libs.gradle.shadow)
implementation(libs.gradle.kotlin.dsl)
implementation(libs.gradle.plugin.kotlin.withVersion(embeddedKotlinVersion))
implementation(libs.gradle.plugin.publish)
implementation(libs.gradle.kotlin.plugin.withVersion(embeddedKotlinVersion))
}
fun Provider<MinimalExternalModuleDependency>.withVersion(version: String): Provider<String> {

View file

@ -1,4 +1,4 @@
rootProject.name = "buildSrc"
enableFeaturePreview("VERSION_CATALOGS")
dependencyResolutionManagement {
versionCatalogs {

View file

@ -1,61 +1,29 @@
import com.diffplug.gradle.spotless.SpotlessExtension
import net.kyori.indra.licenser.spotless.IndraSpotlessLicenserExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
idea
id("org.gradle.kotlin.kotlin-dsl")
id("org.cadixdev.licenser")
id("org.jlleitschuh.gradle.ktlint")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(11))
}
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/") {
mavenCentral()
maven("https://oss.sonatype.org/content/repositories/snapshots/") {
mavenContent {
includeModule("org.cadixdev", "mercury")
}
}
maven("https://repo.papermc.io/repository/maven-public/") {
maven("https://maven.quiltmc.org/repository/release/") {
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")
}
includeGroup("org.quiltmc")
}
}
}
@ -68,46 +36,38 @@ configurations.all {
dependencies.removeIf { it.group == "org.jetbrains.kotlin" }
}
tasks.jar {
manifest {
attributes(
"Implementation-Version" to project.version
)
dependencies {
compileOnly(gradleApi())
compileOnly(kotlin("stdlib-jdk8"))
}
gradlePlugin {
isAutomatedPublishing = false
}
tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
useIR = true
freeCompilerArgs = listOf("-Xopt-in=kotlin.io.path.ExperimentalPathApi")
}
}
// The following is to work around https://github.com/diffplug/spotless/issues/1599
// Ensure the ktlint step is before the license header step
ktlint {
enableExperimentalRules.set(true)
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)
disabledRules.add("no-wildcard-imports")
}
tasks.register("format") {
group = "formatting"
description = "Formats source code according to project style"
dependsOn(tasks.named("spotlessApply"))
dependsOn(tasks.licenseFormat, tasks.ktlintFormat)
}
license {
header.set(resources.text.fromFile(rootProject.file("license/copyright.txt")))
include("**/*.kt")
}
idea {

View file

@ -1,19 +1,22 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.api.artifacts.repositories.PasswordCredentials
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.tasks.PublishToMavenLocal
import org.gradle.api.publish.maven.tasks.PublishToMavenRepository
import org.gradle.kotlin.dsl.credentials
import org.gradle.kotlin.dsl.existing
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.getValue
import org.gradle.kotlin.dsl.maven
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.registering
import org.gradle.kotlin.dsl.withType
plugins {
`maven-publish`
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
@ -33,38 +36,12 @@ fun ShadowJar.configureStandard() {
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 sourcesJar by tasks.existing
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"
val prefix = "paper.libs"
listOf(
"com.github.salomonbrys.kotson",
"com.google.errorprone.annotations",
@ -83,49 +60,101 @@ val shadowJar by tasks.existing(ShadowJar::class) {
"org.objectweb.asm",
"org.osgi",
"org.tukaani.xz",
"org.slf4j",
"codechicken.diffpatch",
"codechicken.repack"
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
}
val devShadowJar by tasks.registering(ShadowJar::class) {
configureStandard()
val MAVEN_PASSWORD = "AriasCreationsMavenPassword"
val MAVEN_USER = "AriasCreationsMavenUser"
from(project.sourceSets.main.get().output)
archiveClassifier.set("dev")
}
val isSnapshot = version().endsWith("-SNAPSHOT")
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 {
register<MavenPublication>("shadow") {
pluginConfig(version())
artifact(shadowJar) {
classifier = null
}
}
}
register<MavenPublication>("maven") {
standardConfig(version())
}
register<MavenPublication>("shadowLocal") {
pluginConfig(localVersion())
artifact(devShadowJar) {
classifier = null
}
}
register<MavenPublication>("mavenLocal") {
standardConfig(localVersion())
}
publications {
withType(MavenPublication::class).configureEach {
pom {
pomConfig()
repositories {
val url = "https://wav.jfrog.io/artifactory/repo/"
maven(url) {
credentials(PasswordCredentials::class)
name = "wavJfrog"
}
}
}
}
fun MavenPom.pomConfig() {
val repoPath = "ObsidianMC/nugget"
val repoUrl = "https://git.zontreck.com/$repoPath"
tasks.withType(PublishToMavenRepository::class).configureEach {
onlyIf {
!publication.name.endsWith("Local")
}
}
tasks.withType(PublishToMavenLocal::class).configureEach {
onlyIf {
publication.name.endsWith("Local")
}
}
name.set("nugget")
description.set("Gradle plugin for the ObsidianMC project")
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) {
val baseName = project.group.toString() + "." + project.name.substringAfter('-')
groupId = baseName
artifactId = "$baseName.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 {
@ -142,10 +171,10 @@ fun MavenPom.pomConfig() {
developers {
developer {
id.set("DenWav")
id.set("DemonWav")
name.set("Kyle Wood")
email.set("kyle@denwav.dev")
url.set("https://github.com/DenWav")
email.set("demonwav@gmail.com")
url.set("https://github.com/DemonWav")
}
}
@ -155,3 +184,14 @@ fun MavenPom.pomConfig() {
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,5 +1,2 @@
group = com.zontreck.nugget
version = 1.7.4-SNAPSHOT
org.gradle.caching = true
org.gradle.parallel = true
group = io.papermc.paperweight
version = 1.0.0-SNAPSHOT

View file

@ -1,22 +1,21 @@
[versions]
asm = "9.7"
lorenz = "0.5.8"
hypo = "1.2.4"
asm = "9.1"
lorenz = "0.5.6"
hypo = "1.0.1"
[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"
httpclient = "org.apache.httpcomponents:httpclient:4.5.13"
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-atlas = "org.cadixdev:atlas:0.2.0"
cadix-at = "org.cadixdev:at:0.1.0-rc1"
cadix-mercury = "org.cadixdev:mercury:0.1.2-paperweight-SNAPSHOT"
cadix-mercury = "org.cadixdev:mercury:0.1.0-rc2-SNAPSHOT"
hypo-model = { module = "dev.denwav.hypo:hypo-model", version.ref = "hypo" }
hypo-core = { module = "dev.denwav.hypo:hypo-core", version.ref = "hypo" }
@ -24,23 +23,18 @@ 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"
lorenzTiny = "org.quiltmc: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"
gradle-licenser = "org.cadixdev.licenser:org.cadixdev.licenser.gradle.plugin:0.6.0"
gradle-shadow = "com.github.johnrengelman.shadow:com.github.johnrengelman.shadow.gradle.plugin:7.0.0"
gradle-ktlint = "org.jlleitschuh.gradle.ktlint:org.jlleitschuh.gradle.ktlint.gradle.plugin:10.0.0"
gradle-kotlin-dsl = "org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:2.1.4"
gradle-kotlin-plugin = { module = "org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin" }
[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-7.0.2-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) 2021 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,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,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,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,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,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,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,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,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,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,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,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,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,19 +0,0 @@
plugins {
`config-kotlin`
`config-publish`
}
dependencies {
shade(projects.nuggetLib)
implementation(libs.bundles.kotson)
implementation(variantOf(libs.diffpatch) { classifier("all") }) {
isTransitive = false
}
}
gradlePlugin {
plugins.all {
description = "Gradle plugin for developing Paper plugins using server internals"
implementationClass = "io.papermc.paperweight.userdev.PaperweightUser"
}
}

View file

@ -1,333 +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.userdev
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.attribute.Obfuscation
import io.papermc.paperweight.userdev.internal.JunitExclusionRule
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.UserdevSetup
import io.papermc.paperweight.userdev.internal.setup.util.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import javax.inject.Inject
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.attributes.Bundling
import org.gradle.api.attributes.Category
import org.gradle.api.attributes.LibraryElements
import org.gradle.api.attributes.Usage
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.bundling.Jar
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.*
import org.gradle.util.internal.NameMatcher
import org.gradle.workers.WorkerExecutor
abstract class PaperweightUser : Plugin<Project> {
@get:Inject
abstract val workerExecutor: WorkerExecutor
@get:Inject
abstract val javaToolchainService: JavaToolchainService
override fun apply(target: Project) {
checkJavaVersion()
val sharedCacheRoot = target.gradle.gradleUserHomeDir.toPath().resolve("caches/paperweight-userdev")
target.gradle.sharedServices.registerIfAbsent(DOWNLOAD_SERVICE_NAME, DownloadService::class) {}
val cleanAll = target.tasks.register<Delete>("cleanAllPaperweightUserdevCaches") {
group = "paperweight"
description = "Delete the project-local & all shared paperweight-userdev setup caches."
delete(target.layout.cache)
delete(sharedCacheRoot)
}
val cleanCache by target.tasks.registering<Delete> {
group = "paperweight"
description = "Delete the project-local paperweight-userdev setup cache."
delete(target.layout.cache)
}
target.configurations.register(DEV_BUNDLE_CONFIG)
// must not be initialized until afterEvaluate, as it resolves the dev bundle
val userdevSetup by lazy { createSetup(target, sharedCacheRoot.resolve(paperweightHash)) }
val userdev = target.extensions.create(
PAPERWEIGHT_EXTENSION,
PaperweightUserExtension::class,
target,
workerExecutor,
javaToolchainService,
target.provider { userdevSetup },
target.objects
)
target.dependencies.extensions.create(
PAPERWEIGHT_EXTENSION,
PaperweightUserDependenciesExtension::class,
target.dependencies
)
createConfigurations(target, target.provider { userdevSetup })
val reobfJar by target.tasks.registering<RemapJar> {
group = "paperweight"
description = "Remap the compiled plugin jar to Spigot's obfuscated runtime names."
mappingsFile.pathProvider(target.provider { userdevSetup.reobfMappings })
remapClasspath.from(target.provider { userdevSetup.serverJar })
fromNamespace.set(DEOBF_NAMESPACE)
toNamespace.set(SPIGOT_NAMESPACE)
remapper.from(project.configurations.named(PLUGIN_REMAPPER_CONFIG))
remapperArgs.set(target.provider { userdevSetup.pluginRemapArgs })
}
target.configurations.register(REOBF_CONFIG) {
isCanBeConsumed = true
isCanBeResolved = false
attributes {
attribute(Usage.USAGE_ATTRIBUTE, target.objects.named(Usage.JAVA_RUNTIME))
attribute(Category.CATEGORY_ATTRIBUTE, target.objects.named(Category.LIBRARY))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, target.objects.named(LibraryElements.JAR))
attribute(Bundling.BUNDLING_ATTRIBUTE, target.objects.named(Bundling.EXTERNAL))
attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, target.objects.named(Obfuscation.OBFUSCATED))
}
outgoing.artifact(reobfJar)
}
target.afterEvaluate {
// Manually check if cleanCache is a target, and skip setup.
// Gradle moved NameMatcher to internal packages in 7.1, so this solution isn't ideal,
// but it does work and allows using the cleanCache task without setting up the workspace first
val cleaningCache = gradle.startParameter.taskRequests
.any { req ->
req.args.any { arg ->
NameMatcher().find(arg, tasks.names) in setOf(cleanCache.name, cleanAll.name)
}
}
if (cleaningCache) {
return@afterEvaluate
}
userdev.reobfArtifactConfiguration.get()
.configure(this, reobfJar)
decorateJarManifests()
if (userdev.injectPaperRepository.get()) {
repositories.maven(PAPER_MAVEN_REPO_URL) {
content { onlyForConfigurations(DEV_BUNDLE_CONFIG) }
}
}
if (userdev.applyJunitExclusionRule.get()) {
applyJunitExclusionRule()
}
// Print a friendly error message if the dev bundle is missing before we call anything else that will try and resolve it
checkForDevBundle()
configureRepositories(userdevSetup)
cleanSharedCaches(this, sharedCacheRoot)
}
}
private fun Project.decorateJarManifests() {
val op = Action<Jar> {
manifest {
attributes(MAPPINGS_NAMESPACE_MANIFEST_KEY to DEOBF_NAMESPACE)
}
}
tasks.named("jar", Jar::class, op)
if ("shadowJar" in tasks.names) {
tasks.named("shadowJar", Jar::class, op)
}
}
private fun Project.applyJunitExclusionRule() = dependencies {
components {
withModule<JunitExclusionRule>(JunitExclusionRule.TARGET)
}
}
private fun Project.configureRepositories(userdevSetup: UserdevSetup) = repositories {
maven(userdevSetup.paramMappings.url) {
name = PARAM_MAPPINGS_REPO_NAME
content { onlyForConfigurations(PARAM_MAPPINGS_CONFIG) }
}
maven(userdevSetup.remapper.url) {
name = REMAPPER_REPO_NAME
content { onlyForConfigurations(REMAPPER_CONFIG) }
}
maven(userdevSetup.decompiler.url) {
name = DECOMPILER_REPO_NAME
content { onlyForConfigurations(DECOMPILER_CONFIG) }
}
for (repo in userdevSetup.libraryRepositories) {
maven(repo)
}
userdevSetup.addIvyRepository(project)
}
private fun Project.checkForDevBundle() {
val hasDevBundle = runCatching {
!configurations.getByName(DEV_BUNDLE_CONFIG).isEmpty
}
if (hasDevBundle.isFailure || !hasDevBundle.getOrThrow()) {
val message = "paperweight requires a development bundle to be added to the 'paperweightDevelopmentBundle' configuration, as" +
" well as a repository to resolve it from in order to function. Use the paperweightDevBundle extension function to do this easily."
throw PaperweightException(
message,
hasDevBundle.exceptionOrNull()?.let { PaperweightException("Failed to resolve dev bundle", it) }
)
}
}
private fun createConfigurations(
target: Project,
userdevSetup: Provider<UserdevSetup>
) {
target.configurations.register(DECOMPILER_CONFIG) {
defaultDependencies {
for (dep in userdevSetup.get().decompiler.coordinates) {
add(target.dependencies.create(dep))
}
}
}
target.configurations.register(PARAM_MAPPINGS_CONFIG) {
defaultDependencies {
for (dep in userdevSetup.get().paramMappings.coordinates) {
add(target.dependencies.create(dep))
}
}
}
fun makeRemapperConfig(name: String) {
target.configurations.register(name) {
defaultDependencies {
for (dep in userdevSetup.get().remapper.coordinates) {
// we use a fat jar for tiny-remapper, so we don't need its transitive deps
val fatTiny = dep.contains(":tiny-remapper:") && dep.endsWith(":fat")
add(
target.dependencies.create(dep) {
if (fatTiny) {
isTransitive = false
}
}
)
}
}
}
}
makeRemapperConfig(REMAPPER_CONFIG)
makeRemapperConfig(PLUGIN_REMAPPER_CONFIG)
val mojangMappedServerConfig = target.configurations.register(MOJANG_MAPPED_SERVER_CONFIG) {
defaultDependencies {
val ctx = createContext(target)
userdevSetup.get().let { setup ->
setup.createOrUpdateIvyRepository(ctx)
setup.populateCompileConfiguration(ctx, this)
}
}
}
target.plugins.withType<JavaPlugin>().configureEach {
listOf(
JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME,
JavaPlugin.TEST_IMPLEMENTATION_CONFIGURATION_NAME
).map(target.configurations::named).forEach { config ->
config {
extendsFrom(mojangMappedServerConfig.get())
}
}
}
target.configurations.register(MOJANG_MAPPED_SERVER_RUNTIME_CONFIG) {
defaultDependencies {
val ctx = createContext(target)
userdevSetup.get().let { setup ->
setup.createOrUpdateIvyRepository(ctx)
setup.populateRuntimeConfiguration(ctx, this)
}
}
}
}
private fun createContext(project: Project): SetupHandler.Context =
SetupHandler.Context(project, workerExecutor, javaToolchainService)
private fun createSetup(target: Project, sharedCacheRoot: Path): UserdevSetup {
val bundleConfig = target.configurations.named(DEV_BUNDLE_CONFIG)
val devBundleZip = bundleConfig.map { it.singleFile }.convertToPath()
val bundleHash = devBundleZip.sha256asHex()
val cacheDir = if (!target.sharedCaches) {
target.layout.cache
} else {
when (bundleConfig.get().dependencies.single()) {
is ProjectDependency -> target.layout.cache
is ModuleDependency -> {
val resolved =
bundleConfig.get().incoming.resolutionResult.rootComponent.get().dependencies.single() as ResolvedDependencyResult
val resolvedId = resolved.selected.id as ModuleComponentIdentifier
sharedCacheRoot.resolve("module/${resolvedId.group}/${resolvedId.module}/${resolvedId.version}")
}
else -> sharedCacheRoot.resolve("non-module/$bundleHash")
}
}
val serviceName = "paperweight-userdev:setupService:$bundleHash"
return target.gradle.sharedServices
.registerIfAbsent(serviceName, UserdevSetup::class) {
parameters {
cache.set(cacheDir)
bundleZip.set(devBundleZip)
bundleZipHash.set(bundleHash)
downloadService.set(target.download)
genSources.set(target.genSources)
}
}
.get()
}
}

View file

@ -1,263 +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.userdev
import io.papermc.paperweight.util.constants.*
import org.gradle.api.Action
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.*
abstract class PaperweightUserDependenciesExtension(
private val dependencies: DependencyHandler
) {
/**
* Adds a dependency on Paper's dev bundle to the dev bundle [org.gradle.api.artifacts.Configuration].
*
* @param version dependency version
* @param group dependency group
* @param artifactId dependency artifactId
* @param configuration dependency configuration
* @param classifier dependency classifier
* @param ext dependency extension
* @param devBundleConfigurationName name of the dev bundle [org.gradle.api.artifacts.Configuration]
* @param configurationAction action configuring the dependency
* @return dependency
*/
@JvmOverloads
fun paperDevBundle(
version: String? = null,
group: String = "io.papermc.paper",
artifactId: String = "dev-bundle",
configuration: String? = null,
classifier: String? = null,
ext: String? = null,
devBundleConfigurationName: String = DEV_BUNDLE_CONFIG,
configurationAction: Action<ExternalModuleDependency> = nullAction()
): ExternalModuleDependency {
val dep = dependencies.create(group, artifactId, version, configuration, classifier, ext)
configurationAction(dep)
dependencies.add(devBundleConfigurationName, dep)
return dep
}
/**
* Adds a dependency on Folia's dev bundle to the dev bundle [org.gradle.api.artifacts.Configuration].
*
* @param version dependency version
* @param group dependency group
* @param artifactId dependency artifactId
* @param configuration dependency configuration
* @param classifier dependency classifier
* @param ext dependency extension
* @param devBundleConfigurationName name of the dev bundle [org.gradle.api.artifacts.Configuration]
* @param configurationAction action configuring the dependency
* @return dependency
*/
@JvmOverloads
fun foliaDevBundle(
version: String? = null,
group: String = "dev.folia",
artifactId: String = "dev-bundle",
configuration: String? = null,
classifier: String? = null,
ext: String? = null,
devBundleConfigurationName: String = DEV_BUNDLE_CONFIG,
configurationAction: Action<ExternalModuleDependency> = nullAction()
): ExternalModuleDependency {
val dep = dependencies.create(group, artifactId, version, configuration, classifier, ext)
configurationAction(dep)
dependencies.add(devBundleConfigurationName, dep)
return dep
}
/**
* Adds a dependency to the dev bundle [org.gradle.api.artifacts.Configuration].
*
* @param group dependency group
* @param version dependency version
* @param artifactId dependency artifactId
* @param configuration dependency configuration
* @param classifier dependency classifier
* @param ext dependency extension
* @param devBundleConfigurationName name of the dev bundle [org.gradle.api.artifacts.Configuration]
* @param configurationAction action configuring the dependency
* @return dependency
*/
@JvmOverloads
fun devBundle(
group: String,
version: String? = null,
artifactId: String = "dev-bundle",
configuration: String? = null,
classifier: String? = null,
ext: String? = null,
devBundleConfigurationName: String = DEV_BUNDLE_CONFIG,
configurationAction: Action<ExternalModuleDependency> = nullAction()
): ExternalModuleDependency {
val dep = dependencies.create(group, artifactId, version, configuration, classifier, ext)
configurationAction(dep)
dependencies.add(devBundleConfigurationName, dep)
return dep
}
/**
* Adds a dependency to the [DEV_BUNDLE_CONFIG] configuration.
*
* Intended for use with Gradle version catalogs.
*
* @param bundle dev bundle dependency provider
* @param configurationAction action configuring the dependency
*/
@JvmOverloads
fun devBundle(
bundle: Provider<MinimalExternalModuleDependency>,
configurationAction: Action<ExternalModuleDependency> = nullAction()
) {
dependencies.addProvider(DEV_BUNDLE_CONFIG, bundle, configurationAction)
}
/**
* Adds a dependency on the Paper dev bundle to the [DEV_BUNDLE_CONFIG] configuration.
*
* Intended for use with Gradle version catalogs.
*
* @param version version provider
* @param configurationAction action configuring the dependency
*/
@JvmOverloads
fun paperDevBundle(
version: Provider<String>,
configurationAction: Action<ExternalModuleDependency> = nullAction()
) {
dependencies.addProvider(DEV_BUNDLE_CONFIG, version.map { "io.papermc.paper:dev-bundle:$it" }, configurationAction)
}
/**
* Adds a dependency on the Folia dev bundle to the [DEV_BUNDLE_CONFIG] configuration.
*
* Intended for use with Gradle version catalogs.
*
* @param version version provider
* @param configurationAction action configuring the dependency
*/
@JvmOverloads
fun foliaDevBundle(
version: Provider<String>,
configurationAction: Action<ExternalModuleDependency> = nullAction()
) {
dependencies.addProvider(DEV_BUNDLE_CONFIG, version.map { "dev.folia:dev-bundle:$it" }, configurationAction)
}
/**
* Creates a Folia dev bundle dependency without adding it to any configurations.
*
* @param version dependency version
* @param group dependency group
* @param artifactId dependency artifactId
* @param configuration dependency configuration
* @param classifier dependency classifier
* @param ext dependency extension
* @param configurationAction action configuring the dependency
* @return dependency
*/
@JvmOverloads
fun foliaDevBundleDependency(
version: String? = null,
group: String = "dev.folia",
artifactId: String = "dev-bundle",
configuration: String? = null,
classifier: String? = null,
ext: String? = null,
configurationAction: Action<ExternalModuleDependency> = nullAction()
): ExternalModuleDependency {
val dep = dependencies.create(group, artifactId, version, configuration, classifier, ext)
configurationAction(dep)
return dep
}
/**
* Creates a Paper dev bundle dependency without adding it to any configurations.
*
* @param version dependency version
* @param group dependency group
* @param artifactId dependency artifactId
* @param configuration dependency configuration
* @param classifier dependency classifier
* @param ext dependency extension
* @param configurationAction action configuring the dependency
* @return dependency
*/
@JvmOverloads
fun paperDevBundleDependency(
version: String? = null,
group: String = "io.papermc.paper",
artifactId: String = "dev-bundle",
configuration: String? = null,
classifier: String? = null,
ext: String? = null,
configurationAction: Action<ExternalModuleDependency> = nullAction()
): ExternalModuleDependency {
val dep = dependencies.create(group, artifactId, version, configuration, classifier, ext)
configurationAction(dep)
return dep
}
/**
* Creates a dev bundle dependency without adding it to any configurations.
*
* @param group dependency group
* @param version dependency version
* @param artifactId dependency artifactId
* @param configuration dependency configuration
* @param classifier dependency classifier
* @param ext dependency extension
* @param configurationAction action configuring the dependency
* @return dependency
*/
@JvmOverloads
fun devBundleDependency(
group: String,
version: String? = null,
artifactId: String = "dev-bundle",
configuration: String? = null,
classifier: String? = null,
ext: String? = null,
configurationAction: Action<ExternalModuleDependency> = nullAction()
): ExternalModuleDependency {
val dep = dependencies.create(group, artifactId, version, configuration, classifier, ext)
configurationAction(dep)
return dep
}
@Suppress("unchecked_cast")
private fun <T> nullAction(): Action<T> {
return NullAction as Action<T>
}
private object NullAction : Action<Any> {
override fun execute(t: Any) {}
}
}

View file

@ -1,85 +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.userdev
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.util.*
import org.gradle.api.Project
import org.gradle.api.file.RegularFile
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkerExecutor
/**
* Extension exposing configuration and other APIs for paperweight userdev.
*/
abstract class PaperweightUserExtension(
project: Project,
workerExecutor: WorkerExecutor,
javaToolchainService: JavaToolchainService,
setup: Provider<SetupHandler>,
objects: ObjectFactory
) {
/**
* Whether to inject the Paper maven repository for use by the dev bundle configuration.
*
* True by default to allow easily resolving Paper development bundles.
*/
val injectPaperRepository: Property<Boolean> = objects.property<Boolean>().convention(true)
/**
* Whether to patch dependencies to exclude `junit:junit` from the transitive dependencies of `com.googlecode.json-simple:json-simple`.
*
* True by default to avoid `junit:junit` appearing on the `compileClasspath` with older versions of Paper.
*/
val applyJunitExclusionRule: Property<Boolean> = objects.property<Boolean>().convention(true)
/**
* The [ReobfArtifactConfiguration] is responsible for setting the input and output jars for `reobfJar`,
* as well as changing the classifiers of other jars (i.e. `jar` or `shadowJar`).
*/
val reobfArtifactConfiguration: Property<ReobfArtifactConfiguration> = objects.property<ReobfArtifactConfiguration>()
.convention(ReobfArtifactConfiguration.REOBF_PRODUCTION)
/**
* Provides a runnable Mojang mapped server jar, extracted from the current dev bundle.
*/
@Deprecated(
message = "As of 1.18, the dev bundle no longer contains a runnable server jar. Use the mojangMappedServerRuntime configuration instead.",
replaceWith = ReplaceWith("project.configurations.mojangMappedServerRuntime"),
level = DeprecationLevel.WARNING
)
val mojangMappedServerJar: Provider<RegularFile> = objects.fileProperty().pathProvider(
setup.map { it.serverJar(SetupHandler.Context(project, workerExecutor, javaToolchainService)) }
).withDisallowChanges().withDisallowUnsafeRead()
/**
* Provides the Minecraft version of the current dev bundle.
*/
val minecraftVersion: Provider<String> = objects.property<String>().value(
setup.map { it.minecraftVersion }
).withDisallowChanges()
}

View file

@ -1,91 +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.userdev
import io.papermc.paperweight.tasks.*
import org.gradle.api.Project
import org.gradle.api.UnknownTaskException
import org.gradle.api.plugins.BasePluginExtension
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.kotlin.dsl.*
/**
* Configures input/output for `reobfJar` and potentially changes classifiers of other jars.
*/
fun interface ReobfArtifactConfiguration {
fun configure(project: Project, reobfJar: TaskProvider<RemapJar>)
companion object {
/**
* Used when the reobfJar artifact is the main production output.
*
* Sets the `jar` classifier to `dev`, and the `shadowJar` classifier to `dev-all` if it exists.
* The `reobfJar` will have no classifier. [BasePluginExtension.getArchivesName] is used to name
* the `reobfJar`, falling back to the project name if it is not configured.
*/
@JvmStatic
val REOBF_PRODUCTION: ReobfArtifactConfiguration = ReobfArtifactConfiguration { project, reobfJar ->
val jar = project.tasks.named<AbstractArchiveTask>(JavaPlugin.JAR_TASK_NAME) {
archiveClassifier.set("dev")
}
val devJarTask = try {
project.tasks.named<AbstractArchiveTask>("shadowJar") {
archiveClassifier.set("dev-all")
}
} catch (ex: UnknownTaskException) {
jar
}
reobfJar {
inputJar.set(devJarTask.flatMap { it.archiveFile })
outputJar.convention(archivesName(project).flatMap { layout.buildDirectory.file("libs/$it-${project.version}.jar") })
}
}
/**
* Used when the Mojang-mapped artifact (`jar`/`shadowJar`) is the main production output.
*
* Does not modify `jar` or `shadowJar` classifier, [BasePluginExtension.getArchivesName] is used to name
* the `reobfJar`, falling back to the project name if it is not configured.
*/
@JvmStatic
val MOJANG_PRODUCTION: ReobfArtifactConfiguration = ReobfArtifactConfiguration { project, reobfJar ->
val devJarTask = try {
project.tasks.named<AbstractArchiveTask>("shadowJar")
} catch (ex: UnknownTaskException) {
project.tasks.named<AbstractArchiveTask>(JavaPlugin.JAR_TASK_NAME)
}
reobfJar {
inputJar.set(devJarTask.flatMap { it.archiveFile })
outputJar.convention(archivesName(project).flatMap { layout.buildDirectory.file("libs/$it-${project.version}-reobf.jar") })
}
}
fun archivesName(project: Project): Provider<String> =
project.extensions.findByType(BasePluginExtension::class)?.archivesName ?: project.provider { project.name }
}
}

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.userdev.attribute
import org.gradle.api.Named
import org.gradle.api.attributes.Attribute
/**
* Attribute representing the state of obfuscation.
*/
interface Obfuscation : Named {
companion object {
val OBFUSCATION_ATTRIBUTE = Attribute.of(
"io.papermc.paperweight.obfuscation",
Obfuscation::class.java
)
// Note that we don't reference this in the project, but you can use it as a value to pick the
// runtimeElements configuration instead of reobf.
/**
* No obfuscation, i.e. using human-readable names.
*/
const val NONE = "none"
/**
* Obfuscated, i.e. using runtime names.
*/
const val OBFUSCATED = "obfuscated"
}
}

View file

@ -1,83 +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
*/
import io.papermc.paperweight.util.constants.*
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.kotlin.dsl.*
/**
* Adds a dependency on Paper's dev bundle to the dev bundle [org.gradle.api.artifacts.Configuration].
*
* @param version dependency version
* @param group dependency group
* @param artifactId dependency artifactId
* @param configuration dependency configuration
* @param classifier dependency classifier
* @param ext dependency extension
* @param devBundleConfigurationName name of the dev bundle [org.gradle.api.artifacts.Configuration]
* @param configurationAction action configuring the dependency
*/
@Deprecated(
message = "Replaced by extension methods",
replaceWith = ReplaceWith(
"paperweight.paperDevBundle"
)
)
fun DependencyHandlerScope.paperDevBundle(
version: String? = null,
group: String = "io.papermc.paper",
artifactId: String = "dev-bundle",
configuration: String? = null,
classifier: String? = null,
ext: String? = null,
devBundleConfigurationName: String = DEV_BUNDLE_CONFIG,
configurationAction: ExternalModuleDependency.() -> Unit = {}
): ExternalModuleDependency = devBundleConfigurationName(group, artifactId, version, configuration, classifier, ext, configurationAction)
/**
* Adds a dependency to the dev bundle [org.gradle.api.artifacts.Configuration].
*
* @param group dependency group
* @param version dependency version
* @param artifactId dependency artifactId
* @param configuration dependency configuration
* @param classifier dependency classifier
* @param ext dependency extension
* @param devBundleConfigurationName name of the dev bundle [org.gradle.api.artifacts.Configuration]
* @param configurationAction action configuring the dependency
*/
@Deprecated(
message = "Replaced by extension methods",
replaceWith = ReplaceWith(
"paperweight.devBundle"
)
)
fun DependencyHandlerScope.paperweightDevBundle(
group: String,
version: String? = null,
artifactId: String = "dev-bundle",
configuration: String? = null,
classifier: String? = null,
ext: String? = null,
devBundleConfigurationName: String = DEV_BUNDLE_CONFIG,
configurationAction: ExternalModuleDependency.() -> Unit = {}
): ExternalModuleDependency = devBundleConfigurationName(group, artifactId, version, configuration, classifier, ext, configurationAction)

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.userdev.internal
import org.gradle.api.artifacts.CacheableRule
import org.gradle.api.artifacts.ComponentMetadataContext
import org.gradle.api.artifacts.ComponentMetadataRule
/**
* Excludes `junit:junit` from the transitive dependencies of all variants.
*/
@CacheableRule
abstract class JunitExclusionRule : ComponentMetadataRule {
companion object {
const val TARGET = "com.googlecode.json-simple:json-simple"
}
override fun execute(ctx: ComponentMetadataContext) {
ctx.details.allVariants {
withDependencies {
removeIf {
it.group == "junit" && it.name == "junit"
}
}
}
}
}

View file

@ -1,91 +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.userdev.internal.setup
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.internal.setup.v2.DevBundleV2
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
private val supported = mapOf(
2 to DevBundleV2.Config::class, // 1.17.1
3 to GenerateDevBundle.DevBundleConfig::class, // up to 1.20.4
4 to GenerateDevBundle.DevBundleConfig::class, // 1.20.5, early 1.20.6
GenerateDevBundle.currentDataVersion to GenerateDevBundle.DevBundleConfig::class, // 1.20.6+ (nullable mojangApiCoordinates)
)
data class ExtractedBundle<C>(
val changed: Boolean,
val config: C,
val dataVersion: Int,
val dir: Path,
) {
constructor(bundleChanged: Boolean, pair: Pair<C, Int>, dir: Path) :
this(bundleChanged, pair.first, pair.second, dir)
}
fun extractDevBundle(
destinationDirectory: Path,
devBundle: Path,
newDevBundleHash: String
): ExtractedBundle<Any> {
val hashFile = destinationDirectory.resolve("current.sha256")
if (destinationDirectory.exists()) {
val currentDevBundleHash = if (hashFile.isRegularFile()) hashFile.readText(Charsets.UTF_8) else ""
if (currentDevBundleHash.isNotBlank() && newDevBundleHash == currentDevBundleHash) {
return ExtractedBundle(false, readDevBundle(destinationDirectory), destinationDirectory)
}
destinationDirectory.deleteRecursive()
}
destinationDirectory.createDirectories()
hashFile.writeText(newDevBundleHash, Charsets.UTF_8)
devBundle.openZip().use { fs ->
fs.getPath("/").copyRecursivelyTo(destinationDirectory)
}
return ExtractedBundle(true, readDevBundle(destinationDirectory), destinationDirectory)
}
private fun readDevBundle(
extractedDevBundlePath: Path
): Pair<Any, Int> {
val dataVersion = extractedDevBundlePath.resolve("data-version.txt").readText().trim().toInt()
if (dataVersion !in supported) {
throw PaperweightException(
"The paperweight development bundle you are attempting to use is of data version '$dataVersion', but" +
" the currently running version of paperweight only supports data versions '$supported'."
)
}
val configClass = supported[dataVersion] ?: throw PaperweightException("Could not find config class for version $dataVersion?")
val configFile = extractedDevBundlePath.resolve("config.json")
val config: Any = configFile.bufferedReader(Charsets.UTF_8).use { reader ->
gson.fromJson(reader, configClass.java)
}
return config to dataVersion
}

View file

@ -1,91 +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.userdev.internal.setup
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.internal.setup.v2.DevBundleV2
import io.papermc.paperweight.userdev.internal.setup.v2.SetupHandlerImplV2
import io.papermc.paperweight.util.*
import java.nio.file.Path
import org.gradle.api.Project
import org.gradle.api.artifacts.DependencySet
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.workers.WorkerExecutor
interface SetupHandler {
fun createOrUpdateIvyRepository(context: Context)
fun configureIvyRepo(repo: IvyArtifactRepository)
fun populateCompileConfiguration(context: Context, dependencySet: DependencySet)
fun populateRuntimeConfiguration(context: Context, dependencySet: DependencySet)
fun serverJar(context: Context): Path
val serverJar: Path
val reobfMappings: Path
val minecraftVersion: String
val pluginRemapArgs: List<String>
val paramMappings: MavenDep
val decompiler: MavenDep
val remapper: MavenDep
val libraryRepositories: List<String>
data class Context(
val project: Project,
val workerExecutor: WorkerExecutor,
val javaToolchainService: JavaToolchainService
) {
val defaultJavaLauncher: JavaLauncher
get() = javaToolchainService.defaultJavaLauncher(project).get()
}
companion object {
@Suppress("unchecked_cast")
fun create(
parameters: UserdevSetup.Parameters,
extractedBundle: ExtractedBundle<Any>
): SetupHandler = when (extractedBundle.config) {
is GenerateDevBundle.DevBundleConfig -> SetupHandlerImpl(
parameters,
extractedBundle as ExtractedBundle<GenerateDevBundle.DevBundleConfig>,
)
is DevBundleV2.Config -> SetupHandlerImplV2(
parameters,
extractedBundle as ExtractedBundle<DevBundleV2.Config>
)
else -> throw PaperweightException("Unknown dev bundle config type: ${extractedBundle.config::class.java.typeName}")
}
}
}

View file

@ -1,281 +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.userdev.internal.setup
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.internal.setup.step.*
import io.papermc.paperweight.userdev.internal.setup.util.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import org.gradle.api.artifacts.DependencySet
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
class SetupHandlerImpl(
private val parameters: UserdevSetup.Parameters,
private val bundle: ExtractedBundle<GenerateDevBundle.DevBundleConfig>,
private val cache: Path = parameters.cache.path,
) : SetupHandler {
private val vanillaSteps by lazy {
VanillaSteps(
bundle.config.minecraftVersion,
cache,
parameters.downloadService.get(),
bundle.changed,
)
}
private val vanillaServerJar: Path = cache.resolve(paperSetupOutput("vanillaServerJar", "jar"))
private val minecraftLibraryJars = cache.resolve(MINECRAFT_JARS_PATH)
private val filteredVanillaServerJar: Path = cache.resolve(paperSetupOutput("filterJar", "jar"))
private val mojangPlusYarnMappings: Path = cache.resolve(MOJANG_YARN_MAPPINGS)
private val mappedMinecraftServerJar: Path = cache.resolve(paperSetupOutput("mappedMinecraftServerJar", "jar"))
private val fixedMinecraftServerJar: Path = cache.resolve(paperSetupOutput("fixedMinecraftServerJar", "jar"))
private val accessTransformedServerJar: Path = cache.resolve(paperSetupOutput("accessTransformedServerJar", "jar"))
private val decompiledMinecraftServerJar: Path = cache.resolve(paperSetupOutput("decompileMinecraftServerJar", "jar"))
private val patchedSourcesJar: Path = cache.resolve(paperSetupOutput("patchedSourcesJar", "jar"))
private val mojangMappedPaperJar: Path = cache.resolve(paperSetupOutput("applyMojangMappedPaperclipPatch", "jar"))
private fun minecraftLibraryJars(): List<Path> = minecraftLibraryJars.filesMatchingRecursive("*.jar")
private fun generateSources(context: SetupHandler.Context) {
vanillaSteps.downloadVanillaServerJar()
val extractStep = createExtractFromBundlerStep()
val filterVanillaJarStep = FilterVanillaJar(vanillaServerJar, bundle.config.buildData.vanillaJarIncludes, filteredVanillaServerJar)
val genMappingsStep = GenerateMappingsStep.create(
context,
vanillaSteps,
filteredVanillaServerJar,
::minecraftLibraryJars,
mojangPlusYarnMappings,
)
val remapMinecraftStep = RemapMinecraft.create(
context,
bundle.config.buildData.minecraftRemapArgs,
filteredVanillaServerJar,
::minecraftLibraryJars,
mojangPlusYarnMappings,
mappedMinecraftServerJar,
cache,
)
val fixStep = FixMinecraftJar(mappedMinecraftServerJar, fixedMinecraftServerJar, vanillaServerJar)
val atStep = AccessTransformMinecraft(
bundle.dir.resolve(bundle.config.buildData.accessTransformFile),
fixedMinecraftServerJar,
accessTransformedServerJar,
)
val decomp = DecompileMinecraft.create(
context,
accessTransformedServerJar,
decompiledMinecraftServerJar,
cache,
::minecraftLibraryJars,
bundle.config.decompile.args,
)
val applyDevBundlePatchesStep = ApplyDevBundlePatches(
decompiledMinecraftServerJar,
bundle.dir.resolve(bundle.config.patchDir),
patchedSourcesJar
)
StepExecutor.executeSteps(
bundle.changed,
context,
extractStep,
filterVanillaJarStep,
genMappingsStep,
remapMinecraftStep,
fixStep,
atStep,
decomp,
applyDevBundlePatchesStep,
)
}
// This can be called when a user queries the server jar provider in
// PaperweightUserExtension, possibly by a task running in a separate
// thread to dependency resolution.
@Synchronized
private fun applyMojangMappedPaperclipPatch(context: SetupHandler.Context) {
if (setupCompleted) {
return
}
lockSetup(cache, true) {
StepExecutor.executeStep(
context,
RunPaperclip(
bundle.dir.resolve(bundle.config.buildData.mojangMappedPaperclipFile),
mojangMappedPaperJar,
vanillaSteps.mojangJar,
minecraftVersion,
)
)
}
}
private var setupCompleted = false
@Synchronized
override fun createOrUpdateIvyRepository(context: SetupHandler.Context) {
if (setupCompleted) {
return
}
lockSetup(cache) {
createOrUpdateIvyRepositoryDirect(context)
}
}
private fun createOrUpdateIvyRepositoryDirect(context: SetupHandler.Context) {
val source = if (parameters.genSources.get()) {
generateSources(context)
patchedSourcesJar
} else {
vanillaSteps.downloadVanillaServerJar()
StepExecutor.executeStep(context, createExtractFromBundlerStep())
null
}
applyMojangMappedPaperclipPatch(context)
val deps = mutableListOf<String>()
deps.addAll(bundle.config.buildData.compileDependencies)
deps.add(bundle.config.apiCoordinates)
bundle.config.mojangApiCoordinates?.let { deps.add(it) }
installPaperServer(
cache,
bundle.config.mappedServerCoordinates,
deps,
mojangMappedPaperJar,
source,
minecraftVersion,
)
setupCompleted = true
}
override fun configureIvyRepo(repo: IvyArtifactRepository) {
repo.content {
includeFromDependencyNotation(bundle.config.mappedServerCoordinates)
}
}
override fun populateCompileConfiguration(context: SetupHandler.Context, dependencySet: DependencySet) {
dependencySet.add(context.project.dependencies.create(bundle.config.mappedServerCoordinates))
}
override fun populateRuntimeConfiguration(context: SetupHandler.Context, dependencySet: DependencySet) {
listOfNotNull(
bundle.config.mappedServerCoordinates,
bundle.config.apiCoordinates,
bundle.config.mojangApiCoordinates
).forEach { coordinate ->
val dep = context.project.dependencies.create(coordinate).also {
(it as ExternalModuleDependency).isTransitive = false
}
dependencySet.add(dep)
}
for (coordinates in bundle.config.buildData.runtimeDependencies) {
val dep = context.project.dependencies.create(coordinates).also {
(it as ExternalModuleDependency).isTransitive = false
}
dependencySet.add(dep)
}
}
override fun serverJar(context: SetupHandler.Context): Path {
applyMojangMappedPaperclipPatch(context)
return mojangMappedPaperJar
}
override val serverJar: Path
get() = mojangMappedPaperJar
override val reobfMappings: Path
get() = bundle.dir.resolve(bundle.config.buildData.reobfMappingsFile)
override val minecraftVersion: String
get() = bundle.config.minecraftVersion
override val pluginRemapArgs: List<String>
get() = bundle.config.buildData.pluginRemapArgs
override val paramMappings: MavenDep
get() = bundle.config.buildData.paramMappings
override val decompiler: MavenDep
get() = bundle.config.decompile.dep
override val remapper: MavenDep
get() = bundle.config.remapper
override val libraryRepositories: List<String>
get() = bundle.config.buildData.libraryRepositories
private fun createExtractFromBundlerStep(): ExtractFromBundlerStep = ExtractFromBundlerStep(
cache,
vanillaSteps,
vanillaServerJar,
minecraftLibraryJars,
::minecraftLibraryJars
)
private class ExtractFromBundlerStep(
cache: Path,
private val vanillaSteps: VanillaSteps,
private val vanillaServerJar: Path,
private val minecraftLibraryJars: Path,
private val listMinecraftLibraryJars: () -> List<Path>,
) : SetupStep {
override val name: String = "extract libraries and server from downloaded jar"
override val hashFile: Path = cache.resolve(paperSetupOutput("extractFromServerBundler", "hashes"))
override fun run(context: SetupHandler.Context) {
ServerBundler.extractFromBundler(
vanillaSteps.mojangJar,
vanillaServerJar,
minecraftLibraryJars,
null,
null,
null,
null,
)
}
override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {
builder.include(vanillaSteps.mojangJar, vanillaServerJar)
builder.include(listMinecraftLibraryJars())
}
}
}

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.userdev.internal.setup
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.userdev.internal.setup.util.*
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.DependencySet
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.kotlin.dsl.*
abstract class UserdevSetup : BuildService<UserdevSetup.Parameters>, SetupHandler {
companion object {
val LOGGER: Logger = Logging.getLogger(UserdevSetup::class.java)
}
interface Parameters : BuildServiceParameters {
val bundleZip: RegularFileProperty
val bundleZipHash: Property<String>
val cache: RegularFileProperty
val downloadService: Property<DownloadService>
val genSources: Property<Boolean>
}
private val extractDevBundle: ExtractedBundle<Any> = lockSetup(parameters.cache.path) {
val extract = extractDevBundle(
parameters.cache.path.resolve(paperSetupOutput("extractDevBundle", "dir")),
parameters.bundleZip.path,
parameters.bundleZipHash.get()
)
lastUsedFile(parameters.cache.path).writeText(System.currentTimeMillis().toString())
extract
}
private val setup = createSetup()
private fun createSetup(): SetupHandler =
SetupHandler.create(parameters, extractDevBundle)
fun addIvyRepository(project: Project) {
project.repositories {
setupIvyRepository(parameters.cache.path.resolve(IVY_REPOSITORY)) {
configureIvyRepo(this)
}
}
}
// begin delegate to setup
override fun createOrUpdateIvyRepository(context: SetupHandler.Context) {
setup.createOrUpdateIvyRepository(context)
}
override fun configureIvyRepo(repo: IvyArtifactRepository) {
setup.configureIvyRepo(repo)
}
override fun populateCompileConfiguration(context: SetupHandler.Context, dependencySet: DependencySet) {
setup.populateCompileConfiguration(context, dependencySet)
}
override fun populateRuntimeConfiguration(context: SetupHandler.Context, dependencySet: DependencySet) {
setup.populateRuntimeConfiguration(context, dependencySet)
}
override fun serverJar(context: SetupHandler.Context): Path {
return setup.serverJar(context)
}
override val serverJar: Path
get() = setup.serverJar
override val reobfMappings: Path
get() = setup.reobfMappings
override val minecraftVersion: String
get() = setup.minecraftVersion
override val pluginRemapArgs: List<String>
get() = setup.pluginRemapArgs
override val paramMappings: MavenDep
get() = setup.paramMappings
override val decompiler: MavenDep
get() = setup.decompiler
override val remapper: MavenDep
get() = setup.remapper
override val libraryRepositories: List<String>
get() = setup.libraryRepositories
// end delegate to setup
}

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.userdev.internal.setup.step
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import java.nio.file.Path
class AccessTransformMinecraft(
@Input private val at: Path,
@Input private val inputJar: Path,
@Output private val outputJar: Path,
) : SetupStep {
override val name: String = "access transform minecraft server jar"
override val hashFile: Path = outputJar.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
applyAccessTransform(
inputJarPath = inputJar,
outputJarPath = outputJar,
atFilePath = at,
workerExecutor = context.workerExecutor,
launcher = context.defaultJavaLauncher
).await()
}
}

View file

@ -1,99 +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.userdev.internal.setup.step
import codechicken.diffpatch.cli.PatchOperation
import codechicken.diffpatch.util.LogLevel
import codechicken.diffpatch.util.archiver.ArchiveFormat
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder
import io.papermc.paperweight.userdev.internal.setup.util.hashDirectory
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import io.papermc.paperweight.userdev.internal.setup.util.siblingLogFile
import io.papermc.paperweight.util.*
import java.io.PrintStream
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.*
import kotlin.streams.asSequence
class ApplyDevBundlePatches(
@Input private val decompiledJar: Path,
private val devBundlePatches: Path,
@Output private val outputJar: Path,
) : SetupStep {
override val name: String = "apply patches to decompiled jar"
override val hashFile: Path = outputJar.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
val tempPatchDir = findOutputDir(outputJar)
val outputDir = findOutputDir(outputJar)
val log = outputJar.siblingLogFile()
try {
val (patches, newFiles) = Files.walk(devBundlePatches).use { stream ->
stream.asSequence()
.filter { it.isRegularFile() }
.partition { it.name.endsWith(".patch") }
}
for (patch in patches) {
relativeCopy(devBundlePatches, patch, tempPatchDir)
}
ensureDeleted(log)
PrintStream(log.toFile(), Charsets.UTF_8).use { logOut ->
val op = PatchOperation.builder()
.logTo(logOut)
.level(LogLevel.ALL)
.summary(true)
.basePath(decompiledJar, ArchiveFormat.ZIP)
.patchesPath(tempPatchDir)
.outputPath(outputDir)
.build()
try {
op.operate().throwOnError()
} catch (ex: Exception) {
throw PaperweightException(
"Failed to apply dev bundle patches. See the log file at '${log.toFile()}' for more details.",
ex
)
}
}
for (file in newFiles) {
relativeCopy(devBundlePatches, file, outputDir)
}
ensureDeleted(outputJar)
zip(outputDir, outputJar)
} finally {
ensureDeleted(outputDir, tempPatchDir)
}
}
override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {
builder.include(hashDirectory(devBundlePatches))
}
}

View file

@ -1,78 +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.userdev.internal.setup.step
import io.papermc.paperweight.tasks.runDecompiler
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import io.papermc.paperweight.userdev.internal.setup.util.siblingLogFile
import io.papermc.paperweight.util.constants.DECOMPILER_CONFIG
import java.nio.file.Path
import org.gradle.api.artifacts.Configuration
class DecompileMinecraft(
@Input private val inputJar: Path,
@Output private val outputJar: Path,
private val cache: Path,
private val minecraftLibraryJars: () -> List<Path>,
@Input private val decompileArgs: List<String>,
private val decompiler: Configuration,
) : SetupStep {
override val name: String = "decompile transformed minecraft server jar"
override val hashFile: Path = outputJar.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
runDecompiler(
argsList = decompileArgs,
logFile = outputJar.siblingLogFile(),
workingDir = cache,
executable = decompiler,
inputJar = inputJar,
libraries = minecraftLibraryJars(),
outputJar = outputJar,
javaLauncher = context.defaultJavaLauncher
)
}
override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {
builder.include(minecraftLibraryJars())
builder.include(decompiler.map { it.toPath() })
builder.includePaperweightHash = false
}
companion object {
fun create(
context: SetupHandler.Context,
inputJar: Path,
outputJar: Path,
cache: Path,
minecraftLibraryJars: () -> List<Path>,
decompileArgs: List<String>,
): DecompileMinecraft {
val decompiler = context.project.configurations.getByName(DECOMPILER_CONFIG).also { it.resolve() } // resolve decompiler
return DecompileMinecraft(inputJar, outputJar, cache, minecraftLibraryJars, decompileArgs, decompiler)
}
}
}

View file

@ -1,120 +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.userdev.internal.setup.step
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.isRegularFile
import kotlin.io.path.name
import kotlin.io.path.pathString
class FilterPaperShadowJar(
@Input private val sourcesJar: Path,
@Input private val inputJar: Path,
@Output private val outputJar: Path,
private val relocations: List<Relocation>,
) : SetupStep {
override val name: String = "filter mojang mapped paper jar"
override val hashFile: Path = outputJar.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
filterPaperJar(sourcesJar, inputJar, outputJar, relocations)
}
override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {
builder.include(gson.toJson(relocations))
}
private fun filterPaperJar(
sourcesJar: Path,
inputJar: Path,
outputJar: Path,
relocations: List<Relocation>
) {
val includes = arrayListOf<String>()
// Include relocated packages
for (relocation in relocations.map { RelocationWrapper(it) }) {
includes += '/' + relocation.toSlash + "/**"
for (exclude in relocation.relocation.excludes) {
includes += '/' + exclude.replace('.', '/')
}
}
val includedFiles = collectIncludes(sourcesJar, inputJar)
filterJar(
inputJar,
outputJar,
includes
) { path ->
val str = path.pathString
if (str.contains('$')) {
includedFiles.contains(str.split("$")[0] + ".class")
} else {
includedFiles.contains(str)
}
}
}
private fun collectIncludes(
sourcesJar: Path,
inputJar: Path
): Set<String> {
val extraIncludes = hashSetOf<String>()
// Include all files we have sources for
iterateJar(sourcesJar) { entry ->
if (entry.isRegularFile()) {
val string = entry.pathString
extraIncludes += if (string.endsWith(".java")) {
string.substringBeforeLast(".java") + ".class"
} else {
string
}
}
}
// Include non-class resource files from server jar
iterateJar(inputJar) { entry ->
if (entry.isRegularFile() && !entry.name.endsWith(".class")) {
extraIncludes += entry.pathString
}
}
return extraIncludes
}
private fun iterateJar(
jar: Path,
visitor: (Path) -> Unit
) = jar.openZip().use { fs ->
fs.walk().use { stream ->
stream.forEach { path ->
visitor(path)
}
}
}
}

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.userdev.internal.setup.step
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import io.papermc.paperweight.util.*
import java.nio.file.Path
class FilterVanillaJar(
@Input private val vanillaJar: Path,
@Input private val includes: List<String>,
@Output private val outputJar: Path,
) : SetupStep {
override val name: String = "filter vanilla server jar"
override val hashFile: Path = outputJar.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
filterJar(vanillaJar, outputJar, includes)
}
}

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.userdev.internal.setup.step
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import java.nio.file.Path
class FixMinecraftJar(
@Input private val inputJar: Path,
@Output private val outputJar: Path,
@Input private val vanillaServerJar: Path,
private val useLegacyParameterAnnotationFixer: Boolean = false,
) : SetupStep {
override val name: String = "fix minecraft server jar"
override val hashFile: Path = outputJar.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
fixJar(
workerExecutor = context.workerExecutor,
launcher = context.defaultJavaLauncher,
vanillaJarPath = vanillaServerJar,
inputJarPath = inputJar,
outputJarPath = outputJar,
useLegacyParameterAnnotationFixer = useLegacyParameterAnnotationFixer
).await()
}
}

View file

@ -1,77 +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.userdev.internal.setup.step
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
class GenerateMappingsStep(
private val vanillaSteps: VanillaSteps,
@Input private val filteredVanillaJar: Path,
@Input private val paramMappings: Path,
private val minecraftLibraryJars: () -> List<Path>,
@Output private val outputMappings: Path,
) : SetupStep {
override val name: String = "generate mappings"
override val hashFile: Path = outputMappings.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
generateMappings(
vanillaJarPath = filteredVanillaJar,
libraryPaths = minecraftLibraryJars(),
vanillaMappingsPath = vanillaSteps.serverMappings,
paramMappingsPath = paramMappings,
outputMappingsPath = outputMappings,
workerExecutor = context.workerExecutor,
launcher = context.defaultJavaLauncher
).await()
}
override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {
builder.include(minecraftLibraryJars())
builder.include(vanillaSteps.serverMappings)
}
companion object {
fun create(
context: SetupHandler.Context,
vanillaSteps: VanillaSteps,
filteredVanillaJar: Path,
minecraftLibraryJars: () -> List<Path>,
outputMappings: Path,
): GenerateMappingsStep {
vanillaSteps.downloadServerMappings()
// resolve param mappings
val paramMappings = context.project.configurations.named(PARAM_MAPPINGS_CONFIG).map { it.singleFile }.convertToPath()
return GenerateMappingsStep(vanillaSteps, filteredVanillaJar, paramMappings, minecraftLibraryJars, outputMappings)
}
}
}

View file

@ -1,49 +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.userdev.internal.setup.step
import io.papermc.paperweight.userdev.internal.setup.UserdevSetup
import io.papermc.paperweight.util.constants.IVY_REPOSITORY
import io.papermc.paperweight.util.installToIvyRepo
import java.nio.file.Path
fun installPaperServer(
cache: Path,
mappedServerCoordinates: String,
dependencies: List<String>,
serverJar: Path,
serverSourcesJar: Path?,
mcVersion: String,
) {
val didInstall = installToIvyRepo(
cache.resolve(IVY_REPOSITORY),
mappedServerCoordinates,
dependencies,
serverJar,
serverSourcesJar,
)
if (didInstall) {
UserdevSetup.LOGGER.lifecycle(":installed server artifacts to cache")
UserdevSetup.LOGGER.lifecycle(":done setting up paperweight userdev workspace for minecraft {}", mcVersion)
}
}

View file

@ -1,91 +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.userdev.internal.setup.step
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import io.papermc.paperweight.userdev.internal.setup.util.siblingLogFile
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import org.gradle.api.artifacts.Configuration
class RemapMinecraft(
@Input private val minecraftRemapArgs: List<String>,
@Input private val filteredVanillaJar: Path,
private val minecraftLibraryJars: () -> List<Path>,
@Input private val mappings: Path,
private val remapper: Configuration,
@Output private val outputJar: Path,
private val cache: Path,
) : SetupStep {
override val name: String = "remap minecraft server jar"
override val hashFile: Path = outputJar.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
TinyRemapper.run(
argsList = minecraftRemapArgs,
logFile = outputJar.siblingLogFile(),
inputJar = filteredVanillaJar,
mappingsFile = mappings,
fromNamespace = OBF_NAMESPACE,
toNamespace = DEOBF_NAMESPACE,
remapClasspath = minecraftLibraryJars(),
remapper = remapper,
outputJar = outputJar,
launcher = context.defaultJavaLauncher,
workingDir = cache
)
}
override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {
builder.includePaperweightHash = false
builder.include(minecraftLibraryJars())
builder.include(remapper.map { it.toPath() })
}
companion object {
fun create(
context: SetupHandler.Context,
minecraftRemapArgs: List<String>,
filteredVanillaJar: Path,
minecraftLibraryJars: () -> List<Path>,
mappings: Path,
outputJar: Path,
cache: Path,
): RemapMinecraft {
val remapper = context.project.configurations.getByName(REMAPPER_CONFIG).also { it.resolve() } // resolve remapper
return RemapMinecraft(
minecraftRemapArgs,
filteredVanillaJar,
minecraftLibraryJars,
mappings,
remapper,
outputJar,
cache,
)
}
}
}

View file

@ -1,112 +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.userdev.internal.setup
import com.google.gson.JsonObject
import io.papermc.paperweight.userdev.internal.setup.step.Input
import io.papermc.paperweight.userdev.internal.setup.step.Output
import io.papermc.paperweight.userdev.internal.setup.step.SetupStep
import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder
import io.papermc.paperweight.userdev.internal.setup.util.siblingHashesFile
import io.papermc.paperweight.userdev.internal.setup.util.siblingLogFile
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.data.*
import java.nio.file.Path
import kotlin.io.path.*
class RunPaperclip(
@Input private val paperclip: Path,
@Output private val outputJar: Path,
@Input private val mojangJar: Path,
@Input private val minecraftVersion: String,
private val bundler: Boolean = true,
) : SetupStep {
override val name: String = "apply mojang mapped paperclip patch"
override val hashFile: Path = outputJar.siblingHashesFile()
override fun run(context: SetupHandler.Context) {
patchPaperclip(context, paperclip, outputJar, mojangJar, minecraftVersion, bundler)
}
override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {
builder.includePaperweightHash = false
}
private fun patchPaperclip(
context: SetupHandler.Context,
paperclip: Path,
outputJar: Path,
mojangJar: Path,
minecraftVersion: String,
bundler: Boolean,
) {
val logFile = outputJar.siblingLogFile()
val work = createTempDirectory()
ensureDeleted(logFile)
// Copy in mojang jar, so we don't download it twice
val cache = work.resolve("cache")
cache.createDirectories()
mojangJar.copyTo(cache.resolve("mojang_$minecraftVersion.jar"))
context.defaultJavaLauncher.runJar(
classpath = context.project.files(paperclip),
workingDir = work,
logFile = logFile,
jvmArgs = listOf("-Dpaperclip.patchonly=true"),
args = arrayOf()
)
if (bundler) {
handleBundler(paperclip, work, outputJar)
} else {
handleOldPaperclip(work, outputJar)
}
ensureDeleted(work)
}
private fun handleBundler(paperclip: Path, work: Path, outputJar: Path) {
paperclip.openZip().use { fs ->
val root = fs.rootDirectories.single()
val serverVersionJson = root.resolve(FileEntry.VERSION_JSON)
val versionId = gson.fromJson<JsonObject>(serverVersionJson)["id"].asString
val versions = root.resolve(FileEntry.VERSIONS_LIST).readLines()
.map { it.split('\t') }
.associate { it[1] to it[2] }
val serverJarPath = work.resolve("versions/${versions[versionId]}")
outputJar.parent.createDirectories()
serverJarPath.copyTo(outputJar, overwrite = true)
}
}
private fun handleOldPaperclip(work: Path, outputJar: Path) {
val patched = work.resolve("cache").listDirectoryEntries()
.find { it.name.startsWith("patched") } ?: error("Can't find patched jar!")
patched.copyTo(outputJar, overwrite = true)
}
}

View file

@ -1,78 +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.userdev.internal.setup.step
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.userdev.internal.setup.util.*
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.kotlin.dsl.*
class VanillaSteps(
private val minecraftVersion: String,
private val cache: Path,
private val downloadService: DownloadService,
private val bundleChanged: Boolean,
) {
private val versionManifest: MinecraftVersionManifest by lazy { setupMinecraftVersionManifest() }
val mojangJar: Path = cache.resolve(paperSetupOutput("downloadServerJar", "jar"))
val serverMappings: Path = cache.resolve(SERVER_MAPPINGS)
fun downloadVanillaServerJar(): DownloadResult<Unit> = downloadService.download(
"vanilla minecraft server jar",
versionManifest.serverDownload().url,
mojangJar,
expectedHash = versionManifest.serverDownload().hash()
)
fun downloadServerMappings(): DownloadResult<Unit> = downloadService.download(
"mojang server mappings",
versionManifest.serverMappingsDownload().url,
serverMappings,
expectedHash = versionManifest.serverMappingsDownload().hash()
)
private fun downloadMinecraftManifest(force: Boolean): DownloadResult<MinecraftManifest> =
downloadService.download("minecraft manifest", MC_MANIFEST_URL, cache.resolve(MC_MANIFEST), force)
.mapData { gson.fromJson(it.path) }
private fun setupMinecraftVersionManifest(): MinecraftVersionManifest {
var minecraftManifest = downloadMinecraftManifest(bundleChanged)
if (!minecraftManifest.didDownload && minecraftManifest.data.versions.none { it.id == minecraftVersion }) {
minecraftManifest = downloadMinecraftManifest(true)
}
val ver = minecraftManifest.data.versions.firstOrNull { it.id == minecraftVersion }
?: throw PaperweightException("Could not find Minecraft version '$minecraftVersion' in the downloaded manifest.")
val minecraftVersionManifestJson = downloadService.download(
"minecraft version manifest",
ver.url,
cache.resolve(VERSION_JSON),
expectedHash = ver.hash()
)
return gson.fromJson(minecraftVersionManifestJson.path)
}
}

View file

@ -1,137 +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.userdev.internal.setup.step
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.UserdevSetup
import io.papermc.paperweight.userdev.internal.setup.util.HashFunction
import io.papermc.paperweight.userdev.internal.setup.util.HashFunctionBuilder
import io.papermc.paperweight.userdev.internal.setup.util.buildHashFunction
import java.nio.file.Path
import java.util.concurrent.ConcurrentHashMap
import kotlin.io.path.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.system.measureTimeMillis
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Input
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class Output
interface SetupStep {
val name: String
val hashFile: Path
fun run(context: SetupHandler.Context)
fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {}
}
object StepExecutor {
private data class InputOutputData(
val inputs: List<KProperty1<Any, *>>,
val outputs: List<KProperty1<Any, *>>,
)
private val inputOutputDataCache: MutableMap<KClass<out SetupStep>, InputOutputData> =
ConcurrentHashMap<KClass<out SetupStep>, InputOutputData>()
fun executeSteps(expectingChange: Boolean, context: SetupHandler.Context, vararg steps: SetupStep) {
// if we aren't expecting change, assume the last step is the output that matters
// and only verify its inputs/outputs - if it fails then we need to go back through
// and check each step anyway
if (!expectingChange) {
val lastStep = steps.last()
if (makeHashFunction(lastStep).upToDate(lastStep.hashFile)) {
return
}
}
try {
for (step in steps) {
executeStep(context, step)
}
} catch (ex: Exception) {
for (step in steps) {
step.hashFile.deleteIfExists()
}
throw PaperweightException("Failed to execute steps, invalidated relevant caches. Run with --stacktrace for more details.", ex)
}
}
fun executeStep(context: SetupHandler.Context, step: SetupStep) {
val hashFunction = makeHashFunction(step)
if (hashFunction.upToDate(step.hashFile)) {
return
}
UserdevSetup.LOGGER.lifecycle(":executing '{}'", step.name)
val elapsed = measureTimeMillis {
step.run(context)
}
UserdevSetup.LOGGER.info("done executing '{}', took {}s", step.name, elapsed / 1000.00)
hashFunction.writeHash(step.hashFile)
}
private fun makeHashFunction(step: SetupStep): HashFunction {
val data = step.inputOutputData
val inputs = data.inputs.mapNotNull { it.get(step) }
val outputs = data.outputs.mapNotNull { it.get(step) }
return buildHashFunction(inputs, outputs) {
step.touchHashFunctionBuilder(this)
}
}
private val SetupStep.inputOutputData: InputOutputData
get() = inputOutputDataCache.computeIfAbsent(this::class) {
InputOutputData(
it.collectAnnotatedDeclaredProperties<Input>(),
it.collectAnnotatedDeclaredProperties<Output>(),
)
}
private inline fun <reified A : Annotation> KClass<*>.collectAnnotatedDeclaredProperties() =
collectDeclaredProperties {
it.annotations.any { a -> a is A }
}
@Suppress("unchecked_cast")
private fun KClass<*>.collectDeclaredProperties(
filter: (KProperty1<*, *>) -> Boolean
): List<KProperty1<Any, *>> =
declaredMemberProperties.filter(filter).map {
it.isAccessible = true
it as KProperty1<Any, *>
}
}

View file

@ -1,236 +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.userdev.internal.setup.util
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.userdev.PaperweightUser
import io.papermc.paperweight.userdev.internal.setup.UserdevSetup
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Duration
import java.util.stream.Collectors
import kotlin.io.path.*
import kotlin.streams.asSequence
import kotlin.system.measureTimeMillis
import org.gradle.api.Project
import org.gradle.api.provider.Provider
val paperweightHash: String by lazy { hashPaperweightJar() }
fun Path.siblingLogFile(): Path = withDifferentExtension("log")
fun Path.siblingHashesFile(): Path = withDifferentExtension("hashes")
fun Path.siblingLogAndHashesFiles() = Pair(siblingLogFile(), siblingHashesFile())
fun hash(vararg things: Any): String = hash(things.toList())
fun hash(things: List<Any>): String {
val strings = arrayListOf<String>()
val paths = arrayListOf<Path>()
for (thing in things) {
when (thing) {
is String -> strings.add(thing)
is Path -> paths.add(thing)
is Iterable<*> -> strings.add(hash(thing.filterNotNull()))
else -> error("Unknown type: ${thing.javaClass.name}")
}
}
return hashFiles(paths) + if (strings.isEmpty()) {
""
} else {
"\n" + strings.sorted().joinToString("\n") { it.hash(HashingAlgorithm.SHA256).asHexString() }
}
}
fun hashFiles(files: List<Path>): String = files.asSequence()
.filter { it.isRegularFile() }
.sortedBy { it.pathString }
.joinToString("\n") {
"${it.fileName.pathString}:${it.sha256asHex()}"
}
fun hashDirectory(dir: Path): String = Files.walk(dir).use { stream -> hashFiles(stream.filter { it.isRegularFile() }.collect(Collectors.toList())) }
fun DownloadService.download(
downloadName: String,
remote: String,
destination: Path,
forceDownload: Boolean = false,
expectedHash: Hash? = null
): DownloadResult<Unit> {
val hashFile = destination.siblingHashesFile()
val toHash = mutableListOf<Any>(remote, destination)
expectedHash?.let { toHash += it.valueLower }
val upToDate = !forceDownload &&
hashFile.isRegularFile() &&
hashFile.readText() == hash(toHash)
if (upToDate) {
return DownloadResult(destination, false, Unit)
}
UserdevSetup.LOGGER.lifecycle(":executing 'download {}'", downloadName)
val elapsed = measureTimeMillis {
destination.parent.createDirectories()
destination.deleteIfExists()
download(remote, destination, expectedHash)
}
UserdevSetup.LOGGER.info("done executing 'download {}', took {}s", downloadName, elapsed / 1000.00)
hashFile.writeText(hash(toHash))
return DownloadResult(destination, true, Unit)
}
fun buildHashFunction(vararg things: Any, op: HashFunctionBuilder.() -> Unit = {}): HashFunction = HashFunction {
val builder = HashFunctionBuilder.create()
builder.op()
if (builder.includePaperweightHash) {
builder.include(paperweightHash)
}
builder.include(*things)
hash(builder)
}
data class DownloadResult<D>(val path: Path, val didDownload: Boolean, val data: D) {
fun <N> mapData(mapper: (DownloadResult<D>) -> N): DownloadResult<N> = DownloadResult(path, didDownload, mapper(this))
}
interface HashFunctionBuilder : MutableList<Any> {
var includePaperweightHash: Boolean
fun include(vararg things: Any): Boolean = addAll(things.toList())
fun include(thing: Any): Boolean = add(thing)
companion object {
fun create(): HashFunctionBuilder = HashFunctionBuilderImpl()
}
private class HashFunctionBuilderImpl(
override var includePaperweightHash: Boolean = true,
) : HashFunctionBuilder, MutableList<Any> by ArrayList()
}
fun interface HashFunction : () -> String {
fun writeHash(hashFile: Path) {
hashFile.parent.createDirectories()
hashFile.writeText(this())
}
fun upToDate(hashFile: Path): Boolean {
return hashFile.isRegularFile() && hashFile.readText() == this()
}
}
private fun hashPaperweightJar(): String {
val userdevShadowJar = Paths.get(PaperweightUser::class.java.protectionDomain.codeSource.location.toURI())
return userdevShadowJar.sha256asHex()
}
fun <R> lockSetup(cache: Path, canBeNested: Boolean = false, action: () -> R): R {
val lockFile = cache.resolve(USERDEV_SETUP_LOCK)
val alreadyHad = acquireProcessLockWaiting(lockFile)
try {
return action()
} finally {
if (!canBeNested || !alreadyHad) {
lockFile.deleteForcefully()
}
}
}
// set by most CI
val Project.ci: Provider<Boolean>
get() = providers.environmentVariable("CI")
.map { it.toBoolean() }
.orElse(false)
private fun stableProp(name: String) = "paperweight.$name"
private fun experimentalProp(name: String) = "paperweight.experimental.$name"
val Project.genSources: Boolean
get() {
val ci = ci.get()
val prop = providers.gradleProperty(experimentalProp("genSources")).orNull?.toBoolean()
return prop ?: !ci
}
val Project.sharedCaches: Boolean
get() = providers.gradleProperty(stableProp("sharedCaches"))
.map { it.toBoolean() }
.orElse(true)
.get()
private fun deleteUnusedAfter(target: Project): Long = target.providers.gradleProperty(stableProp("sharedCaches.deleteUnusedAfter"))
.map { value -> parseDuration(value) }
.orElse(Duration.ofDays(7))
.map { duration -> duration.toMillis() }
.get()
fun lastUsedFile(cacheDir: Path): Path = cacheDir.resolve(paperSetupOutput("last-used", "txt"))
fun cleanSharedCaches(target: Project, root: Path) {
if (!root.exists()) {
return
}
val toDelete = Files.walk(root).use { stream ->
stream.asSequence()
.filter { it.name == "last-used.txt" }
.mapNotNull {
val pwDir = it.parent.parent // paperweight dir
val cacheDir = pwDir.parent // cache dir
val lock = cacheDir.resolve(USERDEV_SETUP_LOCK)
if (lock.exists() && ProcessHandle.of(lock.readText().toLong()).isPresent) {
return@mapNotNull null
}
val lastUsed = it.readText().toLong()
val since = System.currentTimeMillis() - lastUsed
val cutoff = deleteUnusedAfter(target)
if (since > cutoff) pwDir else null
}
.toList()
}
for (path in toDelete) {
path.deleteRecursive()
// clean up empty parent directories
var parent: Path = path.parent
while (true) {
val entries = parent.listDirectoryEntries()
if (entries.isEmpty()) {
parent.deleteIfExists()
} else {
break
}
parent = parent.parent
}
}
}

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.userdev.internal.setup.v2
import io.papermc.paperweight.util.*
object DevBundleV2 {
data class Config(
val minecraftVersion: String,
val mappedServerCoordinates: String,
val apiCoordinates: String,
val mojangApiCoordinates: String,
val buildData: BuildData,
val decompile: Runner,
val remap: Runner,
val patchDir: String
)
data class BuildData(
val paramMappings: MavenDep,
val reobfMappingsFile: String,
val accessTransformFile: String,
val mojangMappedPaperclipFile: String,
val vanillaJarIncludes: List<String>,
val vanillaServerLibraries: List<String>,
val libraryDependencies: Set<String>,
val libraryRepositories: List<String>,
val relocations: List<Relocation>
)
data class Runner(val dep: MavenDep, val args: List<String>)
}

View file

@ -1,263 +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.userdev.internal.setup.v2
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.userdev.internal.setup.ExtractedBundle
import io.papermc.paperweight.userdev.internal.setup.RunPaperclip
import io.papermc.paperweight.userdev.internal.setup.SetupHandler
import io.papermc.paperweight.userdev.internal.setup.UserdevSetup
import io.papermc.paperweight.userdev.internal.setup.step.*
import io.papermc.paperweight.userdev.internal.setup.util.*
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.artifacts.DependencySet
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
class SetupHandlerImplV2(
private val parameters: UserdevSetup.Parameters,
private val bundle: ExtractedBundle<DevBundleV2.Config>,
private val cache: Path = parameters.cache.path,
) : SetupHandler {
private val vanillaSteps by lazy {
VanillaSteps(
bundle.config.minecraftVersion,
cache,
parameters.downloadService.get(),
bundle.changed,
)
}
private val filteredVanillaServerJar: Path = cache.resolve(paperSetupOutput("filterJar", "jar"))
private val minecraftLibraryJars = cache.resolve(MINECRAFT_JARS_PATH)
private val mojangPlusYarnMappings: Path = cache.resolve(MOJANG_YARN_MAPPINGS)
private val mappedMinecraftServerJar: Path = cache.resolve(paperSetupOutput("mappedMinecraftServerJar", "jar"))
private val fixedMinecraftServerJar: Path = cache.resolve(paperSetupOutput("fixedMinecraftServerJar", "jar"))
private val accessTransformedServerJar: Path = cache.resolve(paperSetupOutput("accessTransformedServerJar", "jar"))
private val decompiledMinecraftServerJar: Path = cache.resolve(paperSetupOutput("decompileMinecraftServerJar", "jar"))
private val patchedSourcesJar: Path = cache.resolve(paperSetupOutput("patchedSourcesJar", "jar"))
private val mojangMappedPaperJar: Path = cache.resolve(paperSetupOutput("applyMojangMappedPaperclipPatch", "jar"))
private fun minecraftLibraryJars(): List<Path> = minecraftLibraryJars.listDirectoryEntries("*.jar")
private fun generateSources(context: SetupHandler.Context) {
vanillaSteps.downloadVanillaServerJar()
val downloadMcLibs = object : SetupStep {
override val name: String = "download minecraft libraries"
override val hashFile: Path = cache.resolve(paperSetupOutput("minecraftLibraries", "hashes"))
override fun run(context: SetupHandler.Context) {
downloadMinecraftLibraries(
download = parameters.downloadService,
workerExecutor = context.workerExecutor,
targetDir = minecraftLibraryJars,
repositories = listOf(MC_LIBRARY_URL, MAVEN_CENTRAL_URL),
mcLibraries = bundle.config.buildData.vanillaServerLibraries,
sources = false
).await()
}
override fun touchHashFunctionBuilder(builder: HashFunctionBuilder) {
builder.include(MC_LIBRARY_URL)
builder.include(bundle.config.buildData.vanillaServerLibraries)
builder.include(minecraftLibraryJars())
builder.includePaperweightHash = false
}
}
val filterVanillaJarStep = FilterVanillaJar(vanillaSteps.mojangJar, bundle.config.buildData.vanillaJarIncludes, filteredVanillaServerJar)
val genMappingsStep = GenerateMappingsStep.create(
context,
vanillaSteps,
filteredVanillaServerJar,
::minecraftLibraryJars,
mojangPlusYarnMappings,
)
val remapMinecraftStep = RemapMinecraft.create(
context,
bundle.config.remap.args,
filteredVanillaServerJar,
::minecraftLibraryJars,
mojangPlusYarnMappings,
mappedMinecraftServerJar,
cache,
)
val fixStep = FixMinecraftJar(
mappedMinecraftServerJar,
fixedMinecraftServerJar,
vanillaSteps.mojangJar,
true,
)
val atStep = AccessTransformMinecraft(
bundle.dir.resolve(bundle.config.buildData.accessTransformFile),
fixedMinecraftServerJar,
accessTransformedServerJar,
)
val decomp = DecompileMinecraft.create(
context,
accessTransformedServerJar,
decompiledMinecraftServerJar,
cache,
::minecraftLibraryJars,
bundle.config.decompile.args,
)
val applyDevBundlePatchesStep = ApplyDevBundlePatches(
decompiledMinecraftServerJar,
bundle.dir.resolve(bundle.config.patchDir),
patchedSourcesJar
)
StepExecutor.executeSteps(
bundle.changed,
context,
downloadMcLibs,
filterVanillaJarStep,
genMappingsStep,
remapMinecraftStep,
fixStep,
atStep,
decomp,
applyDevBundlePatchesStep,
)
applyMojangMappedPaperclipPatch(context)
StepExecutor.executeStep(
context,
FilterPaperShadowJar(
patchedSourcesJar,
mojangMappedPaperJar,
filteredMojangMappedPaperJar,
bundle.config.buildData.relocations,
)
)
}
// This can be called when a user queries the server jar provider in
// PaperweightUserExtension, possibly by a task running in a separate
// thread to dependency resolution.
@Synchronized
private fun applyMojangMappedPaperclipPatch(context: SetupHandler.Context) {
if (setupCompleted) {
return
}
lockSetup(cache, true) {
StepExecutor.executeStep(
context,
RunPaperclip(
bundle.dir.resolve(bundle.config.buildData.mojangMappedPaperclipFile),
mojangMappedPaperJar,
vanillaSteps.mojangJar,
minecraftVersion,
false,
)
)
}
}
private val filteredMojangMappedPaperJar: Path = cache.resolve(paperSetupOutput("filteredMojangMappedPaperJar", "jar"))
private var setupCompleted = false
@Synchronized
override fun createOrUpdateIvyRepository(context: SetupHandler.Context) {
if (setupCompleted) {
return
}
lockSetup(cache) {
createOrUpdateIvyRepositoryDirect(context)
}
}
private fun createOrUpdateIvyRepositoryDirect(context: SetupHandler.Context) {
generateSources(context)
val deps = bundle.config.buildData.libraryDependencies.toList() +
bundle.config.apiCoordinates +
bundle.config.mojangApiCoordinates
installPaperServer(
cache,
bundle.config.mappedServerCoordinates,
deps,
filteredMojangMappedPaperJar,
patchedSourcesJar,
minecraftVersion,
)
setupCompleted = true
}
override fun configureIvyRepo(repo: IvyArtifactRepository) {
repo.content {
includeFromDependencyNotation(bundle.config.mappedServerCoordinates)
}
}
override fun populateCompileConfiguration(context: SetupHandler.Context, dependencySet: DependencySet) {
dependencySet.add(context.project.dependencies.create(bundle.config.mappedServerCoordinates))
}
override fun populateRuntimeConfiguration(context: SetupHandler.Context, dependencySet: DependencySet) {
dependencySet.add(context.project.dependencies.create(context.project.files(serverJar(context))))
}
override fun serverJar(context: SetupHandler.Context): Path {
applyMojangMappedPaperclipPatch(context)
return mojangMappedPaperJar
}
override val serverJar: Path
get() = mojangMappedPaperJar
override val reobfMappings: Path
get() = bundle.dir.resolve(bundle.config.buildData.reobfMappingsFile)
override val minecraftVersion: String
get() = bundle.config.minecraftVersion
override val pluginRemapArgs: List<String>
get() = TinyRemapper.pluginRemapArgs // plugin remap args were not included in v2 bundles, if these change check this
override val paramMappings: MavenDep
get() = bundle.config.buildData.paramMappings
override val decompiler: MavenDep
get() = bundle.config.decompile.dep
override val remapper: MavenDep
get() = bundle.config.remap.dep
override val libraryRepositories: List<String>
get() = bundle.config.buildData.libraryRepositories
}

View file

@ -0,0 +1,10 @@
plugins {
`config-kotlin`
`config-publish`
}
dependencies {
shade(projects.paperweightLib)
implementation(libs.kotson)
}

View file

@ -0,0 +1,160 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2021 Kyle Wood (DemonWav)
* 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.PaperweightCoreUpstreamData
import io.papermc.paperweight.tasks.GeneratePaperclipPatch
import io.papermc.paperweight.tasks.patchremap.RemapPatches
import io.papermc.paperweight.util.Constants
import io.papermc.paperweight.util.cache
import io.papermc.paperweight.util.initSubmodules
import io.papermc.paperweight.util.registering
import io.papermc.paperweight.util.setupServerProject
import java.io.File
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.gradle.kotlin.dsl.*
class PaperweightCore : Plugin<Project> {
override fun apply(target: Project) {
target.extensions.create(Constants.EXTENSION, PaperweightCoreExtension::class)
target.gradle.sharedServices.registerIfAbsent("download", 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.initSubmodules()
target.configurations.create(Constants.PARAM_MAPPINGS_CONFIG)
target.configurations.create(Constants.REMAPPER_CONFIG)
target.configurations.create(Constants.DECOMPILER_CONFIG)
target.configurations.create(Constants.PAPERCLIP_CONFIG)
val tasks = AllTasks(target)
target.createPatchRemapTask(tasks)
target.tasks.register<PaperweightCoreUpstreamData>(Constants.PAPERWEIGHT_PREPARE_DOWNSTREAM) {
dependsOn(tasks.applyPatches)
vanillaJar.set(tasks.downloadServerJar.flatMap { it.outputJar })
remappedJar.set(tasks.copyResources.flatMap { it.outputJar })
mcVersion.set(target.ext.minecraftVersion)
mcLibrariesFile.set(tasks.inspectVanillaJar.flatMap { it.serverLibraries })
mcLibrariesDir.set(tasks.downloadMcLibraries.flatMap { it.sourcesOutputDir })
reobfMappings.set(tasks.generateReobfMappings.flatMap { it.reobfMappings })
dataFile.set(target.layout.file(providers.gradleProperty(Constants.PAPERWEIGHT_PREPARE_DOWNSTREAM).map { File(it) }))
}
target.afterEvaluate {
// Setup the server jar
val cache = target.layout.cache
val serverProj = target.ext.serverProject.forUseAtConfigurationTime().orNull ?: return@afterEvaluate
val reobfJar = serverProj.setupServerProject(
target,
cache.resolve(Constants.FINAL_REMAPPED_JAR),
cache.resolve(Constants.SERVER_LIBRARIES),
) {
mappingsFile.set(tasks.generateReobfMappings.flatMap { it.reobfMappings })
} ?: return@afterEvaluate
val generatePaperclipPatch by target.tasks.registering<GeneratePaperclipPatch> {
originalJar.set(tasks.downloadServerJar.flatMap { it.outputJar })
patchedJar.set(reobfJar.flatMap { it.outputJar })
mcVersion.set(target.ext.minecraftVersion)
}
@Suppress("UNUSED_VARIABLE")
val paperclipJar by target.tasks.registering<Jar> {
with(target.tasks.named("jar", Jar::class).get())
val paperclipConfig = target.configurations.named(Constants.PAPERCLIP_CONFIG)
dependsOn(paperclipConfig, generatePaperclipPatch)
val paperclipZip = target.zipTree(paperclipConfig.map { it.singleFile })
from(paperclipZip) {
exclude("META-INF/MANIFEST.MF")
}
from(target.zipTree(generatePaperclipPatch.flatMap { it.outputZip }.get()))
manifest.from(paperclipZip.matching { include("META-INF/MANIFEST.MF") }.singleFile)
}
}
}
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 = "paper"
description = "EXPERIMENTAL & BROKEN: Attempt to remap Paper's patches from Spigot mappings to SRG."
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.downloadServerJar.flatMap { it.outputJar }.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.downloadMcLibraries.flatMap { it.sourcesOutputDir }.get())
libraryImports.set(extension.paper.libraryClassImports)
outputPatchDir.set(extension.paper.remappedSpigotServerPatchDir)
}
}
}

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