Compare commits

...

5 commits

Author SHA1 Message Date
MiniDigger | Martin
aa75f2bbcf maybe fix tests 2024-06-16 20:24:55 +02:00
MiniDigger | Martin
005e051b3c reports pls 2024-06-16 20:16:21 +02:00
MiniDigger | Martin
d569f38cef more options 2024-06-16 19:11:08 +02:00
MiniDigger | Martin
4564fe8a2d test report 2024-06-16 19:02:31 +02:00
MiniDigger | Martin
2d425efdef paperweight v2 2024-06-15 10:28:23 +02:00
100 changed files with 3346 additions and 76 deletions

View file

@ -83,3 +83,9 @@ 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
[*.patch]
trim_trailing_whitespace=false
[*.patch]
trim_trailing_whitespace=false

View file

@ -1,7 +1,7 @@
name: Deploy Snapshot
on:
push:
branches: [ 'main' ]
branches: [ 'main', 'softspoon-v2' ]
paths-ignore:
- 'license/*'
- 'readme.md'
@ -29,7 +29,7 @@ jobs:
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
run: ./gradlew -Dorg.gradle.parallel=true publish --no-daemon --stacktrace -Dorg.gradle.internal.http.socketTimeout=90000 -Dorg.gradle.internal.http.connectionTimeout=90000
env:
ORG_GRADLE_PROJECT_paperUsername: ${{ secrets.DEPLOY_USER }}
ORG_GRADLE_PROJECT_paperPassword: ${{ secrets.DEPLOY_PASS }}

View file

@ -19,4 +19,14 @@ jobs:
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Execute Gradle build
run: ./gradlew build --no-daemon --stacktrace
run: |
git config --global user.email "no-reply@github.com"
git config --global user.name "GitHub Actions"
./gradlew build --no-daemon --stacktrace
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: always()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
detailed_summary: true
annotate_notice: true

5
.gitignore vendored
View file

@ -43,3 +43,8 @@ ehthumbs_vista.db
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.ja
/test/
/testpaper/
/testfork/
/testhistory/

View file

@ -11,7 +11,7 @@ java {
}
tasks.withType(JavaCompile::class).configureEach {
options.release = 11
options.release = 17
}
kotlin {
@ -21,14 +21,15 @@ kotlin {
target {
compilations.configureEach {
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs = listOf("-Xjvm-default=all", "-Xjdk-release=11")
jvmTarget = "17"
freeCompilerArgs = listOf("-Xjvm-default=all", "-Xjdk-release=17", "-opt-in=kotlin.io.path.ExperimentalPathApi")
}
}
}
}
repositories {
mavenLocal() // TODO remove again
maven("https://repo.papermc.io/repository/maven-snapshots/") {
mavenContent {
includeModule("org.cadixdev", "mercury")
@ -38,6 +39,28 @@ repositories {
mavenContent {
includeGroup("codechicken")
includeGroup("net.fabricmc")
includeGroupAndSubgroups("io.papermc")
}
}
maven("https://maven.parchmentmc.org") {
name = "ParchmentMC"
mavenContent {
releasesOnly()
includeGroupAndSubgroups("org.parchmentmc")
}
}
maven("https://maven.neoforged.net/releases") {
name = "NeoForged"
mavenContent {
releasesOnly()
includeGroupAndSubgroups("net.neoforged")
}
}
maven("https://maven.fabricmc.net") {
name = "FabricMC"
mavenContent {
releasesOnly()
includeGroupAndSubgroups("net.fabricmc")
}
}
mavenCentral()
@ -55,6 +78,7 @@ testing {
useKotlinTest(embeddedKotlinVersion)
dependencies {
implementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
implementation("org.junit.platform:junit-platform-launcher:1.10.1")
}
}
}

View file

@ -1,5 +1,5 @@
group = io.papermc.paperweight
version = 1.7.2-SNAPSHOT
version = 2.0.0-SNAPSHOT
org.gradle.caching = true
org.gradle.parallel = true

View file

@ -1,7 +1,9 @@
[versions]
asm = "9.7"
lorenz = "0.5.8"
hypo = "1.2.4"
hypo = "2.3.0"
serialize = "1.5.1"
feather = "1.1.0"
[libraries]
asm-core = { module = "org.ow2.asm:asm", version.ref = "asm" }
@ -9,6 +11,8 @@ asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" }
httpclient = "org.apache.httpcomponents:httpclient:4.5.14"
kotson = "com.github.salomonbrys.kotson:kotson:2.5.0"
coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.2"
jgit = "org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r"
gson = "com.google.code.gson:gson:2.10.1"
cadix-lorenz-core = { module = "org.cadixdev:lorenz", version.ref = "lorenz" }
@ -16,7 +20,9 @@ cadix-lorenz-asm = { module = "org.cadixdev:lorenz-asm", version.ref = "lorenz"
cadix-lorenz-proguard = { module = "org.cadixdev:lorenz-io-proguard", version.ref = "lorenz" }
cadix-atlas = "org.cadixdev:atlas:0.2.1"
cadix-at = "org.cadixdev:at:0.1.0-rc1"
#cadix-mercury = "org.cadixdev:mercury:0.1.2-paperweight-local-SNAPSHOT" # todo local mods for patch remapping
cadix-mercury = "org.cadixdev:mercury:0.1.2-paperweight-SNAPSHOT"
cadix-bombe-jar = "org.cadixdev:bombe-jar:0.4.4"
hypo-model = { module = "dev.denwav.hypo:hypo-model", version.ref = "hypo" }
hypo-core = { module = "dev.denwav.hypo:hypo-core", version.ref = "hypo" }
@ -29,7 +35,18 @@ slf4j-jdk14 = "org.slf4j:slf4j-jdk14:1.7.32"
lorenzTiny = "net.fabricmc:lorenz-tiny:3.0.0"
jbsdiff = "io.sigpipe:jbsdiff:1.0"
diffpatch = "codechicken:DiffPatch:1.5.0.29"
feather-core = { module = "org.parchmentmc:feather", version.ref = "feather" }
feather-gson = { module = "org.parchmentmc.feather:io-gson", version.ref = "feather" }
diffpatch = "codechicken:DiffPatch:1.5.0.30"
serialize-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialize" }
serialize-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialize" }
restamp = "io.papermc.restamp:restamp:1.1.1-SNAPSHOT"
# test
mockk = "io.mockk:mockk:1.13.8"
# Gradle
gradle-licenser = "net.kyori:indra-licenser-spotless:3.1.3"
@ -41,6 +58,6 @@ gradle-plugin-publish = "com.gradle.publish:plugin-publish-plugin:1.2.1"
[bundles]
asm = ["asm-core", "asm-tree"]
cadix = ["cadix-lorenz-core", "cadix-lorenz-asm", "cadix-lorenz-proguard", "cadix-atlas", "cadix-at", "cadix-mercury"]
cadix = ["cadix-lorenz-core", "cadix-lorenz-asm", "cadix-lorenz-proguard", "cadix-atlas", "cadix-at", "cadix-mercury", "cadix-bombe-jar"]
hypo = ["hypo-model", "hypo-core", "hypo-hydrate", "hypo-asm-core", "hypo-asm-hydrate", "hypo-mappings"]
kotson = ["kotson", "gson"]

View file

@ -7,6 +7,7 @@ dependencies {
shade(projects.paperweightLib)
implementation(libs.bundles.kotson)
implementation(libs.coroutines)
}
gradlePlugin {

View file

@ -25,6 +25,7 @@ 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.taskcontainers.SoftSpoonTasks
import io.papermc.paperweight.core.tasks.PaperweightCorePrepareForDownstream
import io.papermc.paperweight.taskcontainers.BundlerJarTasks
import io.papermc.paperweight.taskcontainers.DevBundleTasks
@ -53,7 +54,9 @@ class PaperweightCore : Plugin<Project> {
val ext = target.extensions.create(PAPERWEIGHT_EXTENSION, PaperweightCoreExtension::class, target)
target.gradle.sharedServices.registerIfAbsent(DOWNLOAD_SERVICE_NAME, DownloadService::class) {}
target.gradle.sharedServices.registerIfAbsent(DOWNLOAD_SERVICE_NAME, DownloadService::class) {
parameters.projectPath.set(target.projectDir)
}
target.tasks.register<Delete>("cleanCache") {
group = "paper"
@ -69,6 +72,7 @@ class PaperweightCore : Plugin<Project> {
target.configurations.create(REMAPPER_CONFIG)
target.configurations.create(DECOMPILER_CONFIG)
target.configurations.create(PAPERCLIP_CONFIG)
target.configurations.create(MACHE_CONFIG)
if (target.providers.gradleProperty("paperweight.dev").orNull == "true") {
target.tasks.register<CreateDiffOutput>("diff") {
@ -90,10 +94,12 @@ class PaperweightCore : Plugin<Project> {
ext.mainClass
)
val softSpoonTasks = SoftSpoonTasks(target, tasks)
target.createPatchRemapTask(tasks)
target.tasks.register<PaperweightCorePrepareForDownstream>(PAPERWEIGHT_PREPARE_DOWNSTREAM) {
dependsOn(tasks.applyPatches)
dependsOn(tasks.applyPatchesLegacy)
vanillaJar.set(tasks.downloadServerJar.flatMap { it.outputJar })
remappedJar.set(tasks.lineMapJar.flatMap { it.outputJar })
decompiledJar.set(tasks.decompileJar.flatMap { it.outputJar })
@ -124,21 +130,35 @@ class PaperweightCore : Plugin<Project> {
}
target.afterEvaluate {
println("SoftSpoon: ${ext.softSpoon.get()}")
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) }
if (!ext.softSpoon.get()) {
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) }
}
} else {
maven(ext.macheRepo) {
name = MACHE_REPO_NAME
content { onlyForConfigurations(MACHE_CONFIG) }
}
}
}
if (ext.softSpoon.get()) {
softSpoonTasks.afterEvaluate()
return@afterEvaluate
}
// Setup the server jar
val cache = target.layout.cache

View file

@ -38,8 +38,11 @@ open class PaperExtension(objects: ObjectFactory, layout: ProjectLayout) {
val spigotServerPatchDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "patches/server")
val remappedSpigotServerPatchDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "patches/server-remapped")
val unmappedSpigotServerPatchDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "patches/server-unmapped")
val paperApiDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "Paper-API")
val paperServerDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "Paper-Server")
val paperApiDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "paper-api")
val paperServerDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "paper-server")
val sourcePatchDir: DirectoryProperty = objects.dirFrom(paperServerDir, "patches/sources")
val resourcePatchDir: DirectoryProperty = objects.dirFrom(paperServerDir, "patches/resources")
val featurePatchDir: DirectoryProperty = objects.dirFrom(paperServerDir, "patches/feature")
@Suppress("MemberVisibilityCanBePrivate")
val buildDataDir: DirectoryProperty = objects.dirWithDefault(layout, "build-data")

View file

@ -36,10 +36,13 @@ import org.gradle.kotlin.dsl.*
open class PaperweightCoreExtension(project: Project, objects: ObjectFactory, layout: ProjectLayout) {
val softSpoon: Property<Boolean> = objects.property<Boolean>().convention(false)
@Suppress("MemberVisibilityCanBePrivate")
val workDir: DirectoryProperty = objects.dirWithDefault(layout, "work")
val minecraftVersion: Property<String> = objects.property()
val minecraftManifestUrl: Property<String> = objects.property<String>().convention(MC_MANIFEST_URL)
val serverProject: Property<Project> = objects.property()
val mainClass: Property<String> = objects.property<String>().convention("org.bukkit.craftbukkit.Main")
@ -50,6 +53,7 @@ open class PaperweightCoreExtension(project: Project, objects: ObjectFactory, la
val paramMappingsRepo: Property<String> = objects.property()
val decompileRepo: Property<String> = objects.property()
val remapRepo: Property<String> = objects.property()
val macheRepo: Property<String> = objects.property<String>().convention("https://repo.papermc.io/repository/maven-public/")
val vanillaJarIncludes: ListProperty<String> = objects.listProperty<String>().convention(
listOf("/*.class", "/net/minecraft/**", "/com/mojang/math/**")

View file

@ -101,6 +101,23 @@ open class AllTasks(
downloader.set(downloadService)
}
val downloadPaperLibrariesSources by tasks.registering<DownloadPaperLibraries> {
paperDependencies.set(
project.ext.serverProject.map { p ->
val configuration = p.configurations["implementation"]
configuration.isCanBeResolved = true
configuration.resolvedConfiguration.resolvedArtifacts.map {
"${it.moduleVersion.id.group}:${it.moduleVersion.id.name}:${it.moduleVersion.id.version}"
}
}
)
repositories.set(listOf(MAVEN_CENTRAL_URL, PAPER_MAVEN_REPO_URL))
outputDir.set(cache.resolve(PAPER_SOURCES_JARS_PATH))
sources.set(true)
downloader.set(downloadService)
}
@Suppress("DuplicatedCode")
val applyServerPatches by tasks.registering<ApplyPaperPatches> {
group = "paper"
@ -126,7 +143,7 @@ open class AllTasks(
mcDevSources.set(extension.mcDevSourceDir)
}
val applyPatches by tasks.registering<Task> {
val applyPatchesLegacy by tasks.registering<Task> {
group = "paper"
description = "Set up the Paper development environment"
dependsOn(applyApiPatches, applyServerPatches)
@ -151,7 +168,7 @@ open class AllTasks(
}
@Suppress("unused")
val rebuildPatches by tasks.registering<Task> {
val rebuildPatchesLegacy by tasks.registering<Task> {
group = "paper"
description = "Rebuilds patches to api and server"
dependsOn(rebuildApiPatches, rebuildServerPatches)

View file

@ -45,7 +45,7 @@ open class InitialTasks(
) {
val downloadMcManifest by tasks.registering<DownloadTask> {
url.set(MC_MANIFEST_URL)
url.set(project.ext.minecraftManifestUrl)
outputFile.set(cache.resolve(MC_MANIFEST))
doNotTrackState("The Minecraft manifest is a changing resource")
@ -54,7 +54,7 @@ open class InitialTasks(
}
private val mcManifest = downloadMcManifest.flatMap { it.outputFile }.map { gson.fromJson<MinecraftManifest>(it) }
val downloadMcVersionManifest by tasks.registering<DownloadTask> {
val downloadMcVersionManifest by tasks.registering<CacheableDownloadTask> {
url.set(
mcManifest.zip(extension.minecraftVersion) { manifest, version ->
manifest.versions.first { it.id == version }.url
@ -71,7 +71,7 @@ open class InitialTasks(
}
private val versionManifest = downloadMcVersionManifest.flatMap { it.outputFile }.map { gson.fromJson<MinecraftVersionManifest>(it) }
val downloadMappings by tasks.registering<DownloadTask> {
val downloadMappings by tasks.registering<CacheableDownloadTask> {
url.set(versionManifest.map { version -> version.serverMappingsDownload().url })
expectedHash.set(versionManifest.map { version -> version.serverMappingsDownload().hash() })
outputFile.set(cache.resolve(SERVER_MAPPINGS))
@ -94,5 +94,6 @@ open class InitialTasks(
serverLibrariesList.set(cache.resolve(SERVER_LIBRARIES_LIST))
serverVersionsList.set(cache.resolve(SERVER_VERSIONS_LIST))
serverLibraryJars.set(cache.resolve(MINECRAFT_JARS_PATH))
serverJar.set(cache.resolve(SERVER_JAR))
}
}

View file

@ -0,0 +1,344 @@
/*
* 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.core.ext
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.tasks.mache.*
import io.papermc.paperweight.tasks.mache.RemapJar
import io.papermc.paperweight.tasks.softspoon.ApplyFeaturePatches
import io.papermc.paperweight.tasks.softspoon.ApplyFilePatches
import io.papermc.paperweight.tasks.softspoon.ApplyFilePatchesFuzzy
import io.papermc.paperweight.tasks.softspoon.RebuildFilePatches
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import io.papermc.paperweight.util.data.mache.*
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskContainer
import org.gradle.kotlin.dsl.*
open class SoftSpoonTasks(
val project: Project,
val allTasks: AllTasks,
tasks: TaskContainer = project.tasks
) {
lateinit var mache: MacheMeta
val macheCodebook by project.configurations.registering {
isTransitive = false
}
val macheRemapper by project.configurations.registering {
isTransitive = false
}
val macheDecompiler by project.configurations.registering {
isTransitive = false
}
val macheParamMappings by project.configurations.registering {
isTransitive = false
}
val macheConstants by project.configurations.registering {
isTransitive = false
}
val macheMinecraft by project.configurations.registering
val macheMinecraftExtended by project.configurations.registering
val macheRemapJar by tasks.registering(RemapJar::class) {
group = "mache"
serverJar.set(allTasks.extractFromBundler.flatMap { it.serverJar })
serverMappings.set(allTasks.downloadMappings.flatMap { it.outputFile })
remapperArgs.set(mache.remapperArgs)
codebookClasspath.from(macheCodebook)
minecraftClasspath.from(macheMinecraft)
remapperClasspath.from(macheRemapper)
paramMappings.from(macheParamMappings)
constants.from(macheConstants)
outputJar.set(layout.cache.resolve(FINAL_REMAPPED_CODEBOOK_JAR))
}
val macheDecompileJar by tasks.registering(DecompileJar::class) {
group = "mache"
inputJar.set(macheRemapJar.flatMap { it.outputJar })
decompilerArgs.set(mache.decompilerArgs)
minecraftClasspath.from(macheMinecraft)
decompiler.from(macheDecompiler)
outputJar.set(layout.cache.resolve(FINAL_DECOMPILE_JAR))
}
val collectAccessTransform by tasks.registering(CollectATsFromPatches::class) {
group = "mache"
patchDir.set(project.ext.paper.featurePatchDir)
}
val mergeCollectedAts by tasks.registering<MergeAccessTransforms> {
firstFile.set(project.ext.paper.additionalAts.fileExists(project))
secondFile.set(collectAccessTransform.flatMap { it.outputFile })
}
val setupMacheSources by tasks.registering(SetupVanilla::class) {
group = "mache"
description = "Setup vanilla source dir (applying mache patches and paper ATs)."
mache.from(project.configurations.named(MACHE_CONFIG))
machePatches.set(layout.cache.resolve(PATCHES_FOLDER))
ats.set(mergeCollectedAts.flatMap { it.outputFile })
minecraftClasspath.from(macheMinecraft)
libraries.from(allTasks.downloadPaperLibrariesSources.flatMap { it.outputDir }, allTasks.downloadMcLibrariesSources.flatMap { it.outputDir })
paperPatches.from(project.ext.paper.sourcePatchDir, project.ext.paper.featurePatchDir)
devImports.set(project.ext.paper.devImports.fileExists(project))
inputFile.set(macheDecompileJar.flatMap { it.outputJar })
predicate.set { Files.isRegularFile(it) && it.toString().endsWith(".java") }
outputDir.set(layout.cache.resolve(BASE_PROJECT).resolve("sources"))
}
val setupMacheResources by tasks.registering(SetupVanilla::class) {
group = "mache"
description = "Setup vanilla resources dir"
inputFile.set(allTasks.extractFromBundler.flatMap { it.serverJar })
predicate.set { Files.isRegularFile(it) && !it.toString().endsWith(".class") }
outputDir.set(layout.cache.resolve(BASE_PROJECT).resolve("resources"))
}
val applySourcePatches by tasks.registering(ApplyFilePatches::class) {
group = "softspoon"
description = "Applies patches to the vanilla sources"
input.set(setupMacheSources.flatMap { it.outputDir })
output.set(project.ext.serverProject.map { it.layout.projectDirectory.dir("src/vanilla/java") })
patches.set(project.ext.paper.sourcePatchDir)
}
val applySourcePatchesFuzzy by tasks.registering(ApplyFilePatchesFuzzy::class) {
group = "softspoon"
description = "Applies patches to the vanilla sources"
input.set(setupMacheSources.flatMap { it.outputDir })
output.set(project.ext.serverProject.map { it.layout.projectDirectory.dir("src/vanilla/java") })
patches.set(project.ext.paper.sourcePatchDir)
}
val applyResourcePatches by tasks.registering(ApplyFilePatches::class) {
group = "softspoon"
description = "Applies patches to the vanilla resources"
input.set(setupMacheResources.flatMap { it.outputDir })
output.set(project.ext.serverProject.map { it.layout.projectDirectory.dir("src/vanilla/resources") })
patches.set(project.ext.paper.resourcePatchDir)
}
val applyFilePatches by tasks.registering(Task::class) {
group = "softspoon"
description = "Applies all file patches"
dependsOn(applySourcePatches, applyResourcePatches)
}
val applyFeaturePatches by tasks.registering(ApplyFeaturePatches::class) {
group = "softspoon"
description = "Applies all feature patches"
dependsOn(applyFilePatches)
repo.set(project.ext.serverProject.map { it.layout.projectDirectory.dir("src/vanilla/java") })
patches.set(project.ext.paper.featurePatchDir)
}
val applyPatches by tasks.registering(Task::class) {
group = "softspoon"
description = "Applies all patches"
dependsOn(applyFilePatches, applyFeaturePatches)
}
val rebuildSourcePatches by tasks.registering(RebuildFilePatches::class) {
group = "softspoon"
description = "Rebuilds patches to the vanilla sources"
minecraftClasspath.from(macheMinecraftExtended)
atFile.set(project.ext.paper.additionalAts.fileExists(project))
atFileOut.set(project.ext.paper.additionalAts.fileExists(project))
base.set(layout.cache.resolve(BASE_PROJECT).resolve("sources"))
input.set(project.ext.serverProject.map { it.layout.projectDirectory.dir("src/vanilla/java") })
patches.set(project.ext.paper.sourcePatchDir)
}
val rebuildResourcePatches by tasks.registering(RebuildFilePatches::class) {
group = "softspoon"
description = "Rebuilds patches to the vanilla resources"
base.set(layout.cache.resolve(BASE_PROJECT).resolve("resources"))
input.set(project.ext.serverProject.map { it.layout.projectDirectory.dir("src/vanilla/resources") })
patches.set(project.ext.paper.resourcePatchDir)
}
val rebuildFilePatches by tasks.registering(Task::class) {
group = "softspoon"
description = "Rebuilds all file patches"
dependsOn(rebuildSourcePatches, rebuildResourcePatches)
}
val rebuildFeaturePatches by tasks.registering(RebuildGitPatches::class) {
group = "softspoon"
description = "Rebuilds all feature patches"
dependsOn(rebuildFilePatches)
inputDir.set(project.ext.serverProject.map { it.layout.projectDirectory.dir("src/vanilla/java") })
patchDir.set(project.ext.paper.featurePatchDir)
baseRef.set("file")
}
val rebuildPatches by tasks.registering(Task::class) {
group = "softspoon"
description = "Rebuilds all file patches"
dependsOn(rebuildFilePatches, rebuildFeaturePatches)
}
fun afterEvaluate() {
// load mache
mache = this.project.configurations.named(MACHE_CONFIG).get().singleFile.toPath().openZip().use { zip ->
return@use gson.fromJson<MacheMeta>(zip.getPath("/mache.json").readLines().joinToString("\n"))
}
println("Loaded mache ${mache.macheVersion} for minecraft ${mache.minecraftVersion}")
// setup repos
this.project.repositories {
println("setup repos for ${project.name}")
for (repository in mache.repositories) {
maven(repository.url) {
name = repository.name
mavenContent {
for (group in repository.groups ?: listOf()) {
includeGroupByRegex(group + ".*")
}
}
}
}
maven(MC_LIBRARY_URL) {
name = "Minecraft"
}
mavenCentral()
}
val libsFile = project.layout.cache.resolve(SERVER_LIBRARIES_TXT)
// setup mc deps
macheMinecraft {
withDependencies {
project.dependencies {
val libs = libsFile.convertToPathOrNull()
if (libs != null && libs.exists()) {
libs.forEachLine { line ->
add(create(line))
}
}
}
}
}
macheMinecraftExtended {
extendsFrom(macheMinecraft.get())
withDependencies {
project.dependencies {
add(create(project.files(project.layout.cache.resolve(FINAL_REMAPPED_CODEBOOK_JAR))))
}
}
}
// setup mache deps
this.project.dependencies {
mache.dependencies.codebook.forEach {
"macheCodebook"(it.toMavenString())
}
mache.dependencies.paramMappings.forEach {
"macheParamMappings"(it.toMavenString())
}
mache.dependencies.constants.forEach {
"macheConstants"(it.toMavenString())
}
mache.dependencies.remapper.forEach {
"macheRemapper"(it.toMavenString())
}
mache.dependencies.decompiler.forEach {
"macheDecompiler"(it.toMavenString())
}
}
this.project.ext.serverProject.get().setupServerProject(libsFile)
}
private fun Project.setupServerProject(libsFile: Path) {
if (!projectDir.exists()) {
return
}
// minecraft deps
val macheMinecraft by configurations.creating {
withDependencies {
dependencies {
// setup mc deps
val libs = libsFile.convertToPathOrNull()
if (libs != null && libs.exists()) {
libs.forEachLine { line ->
add(create(line))
}
}
}
}
}
// impl extends minecraft
configurations.named(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME) {
extendsFrom(macheMinecraft)
}
// repos
repositories {
mavenCentral()
maven(PAPER_MAVEN_REPO_URL)
maven(MC_LIBRARY_URL)
}
// add vanilla source set
the<JavaPluginExtension>().sourceSets.named(SourceSet.MAIN_SOURCE_SET_NAME) {
java {
srcDirs(projectDir.resolve("src/vanilla/java"))
}
resources {
srcDirs(projectDir.resolve("src/vanilla/resources"))
}
}
}
}

View file

@ -53,7 +53,9 @@ open class SpigotTasks(
val generateSpigotMappings by tasks.registering<GenerateSpigotMappings> {
classMappings.set(addAdditionalSpigotMappings.flatMap { it.outputClassSrg })
sourceMappings.set(generateMappings.flatMap { it.outputMappings })
// todo hypo update breaks generate mappings, hardcode for now
// sourceMappings.set(generateMappings.flatMap { it.outputMappings })
sourceMappings.set(Path.of("D:\\IntellijProjects\\PaperClean\\.gradle\\caches\\paperweight\\mappings\\official-mojang+yarn.tiny"))
outputMappings.set(cache.resolve(SPIGOT_MOJANG_YARN_MAPPINGS))
notchToSpigotMappings.set(cache.resolve(OBF_SPIGOT_MAPPINGS))

View file

@ -49,7 +49,8 @@ open class VanillaTasks(
val remapJar by tasks.registering<RemapJar> {
inputJar.set(filterVanillaJar.flatMap { it.outputJar })
mappingsFile.set(generateMappings.flatMap { it.outputMappings })
// mappingsFile.set(generateMappings.flatMap { it.outputMappings })
mappingsFile.set(Path.of("D:\\IntellijProjects\\PaperClean\\.gradle\\caches\\paperweight\\mappings\\official-mojang+yarn.tiny"))
fromNamespace.set(OBF_NAMESPACE)
toNamespace.set(DEOBF_NAMESPACE)
remapper.from(project.configurations.named(REMAPPER_CONFIG))

View file

@ -0,0 +1,179 @@
/*
* 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.util.*
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.*
import kotlin.test.Test
import kotlin.test.assertEquals
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.io.CleanupMode
import org.junit.jupiter.api.io.TempDir
class FunctionalTest {
val debug = false
@Disabled
@Test
fun setupCleanTestRepo() {
val projectDir = Path.of("F:\\Projects\\paperweight\\test").ensureClean().createDirectories()
setupMache("fake_mache", projectDir.resolve("mache.zip"))
setupMojang("fake_mojang", projectDir.resolve("fake_mojang"))
projectDir.copyProject("functional_test")
val settings = projectDir.resolve("settings.gradle")
val text = settings.readText()
settings.writeText(text.replace("// includeBuild '..'", "includeBuild '..'").replace("functional_test", "test"))
projectDir.resolve("patches").deleteRecursively()
}
@Test
fun `test simple test project`(@TempDir(cleanup = CleanupMode.ON_SUCCESS) tempDir: Path) {
println("running in $tempDir")
val testResource = Paths.get("src/test/resources/functional_test")
setupMache("fake_mache", tempDir.resolve("mache.zip"))
setupMojang("fake_mojang", tempDir.resolve("fake_mojang"))
val gradleRunner = tempDir.copyProject("functional_test").gradleRunner()
println("\nrunning extractFromBundler\n")
val extractFromBundler = gradleRunner
.withArguments("extractFromBundler", "--stacktrace", "-Dfake=true")
.withDebug(debug)
.build()
assertEquals(extractFromBundler.task(":extractFromBundler")?.outcome, TaskOutcome.SUCCESS)
// appP -> works
println("\nrunning applyPatches dependencies\n")
val appP = gradleRunner
.withArguments("applyPatches", "dependencies", ":test-server:dependencies", "--stacktrace", "-Dfake=true")
.withDebug(debug)
.build()
assertEquals(appP.task(":applyPatches")?.outcome, TaskOutcome.SUCCESS)
// clean rebuild rebP -> changes nothing
println("\nrunning rebuildPatches\n")
val rebP = gradleRunner
.withArguments("rebuildPatches", "--stacktrace", "-Dfake=true")
.withDebug(debug)
.build()
assertEquals(rebP.task(":rebuildPatches")?.outcome, TaskOutcome.SUCCESS)
assertEquals(
testResource.resolve("fake-patches/sources/Test.java.patch").readText(),
tempDir.resolve("fake-patches/sources/Test.java.patch").readText()
)
// add AT to source -> patch and AT file is updated
val sourceFile = tempDir.resolve("test-server/src/vanilla/java/Test.java")
val replacedContent = sourceFile.readText().replace(
"\"2\";",
"\"2\"; // Woo"
).replace("public String getTest2() {", "private final String getTest2() {// Paper-AT: private+f getTest2()Ljava/lang/String;")
sourceFile.writeText(replacedContent)
Git(tempDir.resolve("test-server/src/vanilla/java")).let { git ->
git("add", ".").executeSilently()
git("commit", "--amend", "--no-edit").executeSilently()
}
println("\nrunning rebuildPatches again\n")
val rebP2 = gradleRunner
.withArguments("rebuildPatches", "--stacktrace", "-Dfake=true")
.withDebug(debug)
.build()
assertEquals(rebP2.task(":rebuildPatches")?.outcome, TaskOutcome.SUCCESS)
assertEquals(
testResource.resolve("fake-patches/expected/Test.java.patch").readText(),
tempDir.resolve("fake-patches/sources/Test.java.patch").readText()
)
assertEquals(testResource.resolve("build-data/expected.at").readText(), tempDir.resolve("build-data/fake.at").readText())
}
@Test
fun `test full vanilla project`(@TempDir(cleanup = CleanupMode.ON_SUCCESS) tempDir: Path) {
println("running in $tempDir")
val gradleRunner = tempDir.copyProject("functional_test").gradleRunner()
val extractFromBundler = gradleRunner
.withArguments("extractFromBundler", "--stacktrace", "-Dfake=false")
.withDebug(debug)
.build()
assertEquals(extractFromBundler.task(":extractFromBundler")?.outcome, TaskOutcome.SUCCESS)
val result = gradleRunner
.withArguments("applyPatches", "-Dfake=false")
.withDebug(debug)
.build()
assertEquals(result.task(":applyPatches")?.outcome, TaskOutcome.SUCCESS)
}
fun setupMache(macheName: String, target: Path) {
val macheDir = Paths.get("src/test/resources/$macheName")
zip(macheDir, target)
}
fun setupMojang(mojangName: String, target: Path) {
val mojangDir = Paths.get("src/test/resources/$mojangName")
mojangDir.copyRecursivelyTo(target)
val serverFolder = target.resolve("server")
ProcessBuilder()
.directory(serverFolder)
.command("javac", serverFolder.resolve("Test.java").toString())
.redirectErrorStream(true)
.start()
.waitFor()
ProcessBuilder()
.directory(serverFolder)
.command("jar", "-cf", "server.jar", "Test.class", "test.json")
.redirectErrorStream(true)
.start()
.waitFor()
val versionFolder = target.resolve("bundle/META-INF/versions/fake/")
versionFolder.createDirectories()
serverFolder.resolve("server.jar").copyTo(versionFolder.resolve("server.jar"))
val oshiFolder = target.resolve("bundle/META-INF/libraries/com/github/oshi/oshi-core/6.4.5/")
oshiFolder.createDirectories()
oshiFolder.resolve(
"oshi-core-6.4.5.jar"
).writeBytes(URL("https://libraries.minecraft.net/com/github/oshi/oshi-core/6.4.5/oshi-core-6.4.5.jar").readBytes())
zip(target.resolve("bundle"), target.resolve("bundle.jar"))
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.util.*
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.*
import org.gradle.testkit.runner.GradleRunner
fun Path.copyProject(resourcesProjectName: String): ProjectFiles {
Paths.get("src/test/resources/$resourcesProjectName")
.copyToRecursively(this, followLinks = false)
Git(this)("init").executeSilently()
return ProjectFiles(this)
}
class ProjectFiles(val projectDir: Path) {
val gradleProperties: Path = resolve("gradle.properties")
val buildGradle: Path = resolve("build.gradle")
val buildGradleKts: Path = resolve("build.gradle.kts")
val settingsGradle: Path = resolve("settings.gradle")
val settingsGradleKts: Path = resolve("settings.gradle.kts")
fun resolve(path: String): Path = projectDir.resolve(path)
fun gradleRunner(): GradleRunner = GradleRunner.create()
.forwardOutput()
.withPluginClasspath()
.withProjectDir(projectDir.toFile())
}

View file

@ -0,0 +1,108 @@
{
"minecraftVersion": "fake",
"macheVersion": "fake",
"dependencies": {
"codebook": [
{
"group": "io.papermc.codebook",
"name": "codebook-cli",
"version": "1.0.10",
"classifier": "all"
}
],
"paramMappings": [
{
"group": "org.parchmentmc.data",
"name": "parchment-1.20.4",
"version": "2024.02.25",
"extension": "zip"
}
],
"constants": [
],
"remapper": [
{
"group": "net.neoforged",
"name": "AutoRenamingTool",
"version": "1.0.14",
"classifier": "all"
}
],
"decompiler": [
{
"group": "org.vineflower",
"name": "vineflower",
"version": "1.10.0-20240212.003104-88"
}
]
},
"repositories": [
{
"url": "https://maven.fabricmc.net/",
"name": "FabricMC",
"groups": [
"net.fabricmc"
]
},
{
"url": "https://maven.neoforged.net/releases/",
"name": "NeoForged",
"groups": [
"net.neoforged",
"net.minecraftforge"
]
},
{
"url": "https://repo.papermc.io/repository/maven-public/",
"name": "PaperMC",
"groups": [
"io.papermc"
]
},
{
"url": "https://maven.parchmentmc.org/",
"name": "ParchmentMC",
"groups": [
"org.parchmentmc"
]
},
{
"url": "https://s01.oss.sonatype.org/content/repositories/snapshots/",
"name": "sonatype snapshots",
"groups": [
"org.vineflower"
]
}
],
"decompilerArgs": [
"-nns=true",
"-tcs=true",
"-ovr=false",
"-vvm=true",
"-iec=true",
"-jrt=current",
"-ind= ",
"-jvn=false",
"-dcc=true",
"-sef=true",
"-nls=1"
],
"remapperArgs": [
"--temp-dir={tempDir}",
"--remapper-file={remapperFile}",
"--mappings-file={mappingsFile}",
"--params-file={paramsFile}",
"--output={output}",
"--input={input}",
"--input-classpath={inputClasspath}"
],
"additionalCompileDependencies": {
"compileOnly": [
{
"group": "org.jetbrains",
"name": "annotations",
"version": "24.0.1"
}
]
}
}

View file

@ -0,0 +1,9 @@
--- a/Test.java
+++ b/Test.java
@@ -1,5 +_,5 @@
public class Test {
- public int dum;
+ public int dum2;
private final String test;
public Test(String var1) {

View file

@ -0,0 +1 @@
- com.github.oshi:oshi-core:6.4.5 com/github/oshi/oshi-core/6.4.5/oshi-core-6.4.5.jar

View file

@ -0,0 +1 @@
- fake fake/server.jar

View file

@ -0,0 +1,3 @@
{
"id": "fake"
}

View file

@ -0,0 +1,19 @@
com.mojang.math.Axis -> a:
# {"fileName":"Axis.java","id":"sourceFile"}
com.mojang.math.Axis XN -> a
com.mojang.math.Axis XP -> b
com.mojang.math.Axis YN -> c
com.mojang.math.Axis YP -> d
com.mojang.math.Axis ZN -> e
com.mojang.math.Axis ZP -> f
17:17:com.mojang.math.Axis of(org.joml.Vector3f) -> of
org.joml.Quaternionf rotation(float) -> rotation
23:23:org.joml.Quaternionf rotationDegrees(float) -> rotationDegrees
17:17:org.joml.Quaternionf lambda$of$6(org.joml.Vector3f,float) -> a
14:14:org.joml.Quaternionf lambda$static$5(float) -> a
13:13:org.joml.Quaternionf lambda$static$4(float) -> b
12:12:org.joml.Quaternionf lambda$static$3(float) -> c
11:11:org.joml.Quaternionf lambda$static$2(float) -> d
10:10:org.joml.Quaternionf lambda$static$1(float) -> e
9:9:org.joml.Quaternionf lambda$static$0(float) -> f
9:14:void <clinit>() -> <clinit>

View file

@ -0,0 +1,17 @@
public class Test {
public int dum;
private final String test;
public Test(String test) {
this.test = test;
}
public String getTest() {
return test;
}
public String getTest2() {
return test + "2";
}
}

View file

@ -0,0 +1,3 @@
{
"test": "test"
}

View file

@ -0,0 +1,12 @@
{
"downloads": {
"server": {
"sha1": "",
"url": "file://project/fake_mojang/bundle.jar"
},
"server_mappings": {
"sha1": "",
"url": "file://project/fake_mojang/mappings.txt"
}
}
}

View file

@ -0,0 +1,14 @@
{
"latest": {
"release": "fake",
"snapshot": "fake"
},
"versions": [
{
"id": "fake",
"type": "release",
"url": "file://project/fake_mojang/version.json",
"sha1": ""
}
]
}

View file

@ -0,0 +1,2 @@
.gradle
gradle

View file

@ -0,0 +1,5 @@
# This file is auto generated, any changes may be overridden!
# See CONTRIBUTING.md on how to add access transformers.
private+f Test getTest2()Ljava/lang/String;
public+f Test dum2
public+f Test getTest()Ljava/lang/String;

View file

@ -0,0 +1,4 @@
# This file is auto generated, any changes may be overridden!
# See CONTRIBUTING.md on how to add access transformers.
public+f Test dum2
public+f Test getTest()Ljava/lang/String;

View file

@ -0,0 +1,3 @@
# This file is auto generated, any changes may be overridden!
# See CONTRIBUTING.md on how to add access transformers.
private+f net.minecraft.CrashReport getTitle()Ljava/lang/String;

View file

@ -0,0 +1,52 @@
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
id 'io.papermc.paperweight.core'
}
allprojects {
apply plugin: 'java'
}
repositories {
mavenLocal()
mavenCentral()
}
def fake = Boolean.getBoolean("fake")
dependencies {
if (fake) {
mache files('mache.zip') // use fake mache for testing
} else {
mache 'io.papermc:mache:1.20.4+build.3'
}
}
paperweight {
softSpoon = true
if (fake) {
// use fake mojang data for testing
minecraftVersion = 'fake'
minecraftManifestUrl = 'file://project/fake_mojang/version_manifest.json'
paper {
sourcePatchDir.set(file('fake-patches/sources'))
resourcePatchDir.set(file('fake-patches/resources'))
featurePatchDir.set(file('fake-patches/feature'))
additionalAts.set(file('build-data/fake.at'))
paperServerDir.set(file('test-server'))
}
} else {
minecraftVersion = '1.20.4'
paper {
paperServerDir.set(file('test-server'))
}
}
serverProject = project(':test-server')
}

View file

@ -0,0 +1,15 @@
--- a/Test.java
+++ b/Test.java
@@ -7,10 +_,10 @@
}
public final String getTest() {
- return this.test;
+ return this.test; // WOOOO
}
private final String getTest2() {
- return this.test + "2";
+ return this.test + "2"; // Woo
}
}

View file

@ -0,0 +1,7 @@
--- a/test.json
+++ b/test.json
@@ -1,3 +_,3 @@
{
- "test": "test"
+ "test": "test2"
}

View file

@ -0,0 +1,11 @@
--- a/Test.java
+++ b/Test.java
@@ -7,7 +_,7 @@
}
public final String getTest() {
- return this.test;
+ return this.test; // WOOOO
}
public String getTest2() {

View file

@ -0,0 +1,18 @@
pluginManagement {
// includeBuild '..'
repositories {
mavenLocal()
gradlePluginPortal()
maven {
url "https://repo.papermc.io/repository/maven-public/"
}
maven {
url "https://maven.parchmentmc.org"
}
}
}
rootProject.name = 'functional_test'
include 'test-api'
include 'test-server'

View file

@ -0,0 +1,3 @@
plugins {
id "java-library"
}

View file

@ -0,0 +1,8 @@
plugins {
id "java"
id "com.github.johnrengelman.shadow"
}
dependencies {
implementation project(":test-api")
}

View file

@ -0,0 +1,18 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MiniDigger | Martin <admin@benndorf.dev>
Date: Sun, 31 Mar 2024 17:01:32 +0200
Subject: [PATCH] Example
diff --git a/net/minecraft/server/dedicated/DedicatedServer.java b/net/minecraft/server/dedicated/DedicatedServer.java
index 28def15b5789d34bcfc2a16a17444c9c72dc2c8f..a8f735100a8d4db72dae9be3eeeca0d417be3eee 100644
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -106,6 +106,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER));
thread.start();
LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName());
+ LOGGER.info("Wooooooooooooooooooo");
if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) {
LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
}

View file

@ -0,0 +1,11 @@
--- a/version.json
+++ b/version.json
@@ -1,6 +_,6 @@
{
- "id": "1.20.4",
- "name": "1.20.4",
+ "id": "1.20.4-paper",
+ "name": "1.20.4 - Paper",
"world_version": 3700,
"series_id": "main",
"protocol_version": 765,

View file

@ -0,0 +1,15 @@
--- a/net/minecraft/CrashReport.java
+++ b/net/minecraft/CrashReport.java
@@ -35,10 +_,11 @@
public CrashReport(String title, Throwable exception) {
this.title = title;
this.exception = exception;
+ // FIXME this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit
}
private final String getTitle() {
- return this.title;
+ return this.title; // Test
}
public Throwable getException() {

View file

@ -9,6 +9,8 @@ repositories {
dependencies {
implementation(libs.httpclient)
implementation(libs.bundles.kotson)
implementation(libs.coroutines)
implementation(libs.jgit)
// ASM for inspection
implementation(libs.bundles.asm)
@ -19,9 +21,16 @@ dependencies {
implementation(libs.lorenzTiny)
implementation(libs.feather.core)
implementation(libs.feather.gson)
implementation(libs.jbsdiff)
implementation(libs.restamp)
implementation(variantOf(libs.diffpatch) { classifier("all") }) {
isTransitive = false
}
testImplementation(libs.mockk)
}

View file

@ -32,6 +32,8 @@ import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.io.path.*
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.apache.http.HttpHost
import org.apache.http.HttpStatus
import org.apache.http.client.config.CookieSpecs
@ -41,12 +43,17 @@ import org.apache.http.client.methods.HttpGet
import org.apache.http.client.utils.DateUtils
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClientBuilder
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
abstract class DownloadService : BuildService<BuildServiceParameters.None>, AutoCloseable {
abstract class DownloadService : BuildService<DownloadService.Params>, AutoCloseable {
interface Params : BuildServiceParameters {
val projectPath: DirectoryProperty
}
private companion object {
val LOGGER: Logger = Logging.getLogger(DownloadService::class.java)
@ -64,13 +71,19 @@ abstract class DownloadService : BuildService<BuildServiceParameters.None>, Auto
download(url, file, hash)
}
suspend fun downloadAsync(source: Any, target: Any, hash: Hash? = null) = coroutineScope {
async {
download(source.convertToUrl(), target.convertToPath(), hash, false)
}
}
private fun download(source: URL, target: Path, hash: Hash?, retry: Boolean = false) {
download(source, target)
if (hash == null) {
return
}
val dlHash = target.hashFile(hash.algorithm).asHexString().lowercase(Locale.ENGLISH)
if (dlHash == hash.valueLower) {
if (hash.value == "" || dlHash == hash.valueLower) {
return
}
LOGGER.warn(
@ -91,6 +104,15 @@ abstract class DownloadService : BuildService<BuildServiceParameters.None>, Auto
private fun download(source: URL, target: Path) {
target.parent.createDirectories()
if (source.protocol == "file") {
var path = source.toString().replace("file://", "")
if (source.host == "project") {
path = path.replace("project", parameters.projectPath.path.absolutePathString())
}
Path.of(path).copyTo(target, overwrite = true)
return
}
val etagDir = target.resolveSibling("etags")
etagDir.createDirectories()

View file

@ -189,16 +189,16 @@ fun Git.disableAutoGpgSigningInRepo() {
invoke("config", "tag.gpgSign", "false").executeSilently(silenceErr = true)
}
fun checkoutRepoFromUpstream(git: Git, upstream: Path, upstreamBranch: String) {
fun checkoutRepoFromUpstream(git: Git, upstream: Path, upstreamBranch: String, upstreamName: String = "upstream", branchName: String = "master") {
git("init", "--quiet").executeSilently(silenceErr = true)
git.disableAutoGpgSigningInRepo()
git("remote", "remove", "upstream").runSilently(silenceErr = true)
git("remote", "add", "upstream", upstream.toUri().toString()).executeSilently(silenceErr = true)
git("fetch", "upstream", "--prune").executeSilently(silenceErr = true)
if (git("checkout", "master").runSilently(silenceErr = true) != 0) {
git("checkout", "-b", "master").runSilently(silenceErr = true)
git("remote", "remove", upstreamName).runSilently(silenceErr = true)
git("remote", "add", upstreamName, upstream.toUri().toString()).executeSilently(silenceErr = true)
git("fetch", upstreamName, "--prune").executeSilently(silenceErr = true)
if (git("checkout", branchName).runSilently(silenceErr = true) != 0) {
git("checkout", "-b", branchName).runSilently(silenceErr = true)
}
git("reset", "--hard", "upstream/$upstreamBranch").executeSilently(silenceErr = true)
git("reset", "--hard", "$upstreamName/$upstreamBranch").executeSilently(silenceErr = true)
git("gc").runSilently(silenceErr = true)
}

View file

@ -28,7 +28,7 @@ import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
// Not cached since this is Mojang's server jar
@CacheableTask
abstract class DownloadServerJar : BaseTask() {
@get:Input
@ -41,7 +41,6 @@ abstract class DownloadServerJar : BaseTask() {
abstract val downloader: Property<DownloadService>
@get:Nested
@get:Optional
abstract val expectedHash: Property<Hash>
override fun init() {

View file

@ -59,11 +59,6 @@ abstract class ExtractFromBundler : BaseTask() {
@get:OutputFile
abstract val serverVersionsList: RegularFileProperty
override fun init() {
super.init()
serverJar.set(defaultOutput())
}
@TaskAction
fun run() {
ServerBundler.extractFromBundler(
@ -99,16 +94,16 @@ object ServerBundler {
}
private fun extractServerJar(bundlerZip: Path, serverJar: Path, outputVersionJson: Path?) {
val serverVersionJson = bundlerZip.resolve("version.json")
val serverVersionJson = bundlerZip.resolve(FileEntry.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()
val versions = bundlerZip.resolve(FileEntry.VERSIONS_LIST).readLines()
.map { it.split('\t') }
.associate { it[1] to it[2] }
val serverJarPath = bundlerZip.resolve("/META-INF/versions/${versions[versionId]}")
val serverJarPath = bundlerZip.resolve("${FileEntry.VERSIONS_DIR}/${versions[versionId]}")
serverJar.parent.createDirectories()
serverJarPath.copyTo(serverJar, overwrite = true)
@ -117,7 +112,7 @@ object ServerBundler {
private fun extractLibraryJars(bundlerZip: Path, serverLibraryJars: Path) {
serverLibraryJars.deleteRecursive()
serverLibraryJars.parent.createDirectories()
bundlerZip.resolve("/META-INF/libraries").copyRecursivelyTo(serverLibraryJars)
bundlerZip.resolve(FileEntry.LIBRARIES_DIR).copyRecursivelyTo(serverLibraryJars)
}
private fun writeLibrariesTxt(bundlerZip: Path, serverLibrariesTxt: Path) {

View file

@ -0,0 +1,83 @@
/*
* 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.data.mache.*
import kotlin.io.path.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.TaskAction
abstract class MacheTask : DefaultTask() {
@get:Classpath
abstract val mache: ConfigurableFileCollection
@TaskAction
fun run() {
val meta = getMacheMeta()
println("loaded meta: $meta")
}
private fun getMacheMeta(): MacheMeta {
val metaJson = mutableListOf("")
mache.singleFile.toPath().openZip().use { zip ->
metaJson.addAll(zip.getPath("/mache.json").readLines())
}
return gson.fromJson<MacheMeta>(metaJson.joinToString("\n"))
}
private fun extractVanillaJar() {
// val jar = downloadedJar.convertToPath()
// val out = serverJar.convertToPath().ensureClean()
//
// jar.useZip { root ->
// val metaInf = root.resolve("META-INF")
// val versionsList = metaInf.resolve("versions.list")
// if (versionsList.notExists()) {
// throw Exception("Could not find versions.list")
// }
//
// val lines = versionsList.readLines()
// if (lines.size != 1) {
// throw Exception("versions.list is invalid")
// }
//
// val line = lines.first()
// val parts = line.split(whitespace)
// if (parts.size != 3) {
// throw Exception("versions.list line is invalid")
// }
//
// val serverJarInJar = metaInf.resolve("versions").resolve(parts[2])
// if (serverJarInJar.notExists()) {
// throw Exception("Could not find version jar")
// }
//
// serverJarInJar.copyTo(out)
// }
}
}

View file

@ -34,6 +34,7 @@ import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import writeLF
@CacheableTask
abstract class MergeAccessTransforms : BaseTask() {
@ -67,6 +68,6 @@ abstract class MergeAccessTransforms : BaseTask() {
outputAt.merge(at)
}
AccessTransformFormats.FML.write(outputFile.path, outputAt)
AccessTransformFormats.FML.writeLF(outputFile.path, outputAt)
}
}

View file

@ -32,6 +32,7 @@ import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import writeLF
@CacheableTask
abstract class RemapAccessTransform : BaseTask() {
@ -57,6 +58,6 @@ abstract class RemapAccessTransform : BaseTask() {
val mappingSet = MappingFormats.TINY.read(mappings.path, SPIGOT_NAMESPACE, DEOBF_NAMESPACE)
val resultAt = at.remap(mappingSet)
AccessTransformFormats.FML.write(outputFile.path, resultAt)
AccessTransformFormats.FML.writeLF(outputFile.path, resultAt)
}
}

View file

@ -51,6 +51,7 @@ import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import writeLF
@CacheableTask
abstract class RemapSources : JavaLauncherTask() {
@ -260,7 +261,7 @@ abstract class RemapSources : JavaLauncherTask() {
}
if (generatedAtOutPath != null) {
AccessTransformFormats.FML.write(generatedAtOutPath, processAt)
AccessTransformFormats.FML.writeLF(generatedAtOutPath, processAt)
}
}
}

View file

@ -40,6 +40,7 @@ import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import writeLF
@CacheableTask
abstract class RemapSpigotAt : BaseTask() {
@ -108,7 +109,7 @@ abstract class RemapSpigotAt : BaseTask() {
val mappings = MappingFormats.TINY.read(mapping.path, SPIGOT_NAMESPACE, DEOBF_NAMESPACE)
val remappedAt = outputAt.remap(mappings)
AccessTransformFormats.FML.write(outputFile.path, remappedAt)
AccessTransformFormats.FML.writeLF(outputFile.path, remappedAt)
}
private fun parseAccess(text: String): AccessTransform {

View file

@ -71,6 +71,9 @@ abstract class DownloadTask : DefaultTask() {
fun run() = downloader.get().download(url, outputFile, expectedHash.orNull)
}
@CacheableTask
abstract class CacheableDownloadTask : DownloadTask()
@CacheableTask
abstract class DownloadMcLibraries : BaseTask() {
@ -100,7 +103,7 @@ abstract class DownloadMcLibraries : BaseTask() {
@TaskAction
fun run() {
downloadMinecraftLibraries(
downloadLibraries(
downloader,
workerExecutor,
outputDir.path,
@ -111,20 +114,62 @@ abstract class DownloadMcLibraries : BaseTask() {
}
}
fun downloadMinecraftLibraries(
@CacheableTask
abstract class DownloadPaperLibraries : BaseTask() {
@get:Input
abstract val paperDependencies: ListProperty<String>
@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() {
downloadLibraries(
downloader,
workerExecutor,
outputDir.path,
repositories.get(),
paperDependencies.get(),
sources.get()
)
}
}
fun downloadLibraries(
download: Provider<DownloadService>,
workerExecutor: WorkerExecutor,
targetDir: Path,
repositories: List<String>,
mcLibraries: List<String>,
libraries: List<String>,
sources: Boolean
): WorkQueue {
val excludes = listOf(targetDir.fileSystem.getPathMatcher("glob:*.etag"))
targetDir.deleteRecursive(excludes)
if (!targetDir.exists()) {
targetDir.createDirectories()
}
val queue = workerExecutor.noIsolation()
for (lib in mcLibraries) {
for (lib in libraries) {
if (sources) {
queue.submit(DownloadSourcesToDirAction::class) {
repos.set(repositories)

View file

@ -0,0 +1,112 @@
/*
* 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.mache
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import javax.inject.Inject
import kotlin.io.path.absolutePathString
import kotlin.io.path.deleteIfExists
import kotlin.io.path.name
import kotlin.io.path.outputStream
import kotlin.io.path.writeText
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.ProjectLayout
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.CompileClasspath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.process.ExecOperations
@CacheableTask
abstract class DecompileJar : DefaultTask() {
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFile
abstract val inputJar: RegularFileProperty
@get:Input
abstract val decompilerArgs: ListProperty<String>
@get:CompileClasspath
abstract val minecraftClasspath: ConfigurableFileCollection
@get:Classpath
abstract val decompiler: ConfigurableFileCollection
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Inject
abstract val exec: ExecOperations
@get:Inject
abstract val layout: ProjectLayout
@TaskAction
fun run() {
val out = outputJar.convertToPath().ensureClean()
val cfgFile = layout.buildDirectory.file(DECOMP_CFG).convertToPath().ensureClean()
val cfgText = buildString {
for (file in minecraftClasspath.files) {
append("-e=")
append(file.toPath().absolutePathString())
append(System.lineSeparator())
}
}
cfgFile.writeText(cfgText)
val logs = out.resolveSibling("${out.name}.log")
logs.outputStream().buffered().use { log ->
exec.javaexec {
classpath(decompiler)
mainClass.set("org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler")
maxHeapSize = "3G"
args(decompilerArgs.get())
args("-cfg", cfgFile.absolutePathString())
args(inputJar.convertToPath().absolutePathString())
args(out.absolutePathString())
standardOutput = log
errorOutput = log
}
}
out.openZip().use { root ->
root.getPath("META-INF", "MANIFEST.MF").deleteIfExists()
}
}
}

View file

@ -0,0 +1,106 @@
/*
* 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.mache
import io.papermc.paperweight.util.*
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import org.gradle.process.ExecOperations
@CacheableTask
abstract class RemapJar : DefaultTask() {
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFile
abstract val serverJar: RegularFileProperty
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFile
abstract val serverMappings: RegularFileProperty
@get:Input
abstract val remapperArgs: ListProperty<String>
@get:Classpath
abstract val codebookClasspath: ConfigurableFileCollection
@get:CompileClasspath
abstract val minecraftClasspath: ConfigurableFileCollection
@get:Classpath
abstract val remapperClasspath: ConfigurableFileCollection
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFiles
abstract val paramMappings: ConfigurableFileCollection
@get:Classpath
abstract val constants: ConfigurableFileCollection
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Inject
abstract val exec: ExecOperations
@get:Inject
abstract val layout: ProjectLayout
@TaskAction
fun run() {
val out = outputJar.convertToPath().ensureClean()
val logFile = out.resolveSibling("${out.name}.log")
logFile.outputStream().buffered().use { log ->
exec.javaexec {
classpath(codebookClasspath.singleFile)
maxHeapSize = "2G"
remapperArgs.get().forEach { arg ->
args(
arg
.replace(Regex("\\{tempDir}")) { layout.buildDirectory.dir(".tmp_codebook").get().asFile.absolutePath }
.replace(Regex("\\{remapperFile}")) { remapperClasspath.singleFile.absolutePath }
.replace(Regex("\\{mappingsFile}")) { serverMappings.get().asFile.absolutePath }
.replace(Regex("\\{paramsFile}")) { paramMappings.singleFile.absolutePath }
.replace(Regex("\\{constantsFile}")) { constants.singleFile.absolutePath }
.replace(Regex("\\{output}")) { outputJar.get().asFile.absolutePath }
.replace(Regex("\\{input}")) { serverJar.get().asFile.absolutePath }
.replace(Regex("\\{inputClasspath}")) { minecraftClasspath.files.joinToString(":") { it.absolutePath } }
)
}
standardOutput = log
errorOutput = log
}
}
}
}

View file

@ -0,0 +1,198 @@
/*
* 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.mache
import codechicken.diffpatch.cli.PatchOperation
import codechicken.diffpatch.util.LoggingOutputStream
import codechicken.diffpatch.util.archiver.ArchiveFormat
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.restamp.Restamp
import io.papermc.restamp.RestampContextConfiguration
import io.papermc.restamp.RestampInput
import java.nio.file.Path
import java.util.function.Predicate
import kotlin.io.path.*
import org.cadixdev.at.io.AccessTransformFormats
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.ResetCommand
import org.eclipse.jgit.lib.PersonIdent
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.openrewrite.InMemoryExecutionContext
abstract class SetupVanilla : BaseTask() {
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFile
abstract val inputFile: RegularFileProperty
@get:Internal
abstract val predicate: Property<Predicate<Path>>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Internal
abstract val machePatches: DirectoryProperty
@get:Optional
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val ats: RegularFileProperty
@get:Optional
@get:InputFiles
abstract val libraries: ConfigurableFileCollection
@get:Optional
@get:InputFiles
abstract val paperPatches: ConfigurableFileCollection
@get:Optional
@get:InputFile
abstract val devImports: RegularFileProperty
@get:Optional
@get:CompileClasspath
abstract val minecraftClasspath: ConfigurableFileCollection
@get:Optional
@get:Classpath
abstract val mache: ConfigurableFileCollection
@TaskAction
fun run() {
val outputPath = outputDir.convertToPath()
val git: Git
if (outputPath.resolve(".git/HEAD").isRegularFile()) {
git = Git.open(outputPath.toFile())
git.reset().setRef("ROOT").setMode(ResetCommand.ResetType.HARD).call()
} else {
outputPath.createDirectories()
git = Git.init()
.setDirectory(outputPath.toFile())
.setInitialBranch("main")
.call()
val rootIdent = PersonIdent("ROOT", "root@automated.papermc.io")
git.commit().setMessage("ROOT").setAllowEmpty(true).setAuthor(rootIdent).setSign(false).call()
git.tagDelete().setTags("ROOT").call()
git.tag().setName("ROOT").setTagger(rootIdent).setSigned(false).call()
}
println("Copy initial sources...")
inputFile.convertToPath().openZip().walk()
.filter(predicate.get())
.forEach {
val target = outputPath.resolve(it.toString().substring(1))
target.parent.createDirectories()
// make sure we have a trailing newline
var content = it.readText()
if (!content.endsWith("\n")) {
content += "\n"
}
target.writeText(content)
}
println("Setup git repo...")
commitAndTag(git, "Vanilla")
if (machePatches.isPresent) {
println("Applying mache patches...")
val result = PatchOperation.builder()
.logTo(LoggingOutputStream(logger, LogLevel.LIFECYCLE))
.basePath(outputPath.convertToPath())
.outputPath(outputPath.convertToPath())
.patchesPath(mache.singleFile.toPath(), ArchiveFormat.ZIP)
.patchesPrefix("patches")
.level(codechicken.diffpatch.util.LogLevel.INFO)
.ignorePrefix(".git")
.build()
.operate()
commitAndTag(git, "Mache")
if (result.exit != 0) {
throw Exception("Failed to apply ${result.summary.failedMatches} mache patches")
}
logger.lifecycle("Applied ${result.summary.changedFiles} mache patches")
}
if (ats.isPresent) {
val classPath = minecraftClasspath.files.map { it.toPath() }.toMutableList()
classPath.add(outputPath)
println("Applying access transformers...")
val configuration = RestampContextConfiguration.builder()
.accessTransformers(ats.convertToPath(), AccessTransformFormats.FML)
.sourceRoot(outputPath)
.sourceFilesFromAccessTransformers(false)
.classpath(classPath)
.executionContext(InMemoryExecutionContext { it.printStackTrace() })
.failWithNotApplicableAccessTransformers()
.build()
val parsedInput = RestampInput.parseFrom(configuration)
val results = Restamp.run(parsedInput).allResults
results.forEach { result ->
if (result.after != null) {
outputPath.resolve(result.after!!.sourcePath).writeText(result.after!!.printAll())
}
}
commitAndTag(git, "ATs")
}
if (!libraries.isEmpty && !paperPatches.isEmpty) {
val patches = paperPatches.files.flatMap { it.toPath().walk().filter { path -> path.toString().endsWith(".patch") }.toList() }
McDev.importMcDev(patches, null, devImports.pathOrNull, outputPath, null, libraries.files.map { it.toPath() }, true, "")
commitAndTag(git, "Imports")
}
git.close()
}
private fun commitAndTag(git: Git, name: String) {
val vanillaIdent = PersonIdent(name, "${name.lowercase()}@automated.papermc.io")
git.add().addFilepattern(".").call()
git.commit()
.setMessage(name)
.setAuthor(vanillaIdent)
.setSign(false)
.call()
git.tagDelete().setTags(name).call()
git.tag().setName(name).setTagger(vanillaIdent).setSigned(false).call()
}
}

View file

@ -0,0 +1,76 @@
/*
* 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.softspoon
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import javax.inject.Inject
import org.gradle.api.file.DirectoryProperty
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.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.UntrackedTask
@UntrackedTask(because = "Always apply patches")
abstract class ApplyFeaturePatches : ControllableOutputTask() {
@get:PathSensitive(PathSensitivity.NONE)
@get:InputDirectory
abstract val repo: DirectoryProperty
@get:PathSensitive(PathSensitivity.NONE)
@get:InputDirectory
abstract val patches: DirectoryProperty
@get:Inject
abstract val providers: ProviderFactory
@get:Input
abstract val verbose: Property<Boolean>
override fun init() {
printOutput.convention(false).finalizeValueOnRead()
verbose.convention(false)
}
@TaskAction
fun run() {
Git.checkForGit()
val repoPath = repo.convertToPath()
val git = Git(repoPath)
if (git("checkout", "main").runSilently(silenceErr = true) != 0) {
git("checkout", "-b", "main").runSilently(silenceErr = true)
}
git("reset", "--hard", "file").executeSilently(silenceErr = true)
git("gc").runSilently(silenceErr = true)
applyGitPatches(git, "server repo", repoPath, patches.convertToPath(), printOutput.get(), verbose.get())
}
}

View file

@ -0,0 +1,142 @@
/*
* 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.softspoon
import codechicken.diffpatch.cli.PatchOperation
import codechicken.diffpatch.match.FuzzyLineMatcher
import codechicken.diffpatch.util.LoggingOutputStream
import codechicken.diffpatch.util.PatchMode
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import java.io.PrintStream
import java.nio.file.Path
import kotlin.io.path.*
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.PersonIdent
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.logging.LogLevel
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.UntrackedTask
import org.gradle.api.tasks.options.Option
@UntrackedTask(because = "Always apply patches")
abstract class ApplyFilePatches : BaseTask() {
@get:Input
@get:Option(
option = "verbose",
description = "Prints out more info about the patching process",
)
abstract val verbose: Property<Boolean>
@get:PathSensitive(PathSensitivity.NONE)
@get:InputDirectory
abstract val input: DirectoryProperty
@get:OutputDirectory
abstract val output: DirectoryProperty
@get:PathSensitive(PathSensitivity.NONE)
@get:InputDirectory
abstract val patches: DirectoryProperty
init {
run {
verbose.convention(false)
}
}
@TaskAction
open fun run() {
io.papermc.paperweight.util.Git.checkForGit()
val outputPath = output.convertToPath()
recreateCloneDirectory(outputPath)
checkoutRepoFromUpstream(Git(outputPath), input.convertToPath(), "main", "mache", "main")
setupGitHook(outputPath)
val printStream = PrintStream(LoggingOutputStream(logger, LogLevel.LIFECYCLE))
val result = PatchOperation.builder()
.logTo(printStream)
.basePath(output.convertToPath())
.patchesPath(patches.convertToPath())
.outputPath(output.convertToPath())
.level(if (verbose.get()) codechicken.diffpatch.util.LogLevel.ALL else codechicken.diffpatch.util.LogLevel.INFO)
.mode(mode())
.minFuzz(minFuzz())
.summary(verbose.get())
.lineEnding("\n")
.ignorePrefix(".git")
.build()
.operate()
commit()
if (result.exit != 0) {
val total = result.summary.failedMatches + result.summary.exactMatches +
result.summary.accessMatches + result.summary.offsetMatches + result.summary.fuzzyMatches
throw Exception("Failed to apply ${result.summary.failedMatches}/$total hunks")
}
if (!verbose.get()) {
logger.lifecycle("Applied ${result.summary.changedFiles} patches")
}
}
private fun setupGitHook(outputPath: Path) {
val hook = outputPath.resolve(".git/hooks/post-rewrite")
hook.parent.createDirectories()
hook.writeText(javaClass.getResource("/post-rewrite.sh")!!.readText())
hook.toFile().setExecutable(true)
}
private fun commit() {
val ident = PersonIdent("File", "filepatches@automated.papermc.io")
val git = Git.open(output.convertToPath().toFile())
git.add().addFilepattern(".").call()
git.commit()
.setMessage("File Patches")
.setAuthor(ident)
.setSign(false)
.call()
git.tagDelete().setTags("file").call()
git.tag().setName("file").setTagger(ident).setSigned(false).call()
git.close()
}
internal open fun mode(): PatchMode {
return PatchMode.OFFSET
}
internal open fun minFuzz(): Float {
return FuzzyLineMatcher.DEFAULT_MIN_MATCH_SCORE
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.softspoon
import codechicken.diffpatch.util.PatchMode
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.UntrackedTask
import org.gradle.api.tasks.options.Option
@UntrackedTask(because = "Always apply patches")
abstract class ApplyFilePatchesFuzzy : ApplyFilePatches() {
@get:Input
@get:Option(
option = "min-fuzz",
description = "Min fuzz. The minimum quality needed for a patch to be applied. Default is 0.5.",
)
abstract val minFuzz: Property<String>
init {
run {
minFuzz.convention("0.5")
}
}
override fun mode(): PatchMode {
return PatchMode.FUZZY
}
override fun minFuzz(): Float {
return minFuzz.get().toFloat()
}
}

View file

@ -0,0 +1,135 @@
/*
* 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.softspoon
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.restamp.Restamp
import io.papermc.restamp.RestampContextConfiguration
import io.papermc.restamp.RestampInput
import java.nio.file.Files
import javax.inject.Inject
import kotlin.io.path.*
import org.cadixdev.at.io.AccessTransformFormats
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
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
import org.openrewrite.InMemoryExecutionContext
@CacheableTask
abstract class ApplySourceAT : BaseTask() {
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Optional
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val atFile: RegularFileProperty
@get:Optional
@get:CompileClasspath
abstract val minecraftClasspath: ConfigurableFileCollection
@get:Inject
abstract val worker: WorkerExecutor
override fun init() {
outputJar.convention(defaultOutput())
}
@TaskAction
fun run() {
val queue = worker.processIsolation {
forkOptions {
maxHeapSize = "2G"
}
}
val classPath = minecraftClasspath.files.map { it.toPath() }.toMutableList()
classPath.add(inputJar.convertToPath())
queue.submit(RestampWorker::class) {
minecraftClasspath.from(minecraftClasspath)
atFile.set(atFile)
inputJar.set(inputJar)
outputJar.set(outputJar)
}
}
}
abstract class RestampWorker : WorkAction<RestampWorker.Params> {
interface Params : WorkParameters {
val minecraftClasspath: ConfigurableFileCollection
val atFile: RegularFileProperty
val inputJar: RegularFileProperty
val outputJar: RegularFileProperty
}
override fun execute() {
val inputZip = parameters.inputJar.convertToPath().openZip()
val classPath = parameters.minecraftClasspath.files.map { it.toPath() }.toMutableList()
classPath.add(parameters.inputJar.convertToPath())
val configuration = RestampContextConfiguration.builder()
.accessTransformers(parameters.atFile.convertToPath(), AccessTransformFormats.FML)
.sourceRoot(inputZip.getPath("/"))
.sourceFilesFromAccessTransformers()
.classpath(classPath)
.executionContext(InMemoryExecutionContext { it.printStackTrace() })
.failWithNotApplicableAccessTransformers()
.build()
val parsedInput = RestampInput.parseFrom(configuration)
val results = Restamp.run(parsedInput).allResults
parameters.outputJar.convertToPath().writeZip().use { zip ->
val alreadyWritten = mutableSetOf<String>()
results.forEach { result ->
if (result.after == null) {
println("Ignoring ${result.before?.sourcePath} because result.after is null?")
return@forEach
}
result.after?.let { after ->
zip.getPath(after.sourcePath.toString()).writeText(after.printAll())
alreadyWritten.add("/" + after.sourcePath.toString())
}
}
inputZip.walk().filter { Files.isRegularFile(it) }.filter { !alreadyWritten.contains(it.toString()) }.forEach { file ->
zip.getPath(file.toString()).writeText(file.readText())
}
zip.close()
}
inputZip.close()
}
}

View file

@ -0,0 +1,210 @@
/*
* 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.softspoon
import atFromString
import codechicken.diffpatch.cli.DiffOperation
import codechicken.diffpatch.util.LogLevel
import codechicken.diffpatch.util.LoggingOutputStream
import io.papermc.paperweight.tasks.*
import io.papermc.paperweight.util.*
import io.papermc.restamp.Restamp
import io.papermc.restamp.RestampContextConfiguration
import io.papermc.restamp.RestampInput
import java.io.PrintStream
import java.nio.file.Path
import kotlin.io.path.*
import org.cadixdev.at.AccessTransformSet
import org.cadixdev.at.io.AccessTransformFormats
import org.cadixdev.bombe.type.signature.MethodSignature
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.api.tasks.options.Option
import org.openrewrite.InMemoryExecutionContext
import writeLF
@UntrackedTask(because = "Always rebuild patches")
abstract class RebuildFilePatches : BaseTask() {
@get:Input
@get:Option(
option = "verbose",
description = "Prints out more info about the patching process",
)
abstract val verbose: Property<Boolean>
@get:InputDirectory
abstract val input: DirectoryProperty
@get:InputDirectory
abstract val base: DirectoryProperty
@get:OutputDirectory
abstract val patches: DirectoryProperty
@get:Optional
@get:InputFile
abstract val atFile: RegularFileProperty
@get:Optional
@get:OutputFile
abstract val atFileOut: RegularFileProperty
@get:Optional
@get:CompileClasspath
abstract val minecraftClasspath: ConfigurableFileCollection
@get:Input
abstract val contextLines: Property<Int>
override fun init() {
contextLines.convention(3)
verbose.convention(false)
}
@TaskAction
fun run() {
val patchDir = patches.convertToPath().ensureClean()
patchDir.createDirectory()
val inputDir = input.convertToPath()
val baseDir = base.convertToPath()
val oldAts = if (atFile.isPresent) AccessTransformFormats.FML.read(atFile.convertToPath()) else AccessTransformSet.create()
val git = Git(inputDir)
git("stash", "push").executeSilently(silenceErr = true)
git("checkout", "file").executeSilently(silenceErr = true)
// handle AT
baseDir.walk()
.map { it.relativeTo(baseDir).toString().replace("\\", "/") }
.filter {
!it.startsWith(".git") && !it.endsWith(".nbt") && !it.endsWith(".mcassetsroot")
}
.forEach {
val ats = AccessTransformSet.create()
val source = inputDir.resolve(it)
val decomp = baseDir.resolve(it)
val className = it.replace(".java", "")
handleATInSource(source, ats, className)
handleATInBase(decomp, ats, baseDir)
oldAts.merge(ats)
}
if (atFileOut.isPresent) {
AccessTransformFormats.FML.writeLF(
atFileOut.convertToPath(),
oldAts,
"# This file is auto generated, any changes may be overridden!\n# See CONTRIBUTING.md on how to add access transformers.\n"
)
}
// rebuild patches
val printStream = PrintStream(LoggingOutputStream(logger, org.gradle.api.logging.LogLevel.LIFECYCLE))
val result = DiffOperation.builder()
.logTo(printStream)
.aPath(baseDir)
.bPath(inputDir)
.outputPath(patchDir)
.autoHeader(true)
.level(if (verbose.get()) LogLevel.ALL else LogLevel.INFO)
.lineEnding("\n")
.ignorePrefix(".git")
.ignorePrefix("data/minecraft/structures")
.ignorePrefix("data/.mc")
.ignorePrefix("assets/.mc")
.context(contextLines.get())
.summary(verbose.get())
.build()
.operate()
git("switch", "-").executeSilently(silenceErr = true)
git("stash", "pop").runSilently(silenceErr = true)
logger.lifecycle("Rebuilt ${result.summary.changedFiles} patches")
}
private fun handleATInBase(decomp: Path, newAts: AccessTransformSet, decompRoot: Path) {
if (newAts.classes.isEmpty()) {
return
}
val configuration = RestampContextConfiguration.builder()
.accessTransformSet(newAts)
.sourceRoot(decompRoot)
.sourceFiles(listOf(decomp))
.classpath(minecraftClasspath.files.map { it.toPath() })
.executionContext(InMemoryExecutionContext { it.printStackTrace() })
.build()
val parsedInput = RestampInput.parseFrom(configuration)
val results = Restamp.run(parsedInput).allResults
if (results.size != 1) {
logger.lifecycle("Failed to apply AT to ${decomp.fileName} (doesn't it already exist?): $results")
return
}
val result = results[0].after?.printAll()
if (result != null) {
decomp.writeText(result, Charsets.UTF_8)
}
}
private fun handleATInSource(source: Path, newAts: AccessTransformSet, className: String) {
val sourceLines = source.readLines()
val fixedLines = ArrayList<String>(sourceLines.size)
var requiresWrite = false
sourceLines.forEach { line ->
if (!line.contains("// Paper-AT: ")) {
fixedLines.add(line)
return@forEach
}
requiresWrite = true
val split = line.split("// Paper-AT: ")
val at = split[1]
val atClass = newAts.getOrCreateClass(className)
val parts = at.split(" ")
val accessTransform = atFromString(parts[0])
val name = parts[1]
val index = name.indexOf('(')
if (index == -1) {
atClass.mergeField(name, accessTransform)
} else {
atClass.mergeMethod(MethodSignature.of(name.substring(0, index), name.substring(index)), accessTransform)
}
logger.lifecycle("Found new AT in $className: $at -> $accessTransform")
fixedLines.add(split[0])
}
if (requiresWrite) {
source.writeText(fixedLines.joinToString("\n", postfix = "\n"), Charsets.UTF_8)
}
}
}

View file

@ -35,16 +35,17 @@ object McDev {
fun importMcDev(
patches: Iterable<Path>,
decompJar: Path,
decompJar: Path?,
importsFile: Path?,
targetDir: Path,
dataTargetDir: Path? = null,
librariesDirs: List<Path> = listOf(),
printOutput: Boolean = true
printOutput: Boolean = true,
javaSourceSet: String = "/src/main/java"
) {
val (javaPatchLines, dataPatchLines) = readPatchLines(patches)
val (javaPatchLines, dataPatchLines) = readPatchLines(patches, javaSourceSet)
decompJar.openZip().use { zipFile ->
decompJar?.openZip()?.use { zipFile ->
val decompSourceFiles = mutableSetOf<String>()
val decompDataFiles = mutableSetOf<String>()
@ -127,7 +128,7 @@ object McDev {
}
// Import library classes
val imports = findLibraries(importsFile, libFiles, javaPatchLines)
val imports = findLibraries(importsFile, libFiles, javaPatchLines, javaSourceSet)
logger.log(if (printOutput) LogLevel.LIFECYCLE else LogLevel.DEBUG, "Importing {} classes from library sources...", imports.size)
for ((libraryFileName, importFilePath) in imports) {
@ -170,11 +171,11 @@ object McDev {
}
}
private fun readPatchLines(patches: Iterable<Path>): Pair<Set<String>, Set<String>> {
private fun readPatchLines(patches: Iterable<Path>, javaSourceSet: String): Pair<Set<String>, Set<String>> {
val srcResult = hashSetOf<String>()
val dataResult = hashSetOf<String>()
val javaPrefix = "+++ b/src/main/java/"
val javaPrefix = "+++ b$javaSourceSet/"
val dataPrefix = "+++ b/src/main/resources/data/minecraft/"
for (patch in patches) {
@ -216,7 +217,7 @@ object McDev {
return Pair(srcResult, dataResult)
}
private fun findLibraries(libraryImports: Path?, libFiles: List<Path>, patchLines: Set<String>): Set<LibraryImport> {
private fun findLibraries(libraryImports: Path?, libFiles: List<Path>, patchLines: Set<String>, javaSourceSet: String): Set<LibraryImport> {
val result = hashSetOf<LibraryImport>()
// Imports from library-imports.txt
@ -233,15 +234,15 @@ object McDev {
}
// Scan patches for necessary imports
result += findNeededLibraryImports(patchLines, libFiles)
result += findNeededLibraryImports(patchLines, libFiles, javaSourceSet)
return result
}
private fun findNeededLibraryImports(patchLines: Set<String>, libFiles: List<Path>): Set<LibraryImport> {
private fun findNeededLibraryImports(patchLines: Set<String>, libFiles: List<Path>, javaSourceSet: String): Set<LibraryImport> {
val knownImportMap = findPossibleLibraryImports(libFiles)
.associateBy { it.importFilePath }
val prefix = "+++ b/src/main/java/"
val prefix = "+++ b$javaSourceSet/"
return patchLines.map { it.substringAfter(prefix) }
.mapNotNull { knownImportMap[it] }
.toSet()

View file

@ -0,0 +1,77 @@
/*
* 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 java.io.BufferedWriter
import java.io.StringWriter
import java.nio.file.Path
import kotlin.io.path.*
import org.cadixdev.at.AccessChange
import org.cadixdev.at.AccessTransform
import org.cadixdev.at.AccessTransformSet
import org.cadixdev.at.ModifierChange
import org.cadixdev.at.io.AccessTransformFormat
import org.cadixdev.at.io.AccessTransformFormats
fun atFromString(input: String): AccessTransform {
var last = input.length - 1
val final = if (input[last] == 'f') {
if (input[--last] == '-') ModifierChange.REMOVE else ModifierChange.ADD
} else {
ModifierChange.NONE
}
val access = when (input.split("+", "-").first()) {
"public" -> AccessChange.PUBLIC
"protected" -> AccessChange.PROTECTED
"private" -> AccessChange.PRIVATE
else -> AccessChange.NONE
}
return AccessTransform.of(access, final)
}
fun atToString(at: AccessTransform): String {
val access = when (at.access) {
AccessChange.PRIVATE -> "private"
AccessChange.PROTECTED -> "protected"
AccessChange.PUBLIC -> "public"
else -> ""
}
val final = when (at.final) {
ModifierChange.REMOVE -> "-f"
ModifierChange.ADD -> "+f"
else -> ""
}
return access + final
}
fun AccessTransformFormat.writeLF(path: Path, at: AccessTransformSet, header: String = "") {
val stringWriter = StringWriter()
val writer = BufferedWriter(stringWriter)
AccessTransformFormats.FML.write(writer, at)
writer.close()
// unify line endings
val lines = stringWriter.toString().replace("\r\n", "\n").split("\n")
// remove last empty line, the sort, then add it back
path.writeText(lines.subList(0, lines.size - 1).sorted().joinToString("\n", header, "\n"), Charsets.UTF_8)
}

View file

@ -44,6 +44,7 @@ const val REMAPPER_CONFIG = "remapper"
const val PLUGIN_REMAPPER_CONFIG = "pluginRemapper"
const val DECOMPILER_CONFIG = "decompiler"
const val PAPERCLIP_CONFIG = "paperclip"
const val MACHE_CONFIG = "mache"
const val DEV_BUNDLE_CONFIG = "paperweightDevelopmentBundle"
const val MOJANG_MAPPED_SERVER_CONFIG = "mojangMappedServer"
const val MOJANG_MAPPED_SERVER_RUNTIME_CONFIG = "mojangMappedServerRuntime"
@ -54,6 +55,7 @@ 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 MACHE_REPO_NAME = "paperweightMacheRepository"
const val CACHE_PATH = "caches"
private const val PAPER_PATH = "paperweight"
@ -77,6 +79,9 @@ 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"
const val PAPER_JARS_PATH = "$JARS_PATH/paper"
const val PAPER_SOURCES_JARS_PATH = "$JARS_PATH/paper-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"
@ -90,10 +95,12 @@ const val PATCHED_SPIGOT_MOJANG_YARN_SOURCE_MAPPINGS = "$MAPPINGS_DIR/spigot-moj
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 SPIGOT_MOJANG_PARCHMENT_MAPPINGS = "$MAPPINGS_DIR/spigot-mojang+parchment.tiny"
const val OBF_NAMESPACE = "official"
const val SPIGOT_NAMESPACE = "spigot"
const val DEOBF_NAMESPACE = "mojang+yarn"
const val NEW_DEOBF_NAMESPACE = "mojang+parchment"
const val MAPPINGS_NAMESPACE_MANIFEST_KEY = "paperweight-mappings-namespace"
private const val DATA_PATH = "$PAPER_PATH/data"
@ -105,13 +112,17 @@ 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"
const val SERVER_JAR = "$BUNDLER_PATH/server.jar"
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_REMAPPED_CODEBOOK_JAR = "$TASK_CACHE/codebook-minecraft.jar"
const val FINAL_FILTERED_REMAPPED_JAR = "$TASK_CACHE/filteredMinecraft.jar"
const val FINAL_DECOMPILE_JAR = "$TASK_CACHE/decompileJar.jar"
const val SPIGOT_MACHE_DECOMPILE_JAR = "$TASK_CACHE/macheSpigotDecompileJar.jar"
const val DECOMP_CFG = "$TASK_CACHE/decomp_cfg.txt"
const val MC_DEV_SOURCES_DIR = "$PAPER_PATH/mc-dev-sources"
@ -119,6 +130,13 @@ const val IVY_REPOSITORY = "$PAPER_PATH/ivyRepository"
const val DOWNLOAD_SERVICE_NAME = "paperweightDownloadService"
private const val MACHE_PATH = "$PAPER_PATH/mache"
const val PATCHED_JAR = "$MACHE_PATH/patched.jar"
const val FAILED_PATCH_JAR = "$MACHE_PATH/failed.jar"
const val PATCHES_FOLDER = "$MACHE_PATH/patches"
const val BASE_PROJECT = "$MACHE_PATH/base"
const val REMAPPED_CB = "$MACHE_PATH/remapped-cb"
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

@ -0,0 +1,28 @@
/*
* 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.mache
data class MacheAdditionalDependencies(
val compileOnly: List<MavenArtifact>? = null,
val implementation: List<MavenArtifact>? = null,
)

View file

@ -0,0 +1,31 @@
/*
* 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.mache
data class MacheDependencies(
val codebook: List<MavenArtifact>,
val paramMappings: List<MavenArtifact>,
val constants: List<MavenArtifact>,
val remapper: List<MavenArtifact>,
val decompiler: List<MavenArtifact>,
)

View file

@ -0,0 +1,33 @@
/*
* 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.mache
data class MacheMeta(
val minecraftVersion: String,
val macheVersion: String,
val dependencies: MacheDependencies,
val repositories: List<MacheRepository>,
val decompilerArgs: List<String>,
val remapperArgs: List<String>,
val additionalCompileDependencies: MacheAdditionalDependencies? = null,
)

View file

@ -0,0 +1,29 @@
/*
* 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.mache
data class MacheRepository(
val url: String,
val name: String,
val groups: List<String>? = null,
)

View file

@ -0,0 +1,35 @@
/*
* 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.mache
data class MavenArtifact(
val group: String,
val name: String,
val version: String,
val classifier: String? = null,
val extension: String? = null,
) {
fun toMavenString(): String {
return "$group:$name:$version" + (classifier?.let { ":$it" } ?: "") + (extension?.let { "@$it" } ?: "")
}
}

View file

@ -26,6 +26,7 @@ import io.papermc.paperweight.PaperweightException
import java.io.InputStream
import java.net.URI
import java.nio.file.FileSystem
import java.nio.file.FileSystemNotFoundException
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Path
@ -35,6 +36,9 @@ import java.util.Arrays
import java.util.stream.Collectors
import java.util.stream.Stream
import java.util.stream.StreamSupport
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import kotlin.io.path.*
import kotlin.streams.asSequence
import org.gradle.api.Project
@ -142,13 +146,41 @@ private fun Path.jarUri(): URI {
}
fun Path.openZip(): FileSystem {
return FileSystems.newFileSystem(jarUri(), emptyMap<String, Any>())
return try {
FileSystems.getFileSystem(jarUri())
} catch (e: FileSystemNotFoundException) {
FileSystems.newFileSystem(jarUri(), emptyMap<String, Any>())
}
}
fun Path.writeZip(): FileSystem {
return FileSystems.newFileSystem(jarUri(), mapOf("create" to "true"))
}
inline fun Path.writeZipStream(func: (ZipOutputStream) -> Unit) {
ZipOutputStream(this.outputStream().buffered()).use(func)
}
inline fun Path.readZipStream(func: (ZipInputStream, ZipEntry) -> Unit) {
ZipInputStream(this.inputStream().buffered()).use { zis ->
var entry = zis.nextEntry
while (entry != null) {
func(zis, entry)
entry = zis.nextEntry
}
}
}
fun copyEntry(input: InputStream, output: ZipOutputStream, entry: ZipEntry) {
val newEntry = ZipEntry(entry)
output.putNextEntry(newEntry)
try {
input.copyTo(output)
} finally {
output.closeEntry()
}
}
fun FileSystem.walk(): Stream<Path> {
return StreamSupport.stream(rootDirectories.spliterator(), false)
.flatMap { Files.walk(it) }

View file

@ -102,6 +102,17 @@ class Git(private val repo: Path, private val env: Map<String, String> = emptyMa
throw PaperweightException("You must have git installed and available on your PATH in order to use paperweight.")
}
fun checkForGitRepo(directory: Path): Boolean {
try {
val proc = ProcessBuilder("git", "status").redirectErrorStream(true).directory(directory).start()
proc.inputStream.copyTo(UselessOutputStream)
if (proc.waitFor() == 0) {
return true
}
} catch (_: Exception) {}
return false
}
}
}

View file

@ -74,6 +74,8 @@ import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.jvm.toolchain.JavaToolchainService
import org.gradle.kotlin.dsl.*
val whitespace = Regex("\\s+")
val gson: Gson = GsonBuilder().disableHtmlEscaping().setPrettyPrinting().registerTypeHierarchyAdapter(Path::class.java, PathJsonConverter()).create()
class PathJsonConverter : JsonDeserializer<Path?>, JsonSerializer<Path?> {
@ -107,7 +109,9 @@ fun ProjectLayout.maybeInitSubmodules(offline: Boolean, logger: Logger) {
fun ProjectLayout.initSubmodules() {
Git.checkForGit()
Git(projectDirectory.path)("submodule", "update", "--init").executeOut()
if (Git.checkForGitRepo(projectDirectory.path)) {
Git(projectDirectory.path)("submodule", "update", "--init").executeOut()
}
}
fun Project.offlineMode(): Boolean = gradle.startParameter.isOffline
@ -201,6 +205,18 @@ fun Any.convertToPath(): Path {
}
}
fun Path.ensureClean(): Path {
try {
deleteRecursively()
} catch (e: Exception) {
println("Failed to delete $this: ${e.javaClass.name}: ${e.message}")
e.suppressedExceptions.forEach { println("Suppressed exception: $it") }
throw PaperweightException("Failed to delete $this", e)
}
parent.createDirectories()
return this
}
fun Any.convertToFileProvider(layout: ProjectLayout, providers: ProviderFactory): Provider<RegularFile> {
return when (this) {
is Path -> layout.file(providers.provider { toFile() })

View file

@ -0,0 +1,11 @@
#!/bin/sh
input=$(cat) # <old-object> SP <new-object> [SP <extra-info>] LF
fileCommit=$(git rev-list -n 1 file) # current commit tagged as "file"
oldObject=$(echo $input | cut -d' ' -f1) # <old-object>
newObject=$(echo $input | cut -d' ' -f2) # <new-object>
if [ $oldObject = $fileCommit ]; then
git tag -d file > /dev/null # delete old tag (silent)
git tag file "$newObject" --no-sign
echo "Updated tag 'file' from $oldObject to $newObject"
fi

View file

@ -0,0 +1,83 @@
/*
* 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.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import java.nio.file.Path
import kotlin.test.BeforeTest
import kotlin.test.Test
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkQueue
import org.gradle.workers.WorkerExecutor
import org.junit.jupiter.api.io.TempDir
class ApplyAccessTransformTest : TaskTest() {
private lateinit var task: ApplyAccessTransform
private val workerExecutor: WorkerExecutor = mockk()
private val workQueue: WorkQueue = mockk()
@BeforeTest
fun setup() {
val project = setupProject()
project.apply(plugin = "java")
task = project.tasks.register("applyAccessTransform", ApplyAccessTransform::class).get()
mockkObject(task)
every { task.workerExecutor } returns workerExecutor
every { workerExecutor.processIsolation(any()) } returns workQueue
every { workQueue.submit(ApplyAccessTransform.AtlasAction::class, any()) } answers {
val action = object : ApplyAccessTransform.AtlasAction() {
override fun getParameters(): ApplyAccessTransform.AtlasParameters {
return mockk<ApplyAccessTransform.AtlasParameters>().also {
every { it.inputJar.get() } returns task.inputJar.get()
every { it.atFile.get() } returns task.atFile.get()
every { it.outputJar.get() } returns task.outputJar.get()
}
}
}
action.execute()
}
}
@Test
fun `should apply access transform`(@TempDir tempDir: Path) {
val testResource = Path.of("src/test/resources/apply_access_transform")
val testInput = testResource.resolve("input")
val input = createJar(tempDir, testInput, "Test").toFile()
val output = tempDir.resolve("output.jar").toFile()
val atFile = testInput.resolve("ats.at").toFile()
task.inputJar.set(input)
task.outputJar.set(output)
task.atFile.set(atFile)
task.run()
val testOutput = testResource.resolve("output")
compareJar(tempDir, testOutput, "output", "Test")
}
}

View file

@ -0,0 +1,186 @@
/*
* 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.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.*
import org.eclipse.jgit.api.Git
import org.gradle.testfixtures.ProjectBuilder
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
open class TaskTest {
fun setupProject() = ProjectBuilder.builder()
.withGradleUserHomeDir(File("build"))
.withProjectDir(File(""))
.build()
fun setupDir(tempDir: Path, testResource: Path, name: String): Path {
val temp = tempDir.resolve(name).toFile()
temp.mkdir()
testResource.resolve(name).copyRecursivelyTo(temp.convertToPath())
return temp.convertToPath()
}
fun setupFile(tempDir: Path, testResource: Path, name: String): Path {
val temp = tempDir.resolve(name)
testResource.resolve(name).copyTo(temp)
return temp
}
fun compareDir(tempDir: Path, testResource: Path, name: String) {
val actualOutput = tempDir.resolve(name)
val expectedOutput = testResource.resolve(name)
val expectedFiles = expectedOutput.walk().filter { Files.isRegularFile(it) }.filter { !it.toString().contains(".git") }.toList()
val actualFiles = actualOutput.walk().filter { Files.isRegularFile(it) }.filter { !it.toString().contains(".git") }.toList()
assertEquals(expectedFiles.size, actualFiles.size, "Expected $expectedFiles files, got $actualFiles")
expectedFiles.forEach { expectedFile ->
val actualFile = actualOutput.resolve(expectedOutput.relativize(expectedFile))
compareFile(actualFile, expectedFile)
}
}
fun compareZip(tempDir: Path, testResource: Path, name: String) {
val actualOutput = tempDir.resolve(name)
val expectedOutput = testResource.resolve(name)
compareZip(actualOutput, expectedOutput)
}
fun compareZip(actualOutput: Path, expectedOutput: Path) {
val actualZip = actualOutput.openZip()
val actualFiles = actualZip.walk().filter { Files.isRegularFile(it) }.toList()
val expectedZip = expectedOutput.openZip()
val expectedFiles = expectedZip.walk().filter { Files.isRegularFile(it) }.toList()
assertEquals(expectedFiles.size, actualFiles.size, "Expected $expectedFiles files, got $actualFiles")
expectedFiles.forEach { expectedFile ->
val actualFile = actualZip.getPath(expectedFile.toString())
compareFile(actualFile, expectedFile)
}
}
fun compareFile(tempDir: Path, testResource: Path, name: String) {
val actualOutput = tempDir.resolve(name)
val expectedOutput = testResource.resolve(name)
compareFile(actualOutput, expectedOutput)
}
private fun compareFile(actual: Path, expected: Path) {
assertTrue(actual.exists(), "Expected file $actual doesn't exist")
assertEquals(expected.readText(), actual.readText(), "File $actual doesn't match expected")
}
fun setupGitRepo(directory: File, mainBranch: String, tag: String? = null) {
val git = Git.init().setDirectory(directory).setInitialBranch(mainBranch).call()
git.add().addFilepattern(".").call()
git.commit().setMessage("Test").call()
if (tag != null) {
git.tag().setName(tag).call()
}
git.close()
}
fun createZip(tempDir: Path, testResource: Path, zipName: String, vararg fileNames: String,): Path {
val targetZip = tempDir.resolve(zipName)
targetZip.writeZip().use { zip ->
fileNames.forEach { fileName ->
val sourceFile = testResource.resolve(fileName)
zip.getPath(fileName).writeText(sourceFile.readText())
}
}
return targetZip
}
fun createJar(tempDir: Path, testResource: Path, name: String): Path {
val sourceFile = tempDir.resolve("$name.java")
testResource.resolve("$name.java").copyTo(sourceFile)
// run javac on the file
ProcessBuilder()
.directory(tempDir.toFile())
.command("javac", sourceFile.toString())
.redirectErrorStream(true)
.start()
.waitFor()
// create jar
ProcessBuilder()
.directory(tempDir.toFile())
.command("jar", "-cf", "$name.jar", "$name.class")
.redirectErrorStream(true)
.start()
.waitFor()
return tempDir.resolve("$name.jar")
}
fun compareJar(tempDir: Path, testResource: Path, fileName: String, className: String) {
val outputJar = tempDir.resolve("$fileName.jar")
val expectedOutputFile = testResource.resolve("$fileName.javap")
// unpack jar
ProcessBuilder()
.directory(tempDir.toFile())
.command("jar", "-xf", outputJar.toString())
.redirectErrorStream(true)
.start()
.waitFor()
// disassemble class
val process = ProcessBuilder()
.directory(tempDir.toFile())
.command("javap", "-p", "-c", "$className.class")
.redirectErrorStream(true)
.start()
var actualOutput = process.inputStream.bufferedReader().readText()
val expectedOutput = expectedOutputFile.readText()
// cleanup output
val lines = actualOutput.split("\n")
if (lines[0].startsWith("Picked up JAVA_TOOL_OPTIONS")) {
actualOutput = actualOutput.replace(lines[0] + "\n", "")
}
actualOutput = actualOutput.replace("\r\n", "\n")
process.waitFor()
assertEquals(expectedOutput, actualOutput, "Output doesn't match expected")
}
}

View file

@ -0,0 +1,62 @@
/*
* 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.softspoon
import io.papermc.paperweight.tasks.*
import java.nio.file.Path
import kotlin.test.BeforeTest
import kotlin.test.Test
import org.gradle.kotlin.dsl.*
import org.junit.jupiter.api.io.TempDir
class ApplyFilePatchesTest : TaskTest() {
private lateinit var task: ApplyFilePatches
@BeforeTest
fun setup() {
val project = setupProject()
task = project.tasks.register("applyPatches", ApplyFilePatches::class).get()
}
@Test
fun `should apply patches`(@TempDir tempDir: Path) {
val testResource = Path.of("src/test/resources/apply_patches")
val testInput = testResource.resolve("input")
val input = setupDir(tempDir, testInput, "base").toFile()
val output = tempDir.resolve("source").toFile()
val patches = testInput.resolve("patches").toFile()
setupGitRepo(input, "main")
task.input.set(input)
task.output.set(output)
task.patches.set(patches)
task.verbose.set(true)
task.run()
val testOutput = testResource.resolve("output")
compareDir(tempDir, testOutput, "source")
}
}

View file

@ -0,0 +1,85 @@
/*
* 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.softspoon
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.papermc.paperweight.tasks.*
import java.nio.file.Path
import kotlin.test.BeforeTest
import kotlin.test.Test
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkQueue
import org.gradle.workers.WorkerExecutor
import org.junit.jupiter.api.io.TempDir
class ApplySourceATTest : TaskTest() {
private lateinit var task: ApplySourceAT
private val workerExecutor: WorkerExecutor = mockk()
private val workQueue: WorkQueue = mockk()
@BeforeTest
fun setup() {
val project = setupProject()
task = project.tasks.register("applySourceAT", ApplySourceAT::class).get()
mockkObject(task)
every { task.worker } returns workerExecutor
every { workerExecutor.processIsolation(any()) } returns workQueue
every { workQueue.submit(RestampWorker::class, any()) } answers {
val action = object : RestampWorker() {
override fun getParameters(): Params {
return mockk<Params>().also {
every { it.inputJar.get() } returns task.inputJar.get()
every { it.atFile.get() } returns task.atFile.get()
every { it.outputJar.get() } returns task.outputJar.get()
every { it.minecraftClasspath } returns task.minecraftClasspath
}
}
}
action.execute()
}
}
@Test
fun `should apply source access transformers`(@TempDir tempDir: Path) {
val testResource = Path.of("src/test/resources/apply_source_at")
val testInput = testResource.resolve("input")
val inputJar = createZip(tempDir, testInput, "Test.jar", "Test.java", "Unrelated.java")
val atFile = testInput.resolve("ats.at").toFile()
val outputJar = tempDir.resolve("output.jar")
task.inputJar.set(inputJar.toFile())
task.atFile.set(atFile)
task.outputJar.set(outputJar.toFile())
task.run()
val testOutput = testResource.resolve("output")
val expectedJar = createZip(tempDir, testOutput, "expected.jar", "Test.java", "Unrelated.java")
compareZip(outputJar, expectedJar)
}
}

View file

@ -0,0 +1,68 @@
/*
* 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.softspoon
import io.papermc.paperweight.tasks.*
import java.nio.file.Path
import kotlin.test.BeforeTest
import kotlin.test.Test
import org.gradle.kotlin.dsl.*
import org.junit.jupiter.api.io.TempDir
class RebuildFilePatchesTest : TaskTest() {
private lateinit var task: RebuildFilePatches
@BeforeTest
fun setup() {
val project = setupProject()
task = project.tasks.register("rebuildPatches", RebuildFilePatches::class).get()
}
@Test
fun `should rebuild patches`(@TempDir tempDir: Path) {
val testResource = Path.of("src/test/resources/rebuild_patches")
val testInput = testResource.resolve("input")
val source = setupDir(tempDir, testInput, "source").toFile()
setupGitRepo(source, "main", "file")
val base = setupDir(tempDir, testInput, "base").toFile()
val patches = tempDir.resolve("patches").toFile()
val atFile = testInput.resolve("ats.at").toFile()
val atFileOut = tempDir.resolve("ats.at").toFile()
task.input.set(source)
task.base.set(base)
task.patches.set(patches)
task.atFile.set(atFile)
task.atFileOut.set(atFileOut)
task.verbose.set(true)
task.run()
val testOutput = testResource.resolve("output")
compareDir(tempDir, testOutput, "base")
compareDir(tempDir, testOutput, "source")
compareDir(tempDir, testOutput, "patches")
compareFile(tempDir, testOutput, "ats.at")
}
}

View file

@ -0,0 +1,47 @@
/*
* 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 atFromString
import kotlin.test.Test
import kotlin.test.assertEquals
import org.cadixdev.at.AccessChange
import org.cadixdev.at.AccessTransform
import org.cadixdev.at.ModifierChange
class ATTest {
@Test
fun testATFromString() {
assertEquals(AccessTransform.of(AccessChange.PUBLIC, ModifierChange.REMOVE), atFromString("public-f"))
assertEquals(AccessTransform.of(AccessChange.PUBLIC, ModifierChange.NONE), atFromString("public"))
assertEquals(AccessTransform.of(AccessChange.PUBLIC, ModifierChange.ADD), atFromString("public+f"))
assertEquals(AccessTransform.of(AccessChange.PRIVATE, ModifierChange.REMOVE), atFromString("private-f"))
assertEquals(AccessTransform.of(AccessChange.PRIVATE, ModifierChange.NONE), atFromString("private"))
assertEquals(AccessTransform.of(AccessChange.PRIVATE, ModifierChange.ADD), atFromString("private+f"))
assertEquals(AccessTransform.of(AccessChange.NONE, ModifierChange.REMOVE), atFromString("-f"))
assertEquals(AccessTransform.of(AccessChange.NONE, ModifierChange.ADD), atFromString("+f"))
}
}

View file

@ -0,0 +1,13 @@
public class Test {
public int dum;
private final String test;
public Test(String test) {
this.test = test;
}
public String getTest() {
return test;
}
}

View file

@ -0,0 +1,5 @@
# This file is auto generated, any changes may be overridden!
# See CONTRIBUTING.md on how to add access transformers.
public-f Test test
public+f Test dum
private+f Test getTest()Ljava/lang/String;

View file

@ -0,0 +1,21 @@
Compiled from "Test.java"
public class Test {
public final int dum;
public java.lang.String test;
public Test(java.lang.String);
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #7 // Field test:Ljava/lang/String;
9: return
private final java.lang.String getTest();
Code:
0: aload_0
1: getfield #7 // Field test:Ljava/lang/String;
4: areturn
}

View file

@ -0,0 +1,13 @@
public class Test {
public int dum;
private final String test;
public Test(String test) {
this.test = test;
}
public String getTest() {
return test;
}
}

View file

@ -0,0 +1,10 @@
--- a/Test.java
+++ b/Test.java
@@ -8,6 +_,6 @@
}
public String getTest() {
- return test;
+ return test + "Test"; // Test
}
}

View file

@ -0,0 +1,13 @@
public class Test {
public int dum;
private final String test;
public Test(String test) {
this.test = test;
}
public String getTest() {
return test + "Test"; // Test
}
}

View file

@ -0,0 +1,13 @@
public class Test {
public int dum;
private final String test;
public Test(String test) {
this.test = test;
}
public String getTest() {
return test;
}
}

View file

@ -0,0 +1,3 @@
class Unrealted {
}

View file

@ -0,0 +1,5 @@
# This file is auto generated, any changes may be overridden!
# See CONTRIBUTING.md on how to add access transformers.
public-f Test test
public+f Test dum
private+f Test getTest()Ljava/lang/String;

View file

@ -0,0 +1,13 @@
public class Test {
public final int dum;
public String test;
public Test(String test) {
this.test = test;
}
private final String getTest() {
return test;
}
}

View file

@ -0,0 +1,3 @@
class Unrealted {
}

View file

@ -0,0 +1,3 @@
# This file is auto generated, any changes may be overridden!
# See CONTRIBUTING.md on how to add access transformers.
public Test dum

View file

@ -0,0 +1,13 @@
public class Test {
public int dum;
private final String test;
public Test(String test) {
this.test = test;
}
public String getTest() {
return test;
}
}

View file

@ -0,0 +1,13 @@
public class Test {
public int dum;
public String test;// Paper-AT: public-f test
public Test(String test) {
this.test = test;
}
private final String getTest() {// Paper-AT: private+f getTest()Ljava/lang/String;
return test + "Test"; // Test
}
}

View file

@ -0,0 +1,5 @@
# This file is auto generated, any changes may be overridden!
# See CONTRIBUTING.md on how to add access transformers.
private+f Test getTest()Ljava/lang/String;
public Test dum
public-f Test test

View file

@ -0,0 +1,13 @@
public class Test {
public int dum;
public String test;
public Test(String test) {
this.test = test;
}
private final String getTest() {
return test;
}
}

View file

@ -0,0 +1,10 @@
--- a/Test.java
+++ b/Test.java
@@ -8,6 +_,6 @@
}
private final String getTest() {
- return test;
+ return test + "Test"; // Test
}
}

View file

@ -0,0 +1,13 @@
public class Test {
public int dum;
public String test;
public Test(String test) {
this.test = test;
}
private final String getTest() {
return test + "Test"; // Test
}
}

View file

@ -67,7 +67,9 @@ abstract class PaperweightUser : Plugin<Project> {
val sharedCacheRoot = target.gradle.gradleUserHomeDir.toPath().resolve("caches/paperweight-userdev")
target.gradle.sharedServices.registerIfAbsent(DOWNLOAD_SERVICE_NAME, DownloadService::class) {}
target.gradle.sharedServices.registerIfAbsent(DOWNLOAD_SERVICE_NAME, DownloadService::class) {
parameters.projectPath.set(target.projectDir)
}
val cleanAll = target.tasks.register<Delete>("cleanAllPaperweightUserdevCaches") {
group = "paperweight"

View file

@ -70,12 +70,12 @@ class SetupHandlerImplV2(
override val hashFile: Path = cache.resolve(paperSetupOutput("minecraftLibraries", "hashes"))
override fun run(context: SetupHandler.Context) {
downloadMinecraftLibraries(
downloadLibraries(
download = parameters.downloadService,
workerExecutor = context.workerExecutor,
targetDir = minecraftLibraryJars,
repositories = listOf(MC_LIBRARY_URL, MAVEN_CENTRAL_URL),
mcLibraries = bundle.config.buildData.vanillaServerLibraries,
libraries = bundle.config.buildData.vanillaServerLibraries,
sources = false
).await()
}