Compare commits

...

484 commits
work ... main

Author SHA1 Message Date
49e471e0f8 Rename plugin group
Some checks failed
Deploy Snapshot / Deploy Snapshot (push) Has been cancelled
Test / Test (push) Has been cancelled
2024-10-15 03:06:12 -07:00
95585adea7 Add publish instructions 2024-10-15 02:50:13 -07:00
671d4ff2b2 More adjustments for project name changes 2024-10-15 02:34:16 -07:00
ecb210d3ef Change project name to Nugget 2024-10-15 02:31:08 -07:00
Jason Penilla
91fac7484b
1.7.4-SNAPSHOT 2024-09-27 19:29:41 -07:00
Jason Penilla
4e08a00d65
release: Version 1.7.3 2024-09-27 19:27:08 -07:00
okx-code
4b9a233416
Specify myers diff algorithm explicitly (#252)
* Use myers diff algorithm

This is the default Git diff algorithm and by specifying this explicitly we prevent git configs from overriding this setting. As Paper relies on patches generated with Myers, and breaks with other algorithms, it doesn't make sense to allow users to configure this.

* Add diff algorithm in generatePatches

* Also add to isUnfinishedPatch
2024-09-27 19:22:32 -07:00
Jason Penilla
b3467d9f8d
Guard against invalid remapJar configs (#256)
Proactively throw an error when input and output paths match instead of letting tiny-remapper fail.
2024-09-27 19:06:35 -07:00
Jason Penilla
40f78222c0
1.7.3-SNAPSHOT 2024-08-06 13:42:56 -07:00
Jason Penilla
33a8f201da
release: Version 1.7.2 2024-08-06 13:38:23 -07:00
Jason Penilla
a788a9770d
fix(userdev): invalidate caches for preceding steps when a step fails (#250)
* userdev: invalidate caches for preceding steps when a step fails

a common cause for patch apply failing is decompiling with the wrong jdk,
and we don't want to have to manually clean cache to fix it after replacing the jdk

* adjust message
2024-08-06 13:37:40 -07:00
Jason Penilla
a5ae66c59a
1.7.2-SNAPSHOT 2024-05-12 11:42:32 -07:00
Jason Penilla
4d47004311
release: Version 1.7.1 2024-05-11 13:01:42 -07:00
Jason Penilla
eb14faff2b
Update for paper-mojangapi removal 2024-05-11 12:30:27 -07:00
Jason Penilla
007ef94235
Use first capability of a project in case of multiple when creating bundler jars 2024-05-11 12:21:58 -07:00
Jason Penilla
d3a5f5fc4c
1.7.1-SNAPSHOT 2024-05-01 10:50:11 -07:00
Jason Penilla
c3c104c363
release: Version 1.7.0 2024-05-01 10:46:55 -07:00
Jason Penilla
f964c5bc0b
feat(userdev): Enable shared caches by default (#235)
* userdev: Enable shared caches by default

* Ignore locks for non-existent processes in cleanup

* 1.7.0-SNAPSHOT
2024-04-30 17:23:33 -07:00
Jason Penilla
b26c85b988
Remove withDisallowUnsafeRead from PaperweightUserExtension#minecraftVersion
This is ok to read during configuration, as long as it's after the dev bundle dependency is set.
2024-04-30 11:00:25 -07:00
Jason Penilla
6495ae0a8d
1.6.4-SNAPSHOT 2024-04-29 16:21:39 -07:00
Jason Penilla
a3256368ed
release: Version 1.6.3 2024-04-29 16:19:30 -07:00
Octavia Togami
b47a75a601
Apply a specific rule to exclude JUnit (#205)
* Apply a specific rule to exclude JUnit

This avoids excluding junit from the test classpath, even if explicitly added by a downstream user, which doesn't make sense, and allows people to add junit to the main classpath if they need to for some reason.

* Adjust docs and add constant for json-simple id

---------

Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
2024-04-29 16:11:06 -07:00
Jake Potrebic
381e557a2f
Don't warn about mcdev data files that aren't being modified (#241) 2024-04-29 15:49:55 -07:00
Jason Penilla
3711175570
make ReobfArtifactConfiguration fields @JvmStatic
fixes #243
2024-04-28 19:48:07 -07:00
Jason Penilla
e54e9fce13
Only exclude transitives for our tiny-remapper dep 2024-04-27 20:11:24 -07:00
Jason Penilla
0b9e07345f
feat(userdev): add pluginRemapper configuration to more easily upgrade tiny-remapper for reobfJar without disturbing the dev bundle pipeline 2024-04-27 20:01:00 -07:00
Jason Penilla
f391c0c900
1.6.3-SNAPSHOT 2024-04-27 17:36:29 -07:00
Jason Penilla
668e516a41
release: Version 1.6.2 2024-04-27 17:34:54 -07:00
Jason Penilla
8828b4adc1
Bump default scanJar memory 2024-04-27 17:32:31 -07:00
Jason Penilla
073ed85850
Bump default java launcher version 2024-04-27 17:14:22 -07:00
Jason Penilla
095a3210d2
1.6.2-SNAPSHOT 2024-04-27 16:07:27 -07:00
Jason Penilla
10b0c49ee8
release: Version 1.6.1 2024-04-27 16:04:21 -07:00
Jason Penilla
78f033b9c2
Fix mappings missing from dev bundle server jar (real) 2024-04-27 15:58:38 -07:00
Jason Penilla
0eaa60eeff
Fix mappings missing from dev bundle server jar 2024-04-27 15:43:46 -07:00
Jason Penilla
cfcb6ad8ef
Check for offline mode before initializing submodules 2024-04-27 11:10:48 -07:00
Jason Penilla
cf4fcd8953
1.6.1-SNAPSHOT 2024-04-26 16:21:49 -07:00
Jason Penilla
17b3734bb1
release: Version 1.6.0 2024-04-26 16:17:02 -07:00
Jason Penilla
e92795886c
Bump dev bundle revision to 4
It's important that 1.20.5 plugins include the mappings metadata in their manifests. For this reason we are bumping the dev bundle revision without any changes to the format.
2024-04-26 14:20:06 -07:00
Jason Penilla
114dc611e0
Fix whitespace 2024-04-25 18:35:38 -07:00
Jason Penilla
c6d776d6f4
feat(core/patcher): Add PaperweightSourceGeneratorHelper to replace VanillaGradle usage 2024-04-25 18:33:06 -07:00
Jason Penilla
ca387c97ce
Update RemapSources sourceCompatibility convention 2024-04-25 18:13:31 -07:00
Jason Penilla
c8f6e05472
Plugin remapping (#239)
* Work

* Include mappings hash in manifest

* Use manifest instead of marker file

* Create the manifest if it doesn't exist

* Make RemapJar no-op when the namespaces match

* move files

* decorate userdev jar manifests

* fix relocated shadow jar manifest

* feat(core/patcher): Replace use of shadow plugin

We now reobf using the relocated mappings.
The constant pool still needs to be relocated for references to CB classes by name in for example Commodore or class preloading.

* chore(core/patcher): Remove no longer needed relocation logic from GenerateDevBundle

* chore(core/patcher): Do not extract manifest when including mappings

We don't need this after removing shadow usage

* Bump version to 1.6.0-SNAPSHOT
2024-04-23 11:40:20 -07:00
Jason Penilla
4a2e036152
1.5.16-SNAPSHOT 2024-04-10 17:09:46 -07:00
Jason Penilla
2a02316725
release: Version 1.5.15
This version is due to publishing failures of 1.5.14, no changes from 1.5.14
2024-04-10 17:06:51 -07:00
Jason Penilla
d972b76e9d
release: Version 1.5.14 2024-04-10 17:02:37 -07:00
Jake Potrebic
9026a4ba27
Improved Vineflower support (#238)
* update decompiler flags for vineflower

we want them to match up to mache

* rename decompiler stuff to vineflower

* fix style

* Add back support for ForgeFlower for old dev bundles

* Add missing check

* ignore case

* Fix comment

* Rename method

* Remove verify merges flag

---------

Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
2024-04-10 14:31:03 -07:00
Jason Penilla
ad931e0d37
1.5.14-SNAPSHOT 2024-04-04 14:06:13 -07:00
Jason Penilla
b7a64b0134
release: Version 1.5.13 2024-04-04 14:05:29 -07:00
Jason Penilla
754562481d
Make patched file filtering incremental & compile against filtered jar (#237)
* Make patched file filtering incremental

This makes it more reasonable to compile against

* Compile against the filtered jar

* Add missing `$` to inner class glob patterns

* Fix base path being split by collection add function
2024-04-04 13:53:18 -07:00
Jason Penilla
8c3c62d89d
Update foojay resolver 2024-03-27 12:41:01 -07:00
Jason Penilla
374131ff4f
1.5.13-SNAPSHOT 2024-03-27 12:40:47 -07:00
Jason Penilla
2fba0a0647
release: Version 1.5.12 2024-03-27 12:39:16 -07:00
Jason Penilla
bf78951adc
fix(core/patcher): Download Spigot dependencies using DetachedResolver (#233)
* Download Spigot sources using DetachedResolver; fixes #232

* Create resolver during task init

* Use DependencyFactory instead of project

* Remove redundant casts

* remove unused import
2024-03-27 12:38:27 -07:00
Jason Penilla
0d47c66448
Update Gradle wrapper to 8.7 2024-03-26 11:54:19 -07:00
Jason Penilla
dafc134ef6
Update ASM to 9.7 2024-03-23 16:25:07 -07:00
Jason Penilla
21f771d3e8
Make RemapSources sourceCompatibility a property 2024-01-09 15:13:41 -07:00
Jason Penilla
0b3688ca5b
Use JDK 17 toolchain and set -Xjdk-release=11 2024-01-09 15:12:05 -07:00
Jason Penilla
4bc9495f03
Bump Mercury 2024-01-09 14:49:43 -07:00
Jason Penilla
63838710fc
Update Gradle wrapper to 8.5 2023-12-08 21:13:30 -07:00
Jason Penilla
15224af96b
1.5.12-SNAPSHOT 2023-12-08 11:09:38 -07:00
Jason Penilla
7e631a2a9d
release: Version 1.5.11 2023-12-08 11:05:26 -07:00
Jason Penilla
8230d7d997
Clean up apply and rebuild logging
use paperweight.verboseApplyPatches to see all the Git output, even on
success (when the task's output is enabled)
2023-12-08 10:44:07 -07:00
Jason Penilla
f1548964e7
Don't log Git output for rebuilds by default either (#229) 2023-12-06 15:07:15 -07:00
Jason Penilla
b356b4d4de
Don't log Git output for patch apply by default (#228)
On Paper this can get pretty obnoxious with over 1000 server patches...
2023-12-06 12:45:33 -07:00
Jason Penilla
fc0ead8047
Update spotless and fix license check (#227) 2023-11-29 18:10:27 -07:00
Jason Penilla
4e97731a49
1.5.11-SNAPSHOT 2023-11-21 12:53:24 -07:00
Jason Penilla
6a36cfa316
release: Version 1.5.10 2023-11-21 12:51:07 -07:00
Jason Penilla
76dad969e0
Short circuit userdev up-to-date checking on the last step when the dev bundle hasn't changed (#221) 2023-11-21 12:44:03 -07:00
Jason Penilla
7669316eac
Change back to our deleteRecursive utility instead of Kotlin's new built in one
Also, all the experimental path api we were using is stable now, so remove the opt-in flag
2023-11-21 12:24:32 -07:00
Jason Penilla
87e8db20d0
Make ByteArray#asHexString less slow
the old code was creating formatters (running regex) for each byte...
2023-11-20 17:14:36 -07:00
Jason Penilla
47ec175e27
Update DiffPatch (#225) 2023-11-20 16:39:14 -07:00
Jason Penilla
3be0f6cff7
Update Gradle to 8.4 (#224) 2023-11-20 16:12:08 -07:00
Jason Penilla
7d2ae18db3
Don't hash the dev bundle zip twice (#223) 2023-11-20 14:45:30 -07:00
Jason Penilla
1d88d33cc5
Move DownloadService name to a constant 2023-11-17 00:45:38 -07:00
Jason Penilla
daab4fa35e
Add InputStream contentEquals overload 2023-11-17 00:05:20 -07:00
Jason Penilla
5225a4d132
Use short-circuiting content comparison instead of hash comparison when possible (#222) 2023-11-16 23:44:02 -07:00
Jason Penilla
c7d0820fa1
skip intermediary copy when filtering jars (#220) 2023-11-16 19:54:55 -07:00
Jason Penilla
609bec2f68
increase buffer size when hashing files and don't buffer when hashing strings (#219) 2023-11-16 19:10:21 -07:00
Jason Penilla
87205250d2
1.5.10-SNAPSHOT 2023-10-26 15:48:28 -07:00
Jason Penilla
ee61ec1e07
release: Version 1.5.9 2023-10-26 15:24:53 -07:00
Jason Penilla
00d9ba22ed
Rewrite ExplicitThisAdder handling for instance methods/fields (#218) 2023-10-26 14:57:22 -07:00
Jason Penilla
fa8945f1a8
fix: fix another deprecation warning from implicit convention reference 2023-10-09 17:57:35 -07:00
Jason Penilla
45aaed91c7
1.5.9-SNAPSHOT 2023-10-09 17:54:12 -07:00
Jason Penilla
f940a23453
release: Version 1.5.8 2023-10-09 17:43:39 -07:00
Jason Penilla
2b1404179e
Respect quiet mode in version printout 2023-10-09 17:35:29 -07:00
Jason Penilla
5389e61eee Wait on input stream redirection to finish
Avoids race where output is still copying after process exits
2023-10-09 17:18:21 -07:00
Jason Penilla
b1011c70fa
fix: deprecation warning from implicit convention reference 2023-10-05 11:07:46 -07:00
Jason Penilla
340ee3582f
1.5.8-SNAPSHOT 2023-10-05 10:54:15 -07:00
Jason Penilla
2dd3e6f3b7
release: Version 1.5.7 2023-10-05 10:50:47 -07:00
Jason Penilla
c80a23cf39
bump ASM to 9.6 2023-10-05 10:50:29 -07:00
Jason Penilla
3a4ea86b77
core/patcher: print version and os on plugin apply & don't require unsupported property for windows dev bundle gen 2023-10-05 10:46:02 -07:00
MiniDigger | Martin
586f6a7d49
fix: filter project dir on windows (#214) 2023-10-05 10:20:14 -07:00
Jason
4803719449
add supported environment check for GenerateDevBundle (#213)
bypass the check with `-Ppaperweight.generateDevBundle.ignoreUnsupportedEnvironment=true`

this PR also fixes issues with platform path separators and file tree walk order in the relocation logic, as well as improves error handling
2023-10-01 12:48:20 -07:00
Jason Penilla
88edb5ce16
fix formatting 2023-09-22 18:01:36 -07:00
Jason Penilla
157abbb72f
fix #209 2023-09-22 17:57:46 -07:00
Jason Penilla
23d9406d2c
chore: update comments 2023-09-15 10:12:34 -07:00
Jason Penilla
3806539949
1.5.7-SNAPSHOT 2023-09-15 10:05:38 -07:00
Jason Penilla
8faebd1767
release: Version 1.5.6 2023-09-15 10:02:05 -07:00
Jason Penilla
d23909c7d8
adjust ScanJar loggers 2023-09-15 09:59:35 -07:00
Jason Penilla
28786b0947
use ceilingEntry instead of higherEntry for line mappings
we want to include & prioritize exact matches
2023-09-15 09:26:49 -07:00
Jake Potrebic
de151674ee
Merge pull request #198 from Machine-Maker/fix/FilterProjectDir
fix FilterProjectDir vanillaJar input (fixes runDev in Paper)
2023-06-16 21:29:21 -07:00
Jake Potrebic
c2d4fcb084
fix FilterProjectDir vanillaJar input 2023-06-10 14:05:18 -07:00
Kyle Wood
3f6c7a5feb
Fix Gradle deprecation issue, buildSrc missing name 2023-06-07 19:00:25 -05:00
Kyle Wood
57566dd7da
Remove commented code 2023-06-07 18:59:47 -05:00
Jake Potrebic
ce9df1c5f6
account for inner classes 2023-06-07 17:22:37 -05:00
Jake Potrebic
a3033c6d60
fix license and style violations 2023-06-07 17:22:37 -05:00
Jake Potrebic
329b5663e9
fix a couple issues after rebasing on main 2023-06-07 17:22:36 -05:00
Jake Potrebic
33b8a1b12a
add task to filter project dir 2023-06-07 17:22:36 -05:00
Jake Potrebic
92f922f291
support importing files from data/minecraft/ into the resources folder 2023-06-07 17:22:36 -05:00
Kyle Wood
9f602b892a
Fix license formats 2023-06-07 17:16:31 -05:00
Jake Potrebic
972de2e850
Add task to scan generated code to make sure versions match 2023-06-07 17:16:31 -05:00
Jason Penilla
d3d7eec0c5
chore: Bump ASM & lorenz-tiny 2023-06-06 18:41:46 -07:00
Jason Penilla
d82fddd8d7
release: Version 1.5.5 2023-04-26 09:50:18 -07:00
Jason Penilla
c095ab0265
Add Java version check 2023-04-26 09:47:26 -07:00
Jason Penilla
934e245fdf
1.5.5-SNAPSHOT 2023-04-04 12:26:09 -07:00
Jason Penilla
a260d33b42
release: Version 1.5.4 2023-04-04 12:19:55 -07:00
Jason Penilla
11ba59068f
chore(userdev): log a message when experimental shared caches are enabled 2023-04-04 12:09:15 -07:00
Jason
b56b118555
feat(userdev): Add experimental (for now) option for shared userdev caches (#187) 2023-04-04 11:56:51 -07:00
Michael H
aa146cdd5f Add Folia devbundle extension 2023-03-29 15:50:54 -07:00
Jake Potrebic
30f1b60fed remove bannedClasses logic 2023-03-18 15:46:59 -07:00
Jake Potrebic
4e63b8041d Support using glob syntax in mc dev imports 2023-03-18 15:46:59 -07:00
Jason Penilla
ac168e288b
1.5.4-SNAPSHOT 2023-03-03 18:35:22 -07:00
Jason Penilla
4b5d7c0f18
release: Version 1.5.3 2023-03-03 18:32:02 -07:00
Jason Penilla
298ae45990
Fix order of setupServerProject and dev bundle task configuration in patcher 2023-03-03 18:02:39 -07:00
Jason Penilla
219df998ac
1.5.3-SNAPSHOT 2023-02-27 14:49:43 -07:00
Jason Penilla
0bad385a38
release: Version 1.5.2 2023-02-27 14:48:25 -07:00
Jason
d5ef36b535
Address deprecation warnings when running with Gradle 8 (#186) 2023-02-27 14:40:11 -07:00
Jason Penilla
d655180e7a
Object map version manifests 2023-02-25 15:09:39 -07:00
Jason
12fecf6315
Verify SHA1 hashes when downloading Mojang files (#185) 2023-02-25 13:13:20 -07:00
Jason Penilla
90384770ba Add version catalog helper methods to PaperweightUserDependenciesExtension 2023-02-21 20:15:51 -07:00
Jason Penilla
dd1b1e28ed
1.5.2-SNAPSHOT 2023-02-16 08:55:56 -07:00
Jason Penilla
f7528b6f12
release: Version 1.5.1 2023-02-15 13:20:29 -07:00
Jason Penilla
bed0081cad Update for deprecations 2023-02-15 13:12:11 -07:00
Jason Penilla
4e8233d0ae Add descriptions 2023-02-15 13:12:11 -07:00
Jason Penilla
70e0b7b7bb Downgrade ktlint 2023-02-15 13:12:11 -07:00
Jason Penilla
e17043314f Make sure noRelocate is considered for shadowJar up-to-date check 2023-02-15 13:12:11 -07:00
Jason Penilla
6b158a2b7a Standardize publishing (releases on plugin portal, snapshots on paper repo), combine -lib sources into plugin source jars. 2023-02-15 13:12:11 -07:00
Jason Penilla
307fc2e606
1.5.1-SNAPSHOT 2023-02-09 09:58:35 -07:00
Jason Penilla
6619d202aa
release: Version 1.5.0 2023-02-09 09:43:18 -07:00
Jason
0e759beba4
Use DiffPatch to apply patches in userdev, removes git dependency (#178) 2023-02-09 09:39:52 -07:00
Jason Penilla
7c0dda0ed3
Add missing @returns 2023-02-02 16:28:34 -07:00
Jason Penilla
7d4f0cb889
Update license headers 2023-02-02 16:26:35 -07:00
Jason
8a9f7c7462
userdev: Deprecate top level dependency functions in favor of DependencyHandler extension (#179) 2023-02-02 16:25:29 -07:00
Jason Penilla
668e7a0c7c
1.4.2-SNAPSHOT 2023-01-31 11:09:34 -07:00
Nassim Jahnke
fe82b94a2b
release: Version 1.4.1 2023-01-06 09:28:21 +01:00
Jason Penilla
e1fcff715d
Lock userdev setup fixes #174
Also adds default lock timeout
2022-12-14 22:20:56 -07:00
Jason Penilla
dca41f43c5
Remove duplicate rebuild task and rename patcher apply task 2022-12-14 18:36:45 -07:00
Jason Penilla
6283956ab9
Copy patch rebuild chunking logic from RebuildPaperPatches to SimpleRebuildGitPatches 2022-12-14 18:18:54 -07:00
Jason Penilla
026347a88b
1.4.1-SNAPSHOT 2022-12-13 10:01:42 -07:00
Jason Penilla
ca8767bc2d
release: Version 1.4.0 2022-12-13 10:00:07 -07:00
Jason
71402ecbc0
Lock server dir for mc dev sources and apply patches (#172)
Fixes IntelliJ deciding to sync during apply patches breaking git state
2022-12-13 09:59:27 -07:00
Jason Penilla
ccd22ea127
Update actions 2022-11-22 19:06:16 -07:00
Jason Penilla
08a5f4a9c9
Don't download spigot maven plugins 2022-11-20 11:50:53 -07:00
Jason Penilla
3afff25383
1.3.12-SNAPSHOT 2022-11-19 15:45:23 -07:00
Jason Penilla
1756148d32
release: Version 1.3.11 2022-11-19 15:43:53 -07:00
Jason Penilla
505a11044b
Ensure consistent output from CollectATsFromPatches 2022-11-19 15:34:58 -07:00
Jason Penilla
33f0d78506
Add extraPatchDir property for use during MC updates 2022-11-19 15:28:49 -07:00
Jason Penilla
178e1ba293
1.3.11-SNAPSHOT 2022-11-19 14:41:05 -07:00
Jason Penilla
4c805951b2
release: Version 1.3.10 2022-11-19 14:36:18 -07:00
Jake Potrebic
c280119859
Allow dev imports to pull from spigot libraries (#168)
closes https://github.com/PaperMC/paperweight/issues/131
2022-11-19 14:21:35 -07:00
Jake Potrebic
50b4ca92cf
add ignoreCase for co-author line check 2022-11-18 21:34:35 -08:00
Jason Penilla
fec6119c16
Also end ATs at co-author line 2022-11-18 21:16:41 -07:00
Jason Penilla
3f82b9aa48
Update actions part 2 2022-11-18 17:12:29 -07:00
Jason Penilla
b887043915
Update actions 2022-11-18 17:04:36 -07:00
Jason Penilla
35b5e68aca
fix style 2022-11-18 16:36:01 -07:00
Jake Potrebic
f2e5ab5077
Read ATs from server patch file headers (#171) 2022-11-18 16:33:28 -07:00
Jason Penilla
3f89a135ba
further reduce eager domain object creation 2022-11-14 09:52:28 -07:00
Jason Penilla
ddd552b03f
reduce eager domain object creation 2022-11-14 09:45:12 -07:00
Jason
0e51fe634e
Add ReobfArtifactConfiguration to userdev (#169) 2022-11-08 11:11:31 -07:00
Jason Penilla
7c32ad2d62
Fix style 2022-11-04 22:53:15 -07:00
Jake Potrebic
1bba114771
Check invokedynamic instructions for bad methods in jar scans (#167) 2022-11-04 22:50:36 -07:00
Jason Penilla
967c17e0ec
1.3.10-SNAPSHOT 2022-10-31 15:19:19 -07:00
Jason Penilla
e9750ebf7c
release: Version 1.3.9 2022-10-31 15:17:56 -07:00
Jason Penilla
41ce4d8b9b
fix style 2022-10-31 15:07:29 -07:00
Jason Penilla
e52ef621ff
Add IncludeMappings task from Paper-Server build script 2022-10-31 15:05:33 -07:00
RGBCube
98de95e9c0 Update checkout action version 2022-10-27 22:08:09 +02:00
RGBCube
850aaf8ac4 Use GITHUB_OUTPUT instead of deprecated ::set-output 2022-10-27 22:08:09 +02:00
Jason Penilla
6deed39825 Fix typo 2022-09-13 14:57:50 -07:00
Jason Penilla
c83d1fef94 Fix #146 2022-09-13 14:41:39 -07:00
Jason Penilla
9b7ef249e8 Fixup last commit
jar runner uses processbuilder
2022-09-13 14:35:25 -07:00
Jason Penilla
ae6aeed7b5 Override Xmx for SpigotDecompileJar fixes #158 2022-09-13 14:04:28 -07:00
Jason Penilla
4a7c4f9842
1.3.9-SNAPSHOT 2022-06-27 15:24:37 -07:00
Jason Penilla
57970ab0bb
release: Version 1.3.8 2022-06-27 15:23:19 -07:00
Jason
e510c2ffcc
Add ScanJarForBadCalls task (#155) 2022-06-27 15:22:53 -07:00
Nassim Jahnke
b361bb3ec6
Update MC version manifest url (#154) 2022-06-26 15:37:37 -07:00
Jason Penilla
7cab978aff 1.3.8-SNAPSHOT 2022-06-12 13:42:49 -07:00
Jason Penilla
79db91fa46 Version 1.3.7 2022-06-12 13:25:27 -07:00
Jason Penilla
05c5d2f758 Fix style issue 2022-06-09 13:19:09 -07:00
Jason Penilla
c4d0c5cb24 Improve classifier handling
Fixes launching bundler, needs further review before release
2022-06-08 19:36:12 -07:00
Riley Park
1daa1948c1 update endpoints 2022-05-20 20:39:17 -07:00
Jason Penilla
729ff50a6c
1.3.7-SNAPSHOT 2022-04-21 19:20:31 -07:00
Jason Penilla
9de125ba88
Version 1.3.6 2022-04-21 19:18:27 -07:00
Jason
1943b3353c
Decompile changes & line mapping (#151)
- Use ForgeFlower single file save mode
- Add basic support for Quiltflower
- Implement line mapping
2022-04-21 18:56:50 -07:00
Jason Penilla
7b68885368
Bump ASM & Gradle plugins 2022-04-08 09:15:28 -07:00
Jason Penilla
49031b3f19
Remove KeyedObject from McDev.bannedClasses 2022-04-04 08:55:41 -07:00
Jason Penilla
fea6f5c494
Update sourceCompatibility to 17 for RemapSources 2022-03-31 17:51:07 -07:00
Jason Penilla
94962163a6 Remove duplicate into call 2022-03-09 15:46:30 -08:00
Shane Freeder
1ac512a5b3
Merge pull request #140 from lynxplay/bugfix/include-flightrecorder-config 2022-03-09 16:15:01 +00:00
Bjarne Koll
52817caec7
Copy the flightrecorder config as a resource
Paperweight actively defines an includes list for any resource that are
copied over from the vanilla (remapped) server jar.

Minecraft 1.18 added the /jfr command, allowing users and server
administrators to use java's flight recorder to analyse and collect
data about their clients/servers runtime.
For this a 'flightrecorder-config.jfc' configuration file was added.

This commit now properly includes the file in the includes list of the
copy resources tasks, hence including the configuration file in compiled
paper servers which re-enables the previously failing /jfr command.
2022-03-09 17:05:53 +01:00
Jason Penilla
058a807735
1.3.6-SNAPSHOT 2022-03-03 19:01:24 -07:00
Jason Penilla
a5cda84e7f
Version 1.3.5 2022-03-03 19:00:24 -07:00
Jason Penilla
091a3a2a76
Delete output jar before running tiny-remapper 2022-03-03 17:04:10 -07:00
Jason Penilla
c1bd340a72
1.3.5-SNAPSHOT 2022-01-27 13:11:22 -07:00
Jason Penilla
9c98431634
Version 1.3.4 2022-01-27 13:08:27 -07:00
Jason Penilla
db3487afb8
Ensure vanilla jar is downloaded when source gen disabled 2022-01-21 22:23:57 -07:00
Jason Penilla
964fa21134
run extract from bundler step when source gen disabled 2022-01-21 22:16:28 -07:00
Jason Penilla
1dd0766422
fix provider when prop not set 2022-01-21 21:24:12 -07:00
Jason Penilla
bb9fdebccb
userdev: Skip source generation on CI when possible 2022-01-21 21:09:11 -07:00
Jason Penilla
5cfef1b1a5
1.3.4-SNAPSHOT 2021-12-21 01:09:07 -08:00
Jason Penilla
8f755267dd
Version 1.3.3 2021-12-21 01:07:50 -08:00
Jason Penilla
f038894430
Fix double actions on PRs 2021-12-21 00:47:34 -08:00
Jason
ff68ede186
back-compat for dev bundle v2/1.17.1 (#128) 2021-12-21 00:46:07 -08:00
Jason Penilla
d1647fa2cc
Improve failure message for jar runner 2021-12-18 20:34:45 -08:00
Jason Penilla
1f50d6696f
userdev: Use archivesName from BasePluginExtension for reobfJar name 2021-12-18 13:01:27 -08:00
Jason Penilla
1a8465dcf0
1.3.3-SNAPSHOT 2021-12-12 01:22:10 -08:00
Jason Penilla
931993e212
Version 1.3.2 2021-12-12 01:16:40 -08:00
Jason Penilla
638d21638b
Cleanup download result mapping 2021-12-10 17:03:06 -08:00
Jason Penilla
762221763b
userdev: Fix Minecraft manifest not re-downloading in some cases where it should have 2021-12-10 16:51:23 -08:00
Jason Penilla
d1290e3e84
Start 1.3.2-SNAPSHOT 2021-12-01 22:59:39 -08:00
Jason Penilla
b3ff269b57
Version 1.3.1 2021-12-01 22:55:40 -08:00
Jason Penilla
f1a54fe926
Don't log that library imports have been skipped from net/minecraft 2021-12-01 22:54:42 -08:00
Bjarne Koll
2ad4af9365
Change patcher's shadowJar to use server task (#121) 2021-12-01 03:54:06 -08:00
Jason Penilla
ed3112a32c
Use correct jar for upstream data
closes #123
2021-12-01 03:51:17 -08:00
Kyle Wood
ae902edac2
Start 1.3.1-SNAPSHOT 2021-11-30 19:00:31 -06:00
Kyle Wood
b305ef9553
Version 1.3.0 2021-11-30 18:59:39 -06:00
Kyle Wood
c839029bc4
Add a classifier to the mojmap jar
This means we will havea  -mojmap and a -reobf jar, rather than having
only a -reobf and a default jar. This should be much clearer.
2021-11-30 12:42:52 -06:00
Jason Penilla
516f1cdace
Fix api and mojangapi dependency coordinates 2021-11-29 21:32:38 -08:00
Kyle Wood
bfa8e20ff8
Improve generate dev bundle task configuration cache compatibility
This commit removes storing `Project` as a task input or field and
removes illegal method calls agains `Project` during task execution.

Unfortunately this is still not actually configuration cache compatible
as I haven't found a way to pass `ArtifactCollection` as a configuration
cache compatible task input. Using `Configuration` directly works on
first run, but Gradle is unable to load the configuration cache entry
for that task input.
2021-11-29 15:50:39 -06:00
Kyle Wood
27957e759a
Fix missing development bundle paperclip task input 2021-11-29 14:24:44 -06:00
Jason Penilla
6c9ab905dc
use ModuleId instead of triple 2021-11-28 23:52:24 -08:00
Jason Penilla
5b772c14fb
Configure serverLibrariesTxt for PaperweightCorePrepareForDownstream 2021-11-28 23:50:40 -08:00
Jason Penilla
777d41fc69
userdev: add mojangMappedServerRuntime configuration and deprecate PaperweightUserExtension#mojangMappedServerJar 2021-11-28 21:10:45 -08:00
Kyle Wood
8d47aaa469
Don't keep library-changes.json file inside the bundler jar 2021-11-28 23:08:49 -06:00
Jason Penilla
681cc42bd9
Use paperclip for dev bundles 2021-11-28 20:20:29 -08:00
Jason Penilla
11066cb1ba
Fix UpstreamData package 2021-11-28 20:02:34 -08:00
Kyle Wood
5988b6c42a
Implement creating patching Paperclip jars 2021-11-27 21:02:46 -06:00
Jason Penilla
235a0320f7
Fix record field access 2021-11-26 19:52:06 -08:00
Jason Penilla
ad0c94aff6
Fix inconsistency between output jar names 2021-11-26 16:17:46 -08:00
Jason Penilla
21bc470c31 Initial userdev update for 1.18 2021-11-26 03:11:41 -08:00
Jason Penilla
9ff8f17d8b Register bundler jar tasks earlier 2021-11-26 01:53:01 -08:00
Jason Penilla
ca9bc917bb Clean up bundler jar generation 2021-11-26 01:14:52 -08:00
Kyle Wood
0254dd1e1e
Implement Paperclip bundler jar building
This is only the base runnable jar, not the jar which will contain
Paperclip patches.
2021-11-25 23:51:12 -06:00
Jason Penilla
cf10510466
Remove record fix
No longer needed - mojang is using a patched version of proguard which doesn't mangle them
2021-11-24 13:34:57 -08:00
Jason Penilla
2d9d7b4d47
Put vanilla server in its own configuration 2021-11-24 13:28:48 -08:00
Kyle Wood
d7ad0cd46b
Update generateReobfMappings for 1.18 2021-11-24 03:09:20 -06:00
Kyle Wood
1777b820e7
Remove now unnecessary param annotation fixer 2021-11-24 00:04:03 -06:00
Kyle Wood
782dec5128
Add diff task for paperweight development 2021-11-24 00:03:44 -06:00
Kyle Wood
6bc03c39d9
Remove synthetic handling code for Spigot mapping generation
With MC 1.18 Spigot no longer uses the cursed synthetic "features" in
SpecialSource2.

The logger handler could have been removed in 1.17 but I forgot.
2021-11-23 21:14:12 -06:00
Jason Penilla
9e15ea3ce3
Add leading slash to ExtractFromBundler 2021-11-23 15:00:18 -08:00
Jason Penilla
7799a11289
Hackfix spigot mappings merge 2021-11-23 00:53:52 -08:00
Jason Penilla
79ccae2de2
Use patched mercury 2021-11-22 22:40:52 -08:00
Jason Penilla
7e88acb91a
header 2021-11-22 14:18:57 -08:00
Jason Penilla
489ab48d99
Reduce code duplication between fixjar tasks 2021-11-22 14:17:59 -08:00
Jason Penilla
c296e3d621
fixes for source remap 2021-11-22 04:08:15 -08:00
Jason Penilla
6a3961610e fixes for spigot mappings and remap 2021-11-22 00:03:25 -08:00
Jason Penilla
32d8d14faf Preliminary changes for 1.18 2021-11-21 21:12:37 -08:00
Jason Penilla
0d27315301
Always propagate cause for dev bundle resolution failure 2021-11-20 23:05:32 -08:00
Jason Penilla
7de5af8681
Bump Java for actions 2021-11-16 21:11:20 -08:00
Kyle Wood
311fad8f1f
Start 1.2.1-SNAPSHOT 2021-11-16 22:37:09 -06:00
Kyle Wood
d79a5eb21a
Version 1.2.0 2021-11-16 22:35:31 -06:00
Jason Penilla
d1c5911412 Remove all mappings for spigot recompiled synthetic members in reobf mappings 2021-11-16 22:28:50 -06:00
Jason Penilla
4895a3e32f Fix copyFieldMappings for classes that exist in the field mappings but not in the base mappings. 2021-11-16 22:27:39 -06:00
Jason Penilla
ee77d0c026 Bump version to 1.2.0-SNAPSHOT 2021-11-10 10:02:52 -08:00
Jason Penilla
a4a7fb8ed0 Use new untracked task API instead of upToDateWhen { false } 2021-11-10 10:02:52 -08:00
Jason Penilla
b70515b3d4
Update Gradle wrapper to 7.3 2021-11-09 15:30:49 -08:00
Jason Penilla
a110bc2055 Disable auto tag signing for patch repos 2021-11-09 13:42:26 -08:00
Jason Penilla
ba82b6a0e1
Update Hypo to 1.2.3 2021-11-07 15:53:01 -08:00
Jason Penilla
895da03fdc Fix reobf for synthetic this$ fields on non-static inner classes which spigot recompiles 2021-11-07 17:42:21 -06:00
Jason Penilla
71ff3a00e5 Check for conflicting signatures when adding synths 2021-11-06 23:33:14 -05:00
Jason
be6c16206d
Add missing check for ACC_PRIVATE when determining synthetics that will get mangled by SpecialSource (#107) 2021-11-05 19:06:22 -07:00
Jason Penilla
5787341579
Use LoggerFields Visitor 2021-11-04 19:53:57 -07:00
Jason Penilla
7a6d57c43d
Start 1.1.15-SNAPSHOT 2021-11-03 20:35:32 -07:00
Jason Penilla
d261be9f82
Version 1.1.14 2021-11-03 20:31:12 -07:00
Jason Penilla
835ea19220
Use correct slf4j impl 2021-11-03 20:23:06 -07:00
Jason Penilla
e7c19a8c3e
Start 1.1.14-SNAPSHOT 2021-11-03 19:58:33 -07:00
Jason Penilla
2cb7246128
Version 1.1.13 2021-11-03 16:59:13 -07:00
Jason
6215e929c1
Fix anonymous class order for source mappings (#99) 2021-11-03 12:18:44 -07:00
Jason
2948777623
Split mappings cleanup for sources into a separate task (#98) 2021-10-25 15:19:17 -07:00
Jason
a1b1b140a2
Update Hypo to 1.2.2 (#102) 2021-10-24 00:14:34 -07:00
Jason
d76e04dae4
Update Lorenz to 0.5.8 (#101) 2021-10-23 23:50:15 -07:00
Jason Penilla
00e63c1716 Bump dev bundle data version
Needed due to adding vanilla server libs to build data
2021-10-20 14:50:01 -05:00
Jason Penilla
b4115d1363 Remove UserdevSetup.kt#writeMinecraftLibrariesFile 2021-10-20 14:50:01 -05:00
Jason
938bd3a38a
Fix nullability of UpstreamData#libFile property & other small cleanup (#96) 2021-10-19 12:18:55 -07:00
Jason
f633ab45fc
Use server libraries from inspectVanillaJar for userdev + sources downloads (#95) 2021-10-19 12:15:11 -07:00
Jason
0777a1ce85
Apply reobf mappings patch for forks (#91) 2021-10-18 23:06:45 -07:00
Jason Penilla
91f1190772 Init submodules in clone tasks by default
Didn't think of this until after #92 was merged, but there isn't really any harm in running init submodules when there aren't submodules, and it's possible someone with an upstream using submodules could forget to set this, so it seems better to just enable it by default.
2021-10-13 10:27:31 +02:00
Jason Penilla
b4a8980717 Init submodules in clone task for paper upstreams
Gradle seems to like this more
2021-10-13 09:48:47 +02:00
Shane Freeder
abfb71758d Version 1.1.12 2021-10-03 02:21:17 +01:00
Jason
a6f0a96a0d
Add libraries as dependencies of the server artifact instead of to separate configurations (#88) 2021-10-02 14:03:14 -07:00
Jason
893d2e65d0
Apply ATs in userdev (#81) 2021-10-02 14:03:02 -07:00
Jason Penilla
699b849951 Make UserdevSetup a BuildService to allow sharing setup cache between subprojects if they depend on the same dev bundle 2021-09-29 16:11:15 -05:00
Octavia Togami
0a90aa2879
userdev: Set Obfuscation attribute on reobf configuration (#87) 2021-09-28 21:26:52 -07:00
Jason Penilla
1e3d63a29b Use Gradle property paperweight.filter-patches for patch filtering option instead of command line option
Works around gradle issue GH4311
2021-09-27 02:53:39 -05:00
Jason
5b6f778557
Update to Gradle 7.2 (#84) 2021-09-27 00:01:00 -07:00
Jason Penilla
24512fe2e3 Revert "Update for tiny-remapper 0.4.3 changes"
This reverts commit 4b329b2e8c.

Quilt now plans to simply mirror fabric instead of maintaining
their own TR fork, this allows us to switch back to upstream TR.
2021-09-27 02:00:19 -05:00
Jason Penilla
2672151036 Use provider for final remap jar instead of path
Fixes deprecation warning about task dependencies
2021-09-27 08:44:01 +02:00
Jason Penilla
e5da125708 Add remapped jar for bindings resolution
The AccessTransformerRewriter requires bindings to resolve in order to apply ATs, which wasn't happening for some methods. Adding the remapped jar to the classpath seems to have fixed this, what I'm unsure about is why it worked without this change for many methods.
2021-09-24 01:40:13 -05:00
Jason
5f5e44d341
Small readme updates (#80) 2021-09-23 23:34:33 -07:00
Jason Penilla
d7e97c0ae1 Synchronize access to AccessTransformSet$Class#getOrCreateClass 2021-09-24 01:33:23 -05:00
Jason Penilla
3695c2ebaf Update Lorenz, Atlas, and Mercury 2021-09-24 01:33:23 -05:00
Jason Penilla
2fe2f9de7f Complete inheritance in ApplyAccessTransform 2021-09-24 01:33:23 -05:00
Bram Hagens
80ab8588f7 Log files that are not imported 2021-09-21 14:35:18 +02:00
Jason Penilla
82b42acf85 Add userdev dependencies for tests 2021-09-09 12:57:17 -05:00
Jason Penilla
852f3bef3d Improve mcdev sources generation
I wasn't able to reproduce the issue on my install of Windows, but someone reported having modified files end up in the mcdev sources, this change should reduce chances of things going wrong on various platforms.
2021-08-24 19:58:22 -05:00
Jason Penilla
bbca9901a2 Fix #67 2021-08-24 15:57:53 -05:00
Jason Penilla
8cea4a9172 1.1.12-SNAPSHOT 2021-08-24 15:57:53 -05:00
MiniDigger
0953e65e3f 1.1.11 2021-08-18 20:18:11 +02:00
Jason Penilla
d330bd1b1b Revert "shade and relocate log4j-core"
This reverts commit 53b8d8e975.

Something changed in Gradle 7.2 in regards to logging, and this now
causes more log spam than it was solving.
2021-08-18 12:31:44 -05:00
Jason Penilla
621fed978f Revert "Make ApplyCraftBukkitPatches a cacheable task"
This reverts commit 54e712a6f6.

Seems hit or miss whether the git repo state gets maintained by the
build cache. This can probably be brought back in the future by zipping
the task output.
2021-08-18 12:31:44 -05:00
Jason Penilla
4c4fad3f1f Pass --ignore-whitespace for applying dev bundle patches 2021-08-18 08:26:13 +02:00
Jason Penilla
45f444c0ff Fix dev bundle check 2021-08-18 08:26:13 +02:00
Jason Penilla
55b37e7671 1.1.11-SNAPSHOT 2021-08-18 08:26:13 +02:00
Shane Freeder
8fdcfa09ab
Version 1.1.10 2021-08-17 21:57:29 +01:00
Jason Penilla
b7800816c8 Remove preference for AdoptOpenJDK toolchain
This avoids issues on Apple silicon as Adopt does not ship compatible builds.
2021-08-17 15:43:36 -05:00
Jason Penilla
99d657b170 Avoid experimental Byte.and extension function 2021-08-17 15:43:36 -05:00
Jason Penilla
39f64d8cc0 reduce code duplication for paperclip setup 2021-08-17 15:43:36 -05:00
Jason Penilla
a17e068923 add reobfJar as outgoing artifact of reobf configuration
this allows for depending on the reobf configuration of a project dependency
2021-08-17 15:43:36 -05:00
Jason Penilla
52e713826f add basic versioning to dev bundles 2021-08-17 15:43:36 -05:00
Jason Penilla
aea7613389 provide patched jar instead of paperclip to PaperweightUserExtension#mojangMappedServerJar 2021-08-17 15:43:36 -05:00
Jason Penilla
1b15e3708c improve up-to-date checking for setup actions 2021-08-17 15:43:36 -05:00
Jason Penilla
53b8d8e975 shade and relocate log4j-core
hypo uses log4j2 for logging, so when running mappings generation, userdev was giving a warning about missing log4j impl. core and patcher did not display this warning before as shadow provides a log4j2 impl, and is always used with core/patcher
2021-08-17 15:43:36 -05:00
Jason Penilla
0e4edf85ea automatically attach decompileJar as sources for the remapped minecraft jar 2021-08-17 15:43:36 -05:00
Jason Penilla
d47988d0bc use defaultDependencies callback instead of eachDependency
Using `eachDependency` allows for more flexibility, especially when working with transitive dependencies, however for our current use case it's kind of overkill, so just using `defaultDependencies` is simpler.
2021-08-17 15:43:36 -05:00
Jason Penilla
6dee585d50 move setup to internal package 2021-08-17 15:43:36 -05:00
Jason Penilla
9cc446e04c perform setup during dependency resolution instead of configuration 2021-08-17 15:43:36 -05:00
Jason Penilla
ca165dc4b8 fork dev bundle generation support 2021-08-17 15:43:36 -05:00
Jason Penilla
f26c0a7643 Use server jar url from version manifest instead of cb build data 2021-08-17 15:43:36 -05:00
Jason Penilla
2b07f488ab - Don't run setup when cleaning caches
- Add missing log message
- Remove various unused data from dev bundle
2021-08-17 15:43:36 -05:00
Jason Penilla
03bd820f8d Replace some functions with vals, use args lists from dev bundle config 2021-08-17 15:43:36 -05:00
Jason Penilla
4b329b2e8c Update for tiny-remapper 0.4.3 changes 2021-08-17 15:43:36 -05:00
Jason Penilla
bb0ba357f9 add a better error message when dev bundle is missing, add some apis to the PaperweightUserExtension 2021-08-17 15:43:36 -05:00
Jason Penilla
6d6f5e4aba move userdev setup tasks into configuration time
this allows for building and importing a project in the normal way without needing to first run the now removed setupPaperweightWorkspace task
2021-08-17 15:43:36 -05:00
Jason Penilla
fc5f69d579 Improve handling of CraftBukkit and relocated dependencies 2021-08-17 15:43:36 -05:00
Jason Penilla
a8194efde7 Progress on userdev 2021-08-17 15:43:36 -05:00
Kyle Wood
24a8b06ad2 Initial work on userdev 2021-08-17 15:43:36 -05:00
Jason Penilla
54e712a6f6 Make ApplyCraftBukkitPatches a cacheable task 2021-08-13 21:40:15 -05:00
Jason Penilla
c2805037f2 Apply shadow before trying to access shadowJar
This is already done in core

This fixes `generateReobfMappings` failing to configure when running `./gradlew tasks` before having applied patches
2021-08-11 12:22:11 -05:00
Jason Penilla
87e05b0843 Change default mc-dev sources location to be inside the server project
This makes it easier to "Find in files" for the entire server without also searching the rest of gradle caches
2021-07-31 18:16:24 -05:00
Kyle Wood
9e4a702d8c
Start 1.1.10-SNAPSHOT 2021-07-28 03:06:37 -05:00
Kyle Wood
3e266190bd
Version 1.1.9 2021-07-28 03:01:50 -05:00
Jason Penilla
102219b035 Move mc-dev sources to .gradle 2021-07-28 02:31:34 -05:00
Jason Penilla
59c469af45 Remove unneeded constant 2021-07-28 02:31:34 -05:00
Jason Penilla
f2be248c88 use extension property for mcdev source location 2021-07-28 02:31:34 -05:00
Jason Penilla
dd2cd269fd automatically setup mc-dev sources as a generated sources root for intellij 2021-07-28 02:31:34 -05:00
Jason Penilla
aba3695bb0 Ensure patchCraftBukkit runs when CraftBukkit has changed 2021-07-27 22:39:18 -05:00
Kyle Wood
481bbdfc18
Automoate and simplify process of resuming patch remapping 2021-07-11 15:57:07 -05:00
Kyle Wood
3dcafbb69d
Actually execute the create remapped-base tag command 2021-07-07 20:17:22 -05:00
Kyle Wood
93eff06e08
De-dupe git debug output 2021-07-07 20:00:47 -05:00
Kyle Wood
51368f2f46
Still capture git command output when using debug flag 2021-07-07 19:42:08 -05:00
Kyle Wood
1ecccc4667
Fix cache path string 2021-07-07 19:39:32 -05:00
Kyle Wood
3cd511f6df
Provide a more useful error message when git is not present 2021-07-03 22:58:41 -05:00
Kyle Wood
f5d09e8d1d
Move constants into a package for wildcard import support
Also add more entries in .editorconfig to help IntelliJ, and explicitly
list which packages should always have wildcard imports.
2021-07-03 22:45:33 -05:00
Kyle Wood
00db3813d5
Don't create the output data file for upstreams
Also introduce a new property to separate that value out from the taks
name. Using the same name for both things seemed kind of confusion. This
commit keeps them both for backwards compatibility, but once the version
after this current SNAPSHOT is released, I'll remove it.

Fixes #49
2021-07-03 22:18:33 -05:00
Kyle Wood
0938b9e579
Update to my new username 2021-07-03 21:13:52 -05:00
Kyle Wood
3fbb57516a
Update Hypo to 1.2.1 (fixes Windows + Java 8 issue)
Fixes #43
2021-07-03 21:12:24 -05:00
Kyle Wood
b4a6a098bf
Use Gradle's Java toolchain API for forking tasks 2021-07-03 21:12:24 -05:00
Kyle Wood
dfb3f6d88e
Re-work task configurations to support Gradle's build cache 2021-07-03 21:12:22 -05:00
Kyle Wood
f68b7016d1
Start 1.1.9-SNAPSHOT 2021-06-30 21:21:46 -05:00
Kyle Wood
0da32ff145
Fix style issues 2021-06-29 00:34:18 -05:00
Kyle Wood
200a6edddb
Version 1.1.8 2021-06-29 00:31:14 -05:00
Kyle Wood
5e7f49c63f
Fix forks now getting reobf packages from upstream
I did some of the plumbing to make this work but forgot to actually
connect the two parts between forks and upstrea.
2021-06-29 00:28:47 -05:00
Kyle Wood
6970deb6f3
Fixup comment 2021-06-26 22:55:40 -05:00
Kyle Wood
c67ebf0907
Start 1.1.8-SNAPSHOT 2021-06-26 21:56:20 -05:00
Kyle Wood
a1ca75456a
Version 1.1.7 2021-06-26 21:41:02 -05:00
Kyle Wood
6ff1f7d8d2
Allow ignore gitignore to be configurable with a Gradle property
The default is `true`.
2021-06-26 21:37:35 -05:00
Professor Bloodstone
c5bcd09cf4
Ignore gitignore when adding files in automation
There are idiots like me, who use global gitignore.
While I plan on improving that situation and already disabled global
gitignore for Paper repository,
this doesn't stop other's from asking for support/creating issues when
they hit it.

This should fix it. For reference, PR fixing it for old scripts:
https://github.com/PaperMC/Paper/pull/5461

Example error that happens when you put `target/` in your global
gitignore:
```sh
Applying: MC-145656 Fix Follow Range Initial Target
Patch failed at 0351 MC-145656 Fix Follow Range Initial Target
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
error: invalid object 100644 6cbd2fc4a7041f957966e5b09616e70aae63c0d4 for 'src/main/java/net/minecraft/world/entity/ai/goal/target/NearestAttackableTargetGoal.java'
error: Repository lacks necessary blobs to fall back on 3-way merge.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
```
2021-06-26 21:10:44 -05:00
Jason Penilla
163582fa5d Add remapClasspath to RemapJar
Needed to properly remap plugins
2021-06-26 19:07:25 -07:00
Kyle Wood
96a37ff164
Fork generateMappings task so it doesn't share memory with the daemon 2021-06-26 21:03:58 -05:00
Kyle Wood
16727e4555
Handle reobf issues caused by re-compilation and access changes
This commit includes a large comment which describes the problem it
addresses, so I won't re-iterate all of that here. It is important to
note that simply decompiling and recompiling the code can cause these
issues as well, since we tend to lose some of these fine-grain details
during decompilation.
2021-06-26 21:01:02 -05:00
Kyle Wood
45c18c1fb4
Support fork remapping properly (docs and examples coming) 2021-06-26 16:04:03 -05:00
Kyle Wood
a20fb06e82
Start 1.1.7-SNAPSHOT 2021-06-20 16:18:06 -05:00
Kyle Wood
566a138440
Version 1.1.6 2021-06-20 16:17:16 -05:00
Kyle Wood
3d16820c3c
Properly handle reobf of Spigot fields named if and do
SpecialSource is really stupid and automatically renames members which
have Java keyword names even though those mappings will only ever exist
in bytecode. Because of this, we have to have yet another special case
for SpecialSource. I guess it really is "special"...
2021-06-20 16:14:19 -05:00
Kyle Wood
e0ac40d687
Allow namespaces to be configurable for mappings patches 2021-06-20 15:29:49 -05:00
Kyle Wood
6de57d0a96
Start 1.1.6-SNAPSHOT 2021-06-20 02:40:30 -05:00
Kyle Wood
2d71260a90
Version 1.1.5 2021-06-20 02:40:00 -05:00
Kyle Wood
685abfa47e
Add support for patching generated reobf mappings
We can provide quick and easy workarounds for missing reobf mappings
while we work on properly fixing these issues by patching the generated
mappings.
2021-06-20 02:38:57 -05:00
Kyle Wood
f155d849f5
Start 1.1.5-SNAPSHOT 2021-06-20 01:50:45 -05:00
Kyle Wood
9f2e97c2bd
Version 1.1.4 2021-06-20 01:50:20 -05:00
Kyle Wood
2507973a4e
Consolidate and simplify library and mcdev imports into one file 2021-06-20 01:49:15 -05:00
Kyle Wood
dce96139e7
Start 1.1.4-SNAPSHOT 2021-06-19 23:58:00 -05:00
Kyle Wood
e24891ba33
Several improvements to fork support
Fork projects should perform better and be more stable now. Upstream
patch tasks will now cache as expected, with only the base patch tasks
not caching.

MCDev imports and paperclipJar should both work correctly now as well.
applyPatches and rebuildPatches should work first time as well as
repeat runs, including when running with new MCDev imports and changes
to upstream.

Fixes #25
2021-06-19 23:45:09 -05:00
Jason Penilla
2ce26f2fb1 use invoke instead of configure 2021-06-19 20:21:25 -07:00
Jason Penilla
be349eb335 Register paperclipJar earlier to allow for more eaily configuring it, use correct Jar import in PaperweightPatcher 2021-06-19 20:21:25 -07:00
Jaime Costas Insua
0cef58eace Manually create layout.cache if non existent 2021-06-19 16:06:00 -07:00
Jason Penilla
bb377a631f Use lazy configuration for source imports
This fixes the issue reported by BillyGalbreath in Paper Discord where needed files weren't being imported
2021-06-18 22:23:54 -07:00
Kyle Wood
e5f79b3cf3
Start 1.1.3-SNAPSHOT 2021-06-17 19:00:42 -05:00
Kyle Wood
2c8a13d17b
Version 1.1.2 2021-06-17 18:59:54 -05:00
Kyle Wood
fbe7faa524
Fix new inner classes not getting reobfed, helped by Hypo update
Several issues with how Hypo was handling inner classes is now fixed in
1.2.0.

Fixes #18
2021-06-17 18:58:45 -05:00
Kyle Wood
e575e5369c
Run Hypo in process isolation workers
This allows us to specify how much memory they need, so the Gradle
daemon doesn't need to be run with extra memory.
2021-06-17 14:37:55 -05:00
Kyle Wood
77fe06c637
Move patcher tasks around and create paperclipJar task for patcher
This is the same change I did in the core plugin but forgot to also do
it in patcher. This commit also adds the `paperweight` group to each of
these tasks so they will appear when `gradle tasks` is run.
2021-06-17 14:10:28 -05:00
Kyle Wood
b88846c519
Declare generateReobfMappings and downstream task before afterEvaluate
Fixes #21
2021-06-17 14:07:41 -05:00
MiniDigger
4a24323970 use a base tag for rebuilding api patches too 2021-06-17 20:25:53 +02:00
MiniDigger
a55034a39c delete files we don't care about from api and server folder 2021-06-17 19:34:19 +02:00
MiniDigger
b0cbf7ce9a actually bump version to next snapshot 2021-06-17 19:10:44 +02:00
Kyle Wood
7d1da0b2cb
Start 1.1.0-SNAPSHOT 2021-06-17 01:44:03 -05:00
Kyle Wood
23e8ad60fe
Version 1.1.0 2021-06-17 01:43:42 -05:00
Kyle Wood
a039727122
Include all package-info classes in the decompiled source set
This improves context and IDE assertions provided by package-info
annotations.
2021-06-17 01:42:39 -05:00
Kyle Wood
7013e8c220
Use Hypo to fix issues with reobf mappings
I had to make some updates in Hypo for this to work as well, so bump
Hypo version as well.

Fixes #17
2021-06-17 01:41:38 -05:00
Kyle Wood
ec35b28283
Start 1.0.5-SNAPSHOT 2021-06-16 20:41:37 -05:00
Kyle Wood
98b0aa7ae8
Version 1.0.5 - Call git fetch beefore rebuilding patches
This ensures that remote refs are updated before we use one to rebuild
patches from the wrong spot.
2021-06-16 20:41:07 -05:00
Kyle Wood
462fdcc26e
Start 1.0.4-SNAPSHOT 2021-06-16 01:47:09 -05:00
Kyle Wood
e0d4f19857
Version 1.0.4 - don't attempt to optimize bare repo patches 2021-06-16 01:46:20 -05:00
Kyle Wood
ebefb33061
Start 1.0.3-SNAPSHOT 2021-06-16 01:12:54 -05:00
Kyle Wood
be84234e8d
Version 1.0.3 2021-06-16 01:12:26 -05:00
Kyle Wood
38d7bc2a79
Allow patching bare directories (which aren't git repos)
This simply initializes the bare repo first to allow patches to be
applied on it via git like other standard git patchers.

Fixes #11
2021-06-16 01:11:10 -05:00
Kyle Wood
6c4f5c0022
Maintain .git directory when applying patches
This should help mitigate a lot of issues we've been having (hopefully),
but also has the benefit of keeping repo state in the patched repo (such
as stashes).

Fixes #16
2021-06-16 00:39:38 -05:00
Kyle Wood
e988e70817
Bump Hypo to 1.0.2 2021-06-15 20:37:34 -05:00
Kyle Wood
9734ac1a56
Fix compile (convention isn't as flexible as set) 2021-06-15 20:37:07 -05:00
Kyle Wood
d5753c0c6f
Automatically wire mcdev imports for fork server patch tasks
Fixes #15
2021-06-15 20:29:19 -05:00
Kyle Wood
745018d4b4
Remove gradle/wrapper-validation-action
This action seems to fail randomly all the time
2021-06-15 19:53:45 -05:00
Kyle Wood
970938e158
Start 1.0.2-SNAPSHOT 2021-06-15 19:44:05 -05:00
Kyle Wood
2f745e4287
Version 1.0.2 2021-06-15 19:43:34 -05:00
MiniDigger
784690a92a chunk rebuildpaperpatches for windows support 2021-06-16 00:31:12 +02:00
MiniDigger
46a816d5f0 also use our own cache dir in ApplyGitPatches 2021-06-15 19:15:19 +02:00
MiniDigger
3e5cce729c add a prefix to the remapping temp folder
no gradle, I don't want you to use my WINDOWS dir as a fucking temp folder, wtf
2021-06-15 19:08:00 +02:00
Kyle Wood
ee4194f682
Start 1.0.1-SNAPSHOT 2021-06-15 01:16:44 -05:00
Kyle Wood
b7b6b3d270
Fix MergeAccessTransforms task not wiring task dependencies 2021-06-15 01:16:08 -05:00
Kyle Wood
ccafe9a3da
Snapshot until next deploy 2021-06-15 00:49:27 -05:00
Kyle Wood
bf61f803f3
Version 1.0.0 2021-06-15 00:44:17 -05:00
Kyle Wood
788e691dc3 Improve optional file handling and make library-imports more lenient 2021-06-14 22:43:46 -07:00
Jason Penilla
26b8e25f8a Automatically find library imports 2021-06-14 22:43:46 -07:00
Kyle Wood
ed900662d2
Implement snapshot and release publishing 2021-06-14 23:49:52 -05:00
Kyle Wood
cfcb0ef378
Apply additional ATs to spigot remapped sources as well 2021-06-14 22:56:35 -05:00
Kyle Wood
cb32d78534
Update to Gradle 7.1, fix generatePaperclipPatch output property 2021-06-14 21:17:07 -05:00
Jason Penilla
22010b0473 remove unused imports 2021-06-14 17:39:01 -07:00
Jason Penilla
ba606503f0 Apply additional access transformers earlier so that they are applied to classes we don't import into the source tree 2021-06-14 17:39:01 -07:00
MiniDigger
89a2b1612c use extension for set applyServerPatches input to prevent circular dependency 2021-06-14 22:25:04 +02:00
MiniDigger
f6a071c138 add mcdevsource task, closes #9 2021-06-14 22:14:33 +02:00
MiniDigger
4205a4fa46 use AT functionality 2021-06-14 21:58:15 +02:00
MiniDigger
36186fca7a use proper repos 2021-06-14 20:55:36 +02:00
MiniDigger
5212332f40 deploy to paper repo 2021-06-14 20:48:10 +02:00
MiniDigger
2fea06ded4 add AT support 2021-06-14 19:38:40 +02:00
Kyle Wood
4f1e0f6fdf
Remove changes I didn't mean to push 2021-06-13 20:37:57 -05:00
Kyle Wood
2c9e0bd64c
Fix paperclipJar task referencing wrong Jar 2021-06-13 20:35:02 -05:00
Kyle Wood
5f0989c035
Add jvmargs configuration to paperclip and forgeflower tasks 2021-06-13 18:18:29 -05:00
Kyle Wood
c6dfaa3b03
Change name of paperclipJar task so it doesn't run on a normal build 2021-06-13 17:56:38 -05:00
Kyle Wood
a84caebca1
Add --skip-remapping-patches argument to remapPatches task for forks 2021-06-13 17:23:23 -05:00
Kyle Wood
075f71b02e
Fix patching and fork issues on Windows
Includes workarounds for Windows' case-insensitive file system and
command-line length limits.
2021-06-13 17:00:06 -05:00
MiniDigger
58fd8ccded fork paperclip task to avoid OOM 2021-06-13 21:11:47 +02:00
MiniDigger
e8128c09de disable hard exist check for mcdev for now 2021-06-13 11:23:17 +02:00
Kyle Wood
d989202788
Improve paperweight fork handling
Improves patch task naming and fixes the initial commit message for the
`base` commit of the fork clone directory.
2021-06-12 23:52:00 -05:00
Kyle Wood
228946811d
Fix forks pulling in non-server libraries 2021-06-12 23:12:55 -05:00
Kyle Wood
57a29a114f
Fix reobfJar task configuration handling
tiny-remapper needed an updated version to handle reobf properly, but
the new version didn't have a runnable jar artifact for us to use. So
instead, we need to run the whole configuration (including
dependencies).
2021-06-12 22:14:38 -05:00
Kyle Wood
d73f6d6519
Improve paperweight fork support for non-repo upstreams
This will allow forks to continue using submodules if that's really what
they want to do.
2021-06-12 17:42:45 -05:00
MiniDigger
ec46558598 fix ktlint 2021-06-12 20:16:00 +02:00
MiniDigger
5a4ed40c3e make sure to copy the right war and add deps to the right project for forks
forks now work \o/
2021-06-12 20:09:29 +02:00
MiniDigger
3fe8607ce9 bunch of work on fork stuff
this needs cleaning up
2021-06-12 17:40:01 +02:00
MiniDigger
f115f63dba gson path fix needs the whole hierarchy 2021-06-12 13:45:44 +02:00
MiniDigger
64de449fab move fork tasks to paperweight group, fix gson path issue 2021-06-12 12:50:30 +02:00
MiniDigger
8952ed266c allow comments in mcdev-imports.txt 2021-06-12 10:59:50 +02:00
Kyle Wood
f7100e7e78
Update Hypo (fixes running on Java 8) 2021-06-11 23:26:12 -05:00
MiniDigger
0664751fef task group should be lowercase
hope you happy now zml
2021-06-12 03:00:44 +02:00
MiniDigger
6a3fdbe961 move some frequently used tasks to the paper group 2021-06-12 02:48:41 +02:00
Kyle Wood
3dfbb4b1a0
Fix removed field and formatting 2021-06-11 18:17:07 -05:00
Kyle Wood
2cbae7a1e7
Add reobf mappings generation to allow reobfJar task to complete 2021-06-11 18:12:31 -05:00
Kyle Wood
6406e3c1b4
Improve rebuild patches task names 2021-06-11 16:43:09 -05:00
Kyle Wood
fe3d49f586
Don't delete target directory during patch apply
Not deleting the target directory prevents open terminals from throwing
errors after the directory is deleted and recreated.
2021-06-11 16:42:53 -05:00
Kyle Wood
046a54d1ba
Fix McDev for Paper added classes and add ability to add new classes 2021-06-11 15:07:39 -05:00
Kyle Wood
caccbb1570
Hopefully actually fix library relocating 2021-06-11 06:17:38 -05:00
Kyle Wood
b4f5e44ba2
Fix relocating in published plugin 2021-06-11 05:55:27 -05:00
Kyle Wood
33842f05ed
Update for Spigot 1.17 2021-06-11 05:35:41 -05:00
Kyle Wood
204729678a
Use json for upstream data file 2021-06-10 22:57:42 -05:00
Kyle Wood
db46cc1ddb
Remove now un-used inject annotation 2021-06-09 20:58:59 -05:00
Kyle Wood
a384d7eef4
Initial work on supporting forks 2021-06-09 20:55:14 -05:00
Kyle Wood
6b22ccdf03
Convert all (or most) file system operations to use Path
This solves a variety of issues regarding file access and cleans up the
code quite a bit as well. The NIO.2 APIs combined with Kotlin's new Path
extensions leads to a nice experience.

Closed #7
2021-05-16 19:38:22 -05:00
Kyle Wood
217f2ef61d
Make patch remapping more useable by alloweing to to be resumed
This allows cases where our changes to Spigot's sources that we have to
do to fix cases of bad mappings can be addressed per-patch during the
patch remap process, and continued from.

If an original patch fails to apply, the user can go into the patch
remap directory, fix the patch, run

    git am --continue

and then go back to the Paper directory and restart the process with:

    ./gradlew remapPatches --skip-patches=<number of patch that failed>

The remap patches process will then finish remapping and applying the
patch that failed, and continue with the other patches from there.

Since patch remapping can take so long, this QoL improvement is
essential for allowing Paper forks to be able to remap their own patches
when the time comes.
2021-05-11 21:04:31 -05:00
Kyle Wood
14018872ca
Cache more in GitHub Actions 2021-05-09 22:48:19 -05:00
Kyle Wood
b4baf33b02
Fix deploy config 2021-05-09 22:46:16 -05:00
Kyle Wood
ef81ce64e1
Remove code left around while I was experimenting 2021-05-09 22:23:47 -05:00
Kyle Wood
7510adddb4
Relocate Hypo's new package 2021-05-09 22:23:11 -05:00
Kyle Wood
27cfd2070f
Fix format 2021-05-09 22:20:07 -05:00
Kyle Wood
2bf60cded4
Move tasks into their own classes, for cleaner access
Still maintains task declaration separation, but now all tasks declared
are accessible from the same object without having to manually change
several different places to access other tasks.

This abuses Kotlin default parameter syntax to replicate local variables
for the implicit init { ... } block, but the result isn't that bad.
2021-05-09 22:18:28 -05:00
Kyle Wood
bb8978d0ca
Update Hypo and minor improvements 2021-05-09 21:33:08 -05:00
Kyle Wood
c38cb87411
Upgrade to dependency catalogs 2021-05-07 18:17:06 -05:00
Kyle Wood
bb83b90c47
Update to Gradle 7 and Hypo release 2021-05-07 17:41:46 -05:00
Kyle Wood
3ffc238bbb
Fix remapPatches 2021-04-24 22:55:20 -05:00
Kyle Wood
98f2794d18
Fixes for 1.16.5, debug command output, remap tests, etc 2021-04-24 22:14:20 -05:00
Kyle Wood
37932ac17b
Switch to QuiltMC 2021-04-24 17:23:15 -05:00
Kyle Wood
e9b2021d4a
Add NMS relocation support 2021-04-24 16:40:48 -05:00
Kyle Wood
7d200300c2
Merge branch 'proximyst' into testing/hypo 2021-04-24 16:32:34 -05:00
Kyle Wood
2aba740750
Transition to using Hypo for mappings transformations and cleanup
This allows the reobfJar task to work without issue.
2021-04-24 16:18:07 -05:00
Mariell Hoversholm
bbe536e51b
applyCraftBukkit now works 2021-03-27 23:45:02 +01:00
Kyle Wood
5c07640131
Switch reobf back to tiny-remapper so we can figure out the issues 2021-01-17 02:59:11 -08:00
186 changed files with 16426 additions and 4547 deletions

View file

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

View file

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

35
.github/workflows/deploy-snapshot.yml vendored Normal file
View file

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

View file

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

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

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

4
.gitignore vendored
View file

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

View file

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

22
buildSrc/build.gradle.kts Normal file
View file

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

View file

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

View file

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

View file

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

5
gradle.properties Normal file
View file

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

46
gradle/libs.versions.toml Normal file
View file

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

Binary file not shown.

View file

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

294
gradlew vendored
View file

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

35
gradlew.bat vendored
View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -20,8 +20,10 @@
* USA
*/
package io.papermc.paperweight.ext
package io.papermc.paperweight.core.extension
import io.papermc.paperweight.util.*
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
@ -45,16 +47,14 @@ open class CraftBukkitExtension(objects: ObjectFactory, workDir: DirectoryProper
val specialSourceJar: RegularFileProperty = objects.fileFrom(buildDataBinDir, "SpecialSource.jar")
val specialSource2Jar: RegularFileProperty = objects.fileFrom(buildDataBinDir, "SpecialSource-2.jar")
private fun ObjectFactory.bukkitFileFrom(base: DirectoryProperty, extension: String): RegularFileProperty =
fileProperty().convention(
base.flatMap { dir ->
val file = dir.asFile.listFiles()?.firstOrNull { it.name.endsWith(extension) }
if (file != null) {
mappingsDir.file(file.name)
} else {
// empty
fileProperty()
}
private fun ObjectFactory.bukkitFileFrom(base: DirectoryProperty, extension: String): RegularFileProperty = fileProperty().convention(
base.flatMap { dir ->
val file = dir.path.useDirectoryEntries { it.filter { f -> f.name.endsWith(extension) }.singleOrNull() }
if (file != null) {
mappingsDir.file(file.name)
} else {
fileProperty()
}
)
}
)
}

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -20,33 +20,38 @@
* USA
*/
package io.papermc.paperweight.ext
package io.papermc.paperweight.core.extension
import io.papermc.paperweight.util.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.kotlin.dsl.*
open class PaperExtension(objects: ObjectFactory, layout: ProjectLayout) {
@Suppress("MemberVisibilityCanBePrivate")
val baseTargetDir: DirectoryProperty = objects.dirWithDefault(layout, ".")
val spigotApiPatchDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "Spigot-API-Patches")
val spigotServerPatchDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "Spigot-Server-Patches")
val remappedSpigotServerPatchDir: DirectoryProperty = objects.dirFrom(
baseTargetDir,
"Spigot-Server-Patches-Remapped"
)
val unmappedSpigotServerPatchDir: DirectoryProperty = objects.dirFrom(
baseTargetDir,
"Spigot-Server-Patches-Unmapped"
)
val spigotApiPatchDir: DirectoryProperty = objects.dirFrom(baseTargetDir, "patches/api")
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")
@Suppress("MemberVisibilityCanBePrivate")
val buildDataDir: DirectoryProperty = objects.dirWithDefault(layout, "build-data")
val additionalSpigotClassMappings: RegularFileProperty = objects.fileProperty()
val additionalSpigotMemberMappings: RegularFileProperty = objects.fileProperty()
val libraryClassImports: RegularFileProperty = objects.fileFrom(buildDataDir, "library-imports.txt")
val devImports: RegularFileProperty = objects.fileFrom(buildDataDir, "dev-imports.txt")
val additionalAts: RegularFileProperty = objects.fileFrom(buildDataDir, "paper.at")
val reobfMappingsPatch: RegularFileProperty = objects.fileProperty()
val mappingsPatch: RegularFileProperty = objects.fileProperty()
val craftBukkitPatchPatchesDir: DirectoryProperty = objects.directoryProperty()
val spigotServerPatchPatchesDir: DirectoryProperty = objects.directoryProperty()
val spigotApiPatchPatchesDir: DirectoryProperty = objects.directoryProperty()
val reobfPackagesToFix: ListProperty<String> = objects.listProperty()
}

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -20,37 +20,57 @@
* USA
*/
package io.papermc.paperweight.ext
package io.papermc.paperweight.core.extension
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.util.Locale
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.ProjectLayout
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.kotlin.dsl.property
import org.gradle.kotlin.dsl.*
open class PaperweightExtension(objects: ObjectFactory, layout: ProjectLayout) {
open class PaperweightCoreExtension(project: Project, objects: ObjectFactory, layout: ProjectLayout) {
@Suppress("MemberVisibilityCanBePrivate")
val workDir: DirectoryProperty = objects.dirWithDefault(layout, "work")
val minecraftVersion: Property<String> = objects.property()
val versionPackage: Property<String> = objects.property()
val serverProject: Property<Project> = objects.property()
val mainClass: Property<String> = objects.property<String>().convention("org.bukkit.craftbukkit.Main")
val bundlerJarName: Property<String> = objects.property<String>().convention(project.name.lowercase(Locale.ENGLISH))
val mcDevSourceDir: DirectoryProperty = objects.directoryProperty().convention(serverProject.map { it.layout.cacheDir(MC_DEV_SOURCES_DIR) })
val paramMappingsRepo: Property<String> = objects.property()
val decompileRepo: Property<String> = objects.property()
val remapRepo: Property<String> = objects.property()
val vanillaJarIncludes: ListProperty<String> = objects.listProperty<String>().convention(
listOf("/*.class", "/net/minecraft/**", "/com/mojang/math/**")
)
@Suppress("MemberVisibilityCanBePrivate")
val craftBukkit = CraftBukkitExtension(objects, workDir)
val spigot = SpigotExtension(objects, workDir)
val paper = PaperExtension(objects, layout)
@Suppress("unused")
fun craftBukkit(action: Action<in CraftBukkitExtension>) {
action.execute(craftBukkit)
}
@Suppress("unused")
fun spigot(action: Action<in SpigotExtension>) {
action.execute(spigot)
}
@Suppress("unused")
fun paper(action: Action<in PaperExtension>) {
action.execute(paper)
}

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -20,12 +20,14 @@
* USA
*/
package io.papermc.paperweight.ext
package io.papermc.paperweight.core.extension
import io.papermc.paperweight.util.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.model.ObjectFactory
open class SpigotExtension(objects: ObjectFactory, workDir: DirectoryProperty) {
@Suppress("MemberVisibilityCanBePrivate")
val spigotDir: DirectoryProperty = objects.dirFrom(workDir, "Spigot")
val spigotApiDir: DirectoryProperty = objects.dirFrom(spigotDir, "Spigot-API")

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,14 +22,16 @@
package io.papermc.paperweight
import io.papermc.paperweight.util.convertToFile
import io.papermc.paperweight.util.convertToUrl
import java.io.File
import io.papermc.paperweight.util.*
import java.net.URL
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.time.Instant
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.io.path.*
import org.apache.http.HttpHost
import org.apache.http.HttpStatus
import org.apache.http.client.config.CookieSpecs
@ -39,34 +41,67 @@ 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.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 {
private companion object {
val LOGGER: Logger = Logging.getLogger(DownloadService::class.java)
}
private val httpClient: CloseableHttpClient = HttpClientBuilder.create().let { builder ->
builder.setRetryHandler { _, count, _ -> count < 3 }
builder.useSystemProperties()
builder.build()
}
fun download(source: Any, target: Any) {
fun download(source: Any, target: Any, hash: Hash? = null) {
val url = source.convertToUrl()
val file = target.convertToFile()
download(url, file)
val file = target.convertToPath()
download(url, file, hash)
}
private fun download(source: URL, target: File) {
target.parentFile.mkdirs()
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) {
return
}
LOGGER.warn(
"{} hash of downloaded file '{}' does not match what was expected! (expected: '{}', got: '{}')",
hash.algorithm.name,
target,
hash.valueLower,
dlHash
)
if (retry) {
throw PaperweightException("Failed to download file '$target' from '$source'.")
}
LOGGER.warn("Re-attempting download once before giving up.")
target.deleteIfExists()
download(source, target, hash, true)
}
val etagFile = target.resolveSibling(target.name + ".etag")
private fun download(source: URL, target: Path) {
target.parent.createDirectories()
val etagDir = target.resolveSibling("etags")
etagDir.createDirectories()
val etagFile = etagDir.resolve(target.name + ".etag")
val etag = if (etagFile.exists()) etagFile.readText() else null
val host = HttpHost(source.host, source.port, source.protocol)
val time = if (target.exists()) target.lastModified() else 0
val time = if (target.exists()) target.getLastModifiedTime().toInstant() else Instant.EPOCH
val httpGet = HttpGet(source.file)
// Super high timeout, reduce chances of weird things going wrong
// high timeout, reduce chances of weird things going wrong
val timeouts = TimeUnit.MINUTES.toMillis(5).toInt()
httpGet.config = RequestConfig.custom()
@ -76,46 +111,45 @@ abstract class DownloadService : BuildService<BuildServiceParameters.None>, Auto
.setCookieSpec(CookieSpecs.STANDARD)
.build()
if (time > 0) {
val value = DateTimeFormatter.RFC_1123_DATE_TIME.format(Instant.ofEpochMilli(time).atZone(ZoneOffset.UTC))
httpGet.setHeader("If-Modified-Since", value)
}
if (etag != null) {
httpGet.setHeader("If-None-Match", etag)
if (target.exists()) {
if (time != Instant.EPOCH) {
val value = DateTimeFormatter.RFC_1123_DATE_TIME.format(time.atZone(ZoneOffset.UTC))
httpGet.setHeader("If-Modified-Since", value)
}
if (etag != null) {
httpGet.setHeader("If-None-Match", etag)
}
}
httpClient.execute(host, httpGet).use { response ->
val code = response.statusLine.statusCode
if ((code < 200 || code > 299) && code != HttpStatus.SC_NOT_MODIFIED) {
if (code !in 200..299 && code != HttpStatus.SC_NOT_MODIFIED) {
val reason = response.statusLine.reasonPhrase
throw PaperweightException("Download failed, HTTP code: $code; URL: $source; Reason: $reason")
}
val lastModified = handleResponse(response, time, target)
val lastModified = handleResponse(response, target)
saveEtag(response, lastModified, target, etagFile)
}
}
private fun handleResponse(response: CloseableHttpResponse, time: Long, target: File): Long {
private fun handleResponse(response: CloseableHttpResponse, target: Path): Instant {
val lastModified = with(response.getLastHeader("Last-Modified")) {
if (this == null) {
return@with 0
return@with Instant.EPOCH
}
if (value.isNullOrBlank()) {
return@with 0
return@with Instant.EPOCH
}
val date = DateUtils.parseDate(value) ?: return@with 0
return@with date.time
return@with DateUtils.parseDate(value).toInstant() ?: Instant.EPOCH
}
if (response.statusLine.statusCode == HttpStatus.SC_NOT_MODIFIED) {
if (lastModified != 0L && time >= lastModified) {
return lastModified
}
return lastModified
}
val entity = response.entity ?: return lastModified
entity.content.use { input ->
target.outputStream().buffered().use { output ->
target.outputStream().use { output ->
entity.content.use { input ->
input.copyTo(output)
}
}
@ -123,9 +157,9 @@ abstract class DownloadService : BuildService<BuildServiceParameters.None>, Auto
return lastModified
}
private fun saveEtag(response: CloseableHttpResponse, lastModified: Long, target: File, etagFile: File) {
if (lastModified > 0) {
target.setLastModified(lastModified)
private fun saveEtag(response: CloseableHttpResponse, lastModified: Instant, target: Path, etagFile: Path) {
if (lastModified != Instant.EPOCH) {
target.setLastModifiedTime(FileTime.from(lastModified))
}
val header = response.getFirstHeader("ETag") ?: return

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -24,5 +24,5 @@ package io.papermc.paperweight
class PaperweightException : Exception {
constructor(message: String) : super(message)
constructor(message: String, cause: Throwable) : super(message, cause)
constructor(message: String, cause: Throwable?) : super(message, cause)
}

View file

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

View file

@ -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.extension
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.kotlin.dsl.property
abstract class PaperweightServerExtension(objects: ObjectFactory) {
val craftBukkitPackageVersion: Property<String> = objects.property()
}

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,55 +22,49 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.fileOrNull
import java.io.File
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class AddAdditionalSpigotMappings : BaseTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val classSrg: RegularFileProperty
@get:InputFile
abstract val memberSrg: RegularFileProperty
@get:Optional
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val additionalClassEntriesSrg: RegularFileProperty
@get:Optional
@get:InputFile
abstract val additionalMemberEntriesSrg: RegularFileProperty
@get:OutputFile
abstract val outputClassSrg: RegularFileProperty
@get:OutputFile
abstract val outputMemberSrg: RegularFileProperty
override fun init() {
outputClassSrg.convention(defaultOutput("class.csrg"))
outputMemberSrg.convention(defaultOutput("member.csrg"))
}
@TaskAction
fun run() {
addLines(classSrg.file, additionalClassEntriesSrg.fileOrNull, outputClassSrg.file)
addLines(memberSrg.file, additionalMemberEntriesSrg.fileOrNull, outputMemberSrg.file)
addLines(classSrg.path, additionalClassEntriesSrg.pathOrNull, outputClassSrg.path)
}
private fun addLines(inFile: File, appendFile: File?, outputFile: File) {
private fun addLines(inFile: Path, appendFile: Path?, outputFile: Path) {
val lines = mutableListOf<String>()
inFile.useLines { seq ->
seq.filter { it.startsWith("") }
}
inFile.forEachLine { line -> lines += line }
inFile.useLines { seq -> seq.forEach { line -> lines += line } }
appendFile?.useLines { seq -> seq.forEach { lines.add(it) } }
lines.sort()
outputFile.bufferedWriter().use { writer ->
lines.forEach { writer.appendln(it) }
lines.forEach { writer.appendLine(it) }
}
}
}

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -20,32 +20,33 @@
* USA
*/
package io.papermc.paperweight.tasks.patchremap
package io.papermc.paperweight.tasks
import io.papermc.paperweight.tasks.BaseTask
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.ensureDeleted
import io.papermc.paperweight.util.ensureParentExists
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.orNull
import io.papermc.paperweight.util.path
import io.papermc.paperweight.util.*
import java.nio.file.Path
import javax.inject.Inject
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.AccessTransformFormats
import org.cadixdev.atlas.Atlas
import org.cadixdev.atlas.AtlasTransformerContext
import org.cadixdev.bombe.analysis.InheritanceProvider
import org.cadixdev.bombe.asm.analysis.ClassProviderInheritanceProvider
import org.cadixdev.bombe.asm.jar.ClassProvider
import org.cadixdev.bombe.jar.JarClassEntry
import org.cadixdev.bombe.jar.JarEntryTransformer
import org.cadixdev.bombe.type.signature.MethodSignature
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.submit
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkQueue
import org.gradle.workers.WorkerExecutor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
@ -54,38 +55,67 @@ import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
abstract class ApplyAccessTransform : BaseTask() {
fun applyAccessTransform(
inputJarPath: Path,
outputJarPath: Path,
atFilePath: Path,
jvmArgs: List<String> = listOf("-Xmx1G"),
workerExecutor: WorkerExecutor,
launcher: JavaLauncher
): WorkQueue {
ensureParentExists(outputJarPath)
ensureDeleted(outputJarPath)
@get:InputFile
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmArgs)
forkOptions.executable(launcher.executablePath.path.absolutePathString())
}
queue.submit(ApplyAccessTransform.AtlasAction::class) {
inputJar.set(inputJarPath)
atFile.set(atFilePath)
outputJar.set(outputJarPath)
}
return queue
}
@CacheableTask
abstract class ApplyAccessTransform : JavaLauncherTask() {
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val atFile: RegularFileProperty
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmargs.convention(listOf("-Xmx1G"))
outputJar.convention(defaultOutput())
}
@TaskAction
fun run() {
ensureParentExists(outputJar.file)
ensureDeleted(outputJar.file)
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs("-Xmx1G")
}
queue.submit(AtlasAction::class) {
inputJar.set(this@ApplyAccessTransform.inputJar.file)
atFile.set(this@ApplyAccessTransform.atFile.file)
outputJar.set(this@ApplyAccessTransform.outputJar.file)
}
applyAccessTransform(
inputJarPath = inputJar.path,
outputJarPath = outputJar.path,
atFilePath = atFile.path,
jvmArgs = jvmargs.get(),
workerExecutor = workerExecutor,
launcher = launcher.get()
)
}
abstract class AtlasAction : WorkAction<AtlasParameters> {
@ -94,7 +124,21 @@ abstract class ApplyAccessTransform : BaseTask() {
Atlas().apply {
install {
AtJarEntryTransformer(at)
// Replace the inheritance provider to set ASM9 opcodes
val inheritanceProvider = AtlasTransformerContext::class.java.getDeclaredField("inheritanceProvider")
inheritanceProvider.isAccessible = true
val classProvider = ClassProviderInheritanceProvider::class.java.getDeclaredField("provider")
classProvider.isAccessible = true
inheritanceProvider.set(
it,
ClassProviderInheritanceProvider(
Opcodes.ASM9,
classProvider.get(inheritanceProvider.get(it)) as ClassProvider
)
)
// End replace inheritance provider
AtJarEntryTransformer(it, at)
}
run(parameters.inputJar.path, parameters.outputJar.path)
}
@ -108,21 +152,25 @@ abstract class ApplyAccessTransform : BaseTask() {
}
}
class AtJarEntryTransformer(private val at: AccessTransformSet) : JarEntryTransformer {
class AtJarEntryTransformer(
private val context: AtlasTransformerContext,
private val at: AccessTransformSet
) : JarEntryTransformer {
override fun transform(entry: JarClassEntry): JarClassEntry {
val reader = ClassReader(entry.contents)
val writer = ClassWriter(reader, 0)
reader.accept(AccessTransformerVisitor(at, writer), 0)
reader.accept(AccessTransformerVisitor(at, context.inheritanceProvider(), writer), 0)
return JarClassEntry(entry.name, entry.time, writer.toByteArray())
}
}
class AccessTransformerVisitor(
private val at: AccessTransformSet,
private val inheritanceProvider: InheritanceProvider,
writer: ClassWriter
) : ClassVisitor(Opcodes.ASM7, writer) {
) : ClassVisitor(Opcodes.ASM9, writer) {
private var classTransform: AccessTransformSet.Class? = null
private lateinit var classTransform: AccessTransformSet.Class
override fun visit(
version: Int,
@ -132,8 +180,8 @@ class AccessTransformerVisitor(
superName: String?,
interfaces: Array<out String>?
) {
classTransform = at.getClass(name).orNull
super.visit(version, classTransform?.get().apply(access), name, signature, superName, interfaces)
classTransform = completedClassAt(name)
super.visit(version, classTransform.get().apply(access), name, signature, superName, interfaces)
}
override fun visitField(
@ -143,7 +191,7 @@ class AccessTransformerVisitor(
signature: String?,
value: Any?
): FieldVisitor {
return super.visitField(classTransform?.getField(name).apply(access), name, descriptor, signature, value)
return super.visitField(classTransform.getField(name).apply(access), name, descriptor, signature, value)
}
override fun visitMethod(
@ -153,23 +201,26 @@ class AccessTransformerVisitor(
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val newAccess = classTransform?.getMethod(MethodSignature.of(name, descriptor)).apply(access)
val newAccess = classTransform.getMethod(MethodSignature.of(name, descriptor)).apply(access)
return super.visitMethod(newAccess, name, descriptor, signature, exceptions)
}
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
super.visitInnerClass(name, outerName, innerName, at.getClass(name).orNull?.get().apply(access))
super.visitInnerClass(name, outerName, innerName, completedClassAt(name).get().apply(access))
}
private fun completedClassAt(className: String): AccessTransformSet.Class = synchronized(at) {
at.getOrCreateClass(className).apply { complete(inheritanceProvider) }
}
}
private const val RESET_ACCESS: Int = (Opcodes.ACC_PUBLIC or Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED).inv()
fun AccessTransform?.apply(currentModifier: Int): Int {
if (this == null) {
return currentModifier
}
var value = currentModifier
if (this.access != AccessChange.NONE) {
value = value and RESET_ACCESS
value = value and AsmUtil.RESET_ACCESS
value = value or this.access.modifier
}
when (this.final) {
@ -179,7 +230,8 @@ fun AccessTransform?.apply(currentModifier: Int): Int {
ModifierChange.ADD -> {
value = value or Opcodes.ACC_FINAL
}
else -> {}
else -> {
}
}
return value
}

View file

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

View file

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

View file

@ -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.tasks
import com.github.salomonbrys.kotson.fromJson
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
abstract class ApplyPaperPatches : ControllableOutputTask() {
@get:InputDirectory
abstract val patchDir: DirectoryProperty
@get:InputFile
abstract val remappedSource: RegularFileProperty
@get:InputFile
abstract val remappedTests: RegularFileProperty
@get:InputFile
abstract val caseOnlyClassNameChanges: RegularFileProperty
@get:InputDirectory
abstract val upstreamDir: DirectoryProperty
@get:Input
abstract val upstreamBranch: Property<String>
@get:InputFile
abstract val sourceMcDevJar: RegularFileProperty
@get:InputDirectory
abstract val mcLibrariesDir: DirectoryProperty
@get:InputDirectory
abstract val spigotLibrariesDir: DirectoryProperty
@get:Optional
@get:InputFile
abstract val devImports: RegularFileProperty
@get:Optional
@get:Input
abstract val unneededFiles: ListProperty<String>
@get:Input
abstract val ignoreGitIgnore: Property<Boolean>
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
@get:Inject
abstract val providers: ProviderFactory
@get:OutputDirectory
abstract val mcDevSources: DirectoryProperty
@get:Input
abstract val verbose: Property<Boolean>
override fun init() {
upstreamBranch.convention("master")
ignoreGitIgnore.convention(Git.ignoreProperty(providers)).finalizeValueOnRead()
verbose.convention(providers.verboseApplyPatches())
}
@TaskAction
fun runLocking() {
val lockFile = layout.cache.resolve(applyPatchesLock(outputDir.path))
acquireProcessLockWaiting(lockFile)
try {
run()
} finally {
lockFile.deleteForcefully()
}
}
private fun run() {
Git.checkForGit()
val outputFile = outputDir.path
recreateCloneDirectory(outputFile)
val target = outputFile.name
if (printOutput.get()) {
logger.lifecycle("Creating $target from remapped source...")
}
Git(outputFile).let { git ->
checkoutRepoFromUpstream(git, upstreamDir.path, upstreamBranch.get())
val sourceDir = createDir(outputDir.path.resolve("src/main/java"))
val mcDataDir = outputDir.path.resolve("src/main/resources")
val testDir = createDir(outputDir.path.resolve("src/test/java"))
fs.copy {
from(archives.zipTree(remappedSource.path))
into(sourceDir)
}
fs.copy {
from(archives.zipTree(remappedTests.path))
into(testDir)
}
val patches = patchDir.path.listDirectoryEntries("*.patch")
McDev.importMcDev(
patches = patches,
decompJar = sourceMcDevJar.path,
importsFile = devImports.pathOrNull,
targetDir = sourceDir,
dataTargetDir = mcDataDir,
librariesDirs = listOf(spigotLibrariesDir.path, mcLibrariesDir.path),
printOutput = printOutput.get()
)
val caseOnlyChanges = caseOnlyClassNameChanges.path.bufferedReader(Charsets.UTF_8).use { reader ->
gson.fromJson<List<ClassNameChange>>(reader)
}
for (caseOnlyChange in caseOnlyChanges) {
val obfFile = sourceDir.resolve(caseOnlyChange.obfName + ".java").relativeTo(outputFile)
val deobfFile = sourceDir.resolve(caseOnlyChange.deobfName + ".java").relativeTo(outputFile)
git("mv", "-f", obfFile.toString(), deobfFile.toString()).runSilently(silenceErr = true)
}
unneededFiles.orNull?.forEach { path -> outputFile.resolve(path).deleteRecursive() }
git(*Git.add(ignoreGitIgnore, ".")).executeSilently()
git("commit", "-m", "Initial", "--author=Initial Source <auto@mated.null>").executeSilently()
git("tag", "-d", "base").runSilently(silenceErr = true)
git("tag", "base").executeSilently()
applyGitPatches(git, target, outputFile, patchDir.path, printOutput.get(), verbose.get())
makeMcDevSrc(layout.cache, sourceMcDevJar.path, mcDevSources.path, outputDir.path, sourceDir, mcDataDir)
}
}
private fun createDir(dir: Path): Path {
dir.deleteRecursive()
dir.createDirectories()
return dir
}
}

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -33,10 +33,13 @@ abstract class BaseTask : DefaultTask() {
@get:Inject
abstract val objects: ObjectFactory
@get:Inject
abstract val layout: ProjectLayout
@get:Inject
abstract val fs: FileSystemOperations
@get:Inject
abstract val archives: ArchiveOperations

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
import dev.denwav.hypo.asm.AsmClassDataProvider
import dev.denwav.hypo.asm.hydrate.BridgeMethodHydrator
import dev.denwav.hypo.asm.hydrate.SuperConstructorHydrator
import dev.denwav.hypo.core.HypoContext
import dev.denwav.hypo.hydrate.HydrationManager
import dev.denwav.hypo.mappings.ChangeChain
import dev.denwav.hypo.mappings.MappingsCompletionManager
import dev.denwav.hypo.mappings.contributors.CopyMappingsDown
import dev.denwav.hypo.mappings.contributors.PropagateMappingsUp
import dev.denwav.hypo.mappings.contributors.RemoveUnusedMappings
import dev.denwav.hypo.model.ClassProviderRoot
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
@CacheableTask
abstract class CleanupMappings : JavaLauncherTask() {
@get:Classpath
abstract val sourceJar: RegularFileProperty
@get:CompileClasspath
abstract val libraries: ConfigurableFileCollection
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val inputMappings: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmargs.convention(listOf("-Xmx1G"))
}
@TaskAction
fun run() {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmargs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
queue.submit(CleanupMappingsAction::class) {
inputMappings.set(this@CleanupMappings.inputMappings.path)
libraries.from(this@CleanupMappings.libraries.files)
sourceJar.set(this@CleanupMappings.sourceJar.path)
outputMappings.set(this@CleanupMappings.outputMappings.path)
}
}
abstract class CleanupMappingsAction : WorkAction<CleanupMappingsAction.Parameters> {
interface Parameters : WorkParameters {
val inputMappings: RegularFileProperty
val libraries: ConfigurableFileCollection
val sourceJar: RegularFileProperty
val outputMappings: RegularFileProperty
}
override fun execute() {
val mappings = MappingFormats.TINY.read(
parameters.inputMappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
val cleanedMappings = HypoContext.builder()
.withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(parameters.sourceJar.path)))
.withContextProvider(AsmClassDataProvider.of(parameters.libraries.toJarClassProviderRoots()))
.withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
.build().use { hypoContext ->
HydrationManager.createDefault()
.register(BridgeMethodHydrator.create())
.register(SuperConstructorHydrator.create())
.hydrate(hypoContext)
ChangeChain.create()
.addLink(RemoveUnusedMappings.create())
.addLink(PropagateMappingsUp.create())
.addLink(CopyMappingsDown.create())
.applyChain(mappings, MappingsCompletionManager.create(hypoContext))
}
MappingFormats.TINY.write(
cleanedMappings,
parameters.outputMappings.path,
SPIGOT_NAMESPACE,
DEOBF_NAMESPACE
)
}
}
}

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,8 +22,7 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.Command
import io.papermc.paperweight.util.UselessOutputStream
import io.papermc.paperweight.util.*
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Console

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,31 +22,50 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.zip
import io.papermc.paperweight.util.*
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class CopyResources : BaseTask() {
@get:InputFile
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:InputFile
@get:Classpath
abstract val vanillaJar: RegularFileProperty
@get:Input
abstract val includes: ListProperty<String>
@get:OutputFile
abstract val outputJar: RegularFileProperty
override fun init() {
outputJar.convention(defaultOutput())
includes.convention(
listOf(
"/data/**",
"/assets/**",
"version.json",
"yggdrasil_session_pubkey.der",
"pack.mcmeta",
"flightrecorder-config.jfc",
)
)
}
@TaskAction
fun run() {
val out = outputJar.file
val out = outputJar.path
val target = out.resolveSibling("${out.name}.dir")
target.mkdirs()
target.createDirectories()
fs.copy {
from(archives.zipTree(vanillaJar)) {
@ -54,12 +73,11 @@ abstract class CopyResources : BaseTask() {
include(inc)
}
}
into(target)
from(archives.zipTree(inputJar))
into(target)
}
zip(target, outputJar)
target.deleteRecursively()
target.deleteRecursive()
}
}

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
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.data.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.NamedDomainObjectContainer
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.*
@CacheableTask
abstract class CreateBundlerJar : ZippedTask() {
interface VersionArtifact {
@get:Input
val name: String
@get:Input
val id: Property<String>
@get:Classpath
val file: RegularFileProperty
}
@get:Classpath
abstract val paperclip: ConfigurableFileCollection
@get:Input
abstract val mainClass: Property<String>
@get:Nested
val versionArtifacts: NamedDomainObjectContainer<VersionArtifact> = createVersionArtifactContainer()
@get:Classpath
@get:Optional
abstract val libraryArtifacts: Property<Configuration>
@get:PathSensitive(PathSensitivity.NONE)
@get:InputFile
abstract val serverLibrariesList: RegularFileProperty
@get:Classpath
abstract val vanillaBundlerJar: RegularFileProperty
@get:OutputFile
abstract val libraryChangesJson: RegularFileProperty
private fun createVersionArtifactContainer(): NamedDomainObjectContainer<VersionArtifact> =
objects.domainObjectContainer(VersionArtifact::class) { objects.newInstance(it) }
override fun init() {
super.init()
libraryChangesJson.convention(defaultOutput("$name-library-changes", "json"))
}
override fun run(rootDir: Path) {
paperclip.singleFile.toPath().openZip().use { zip ->
zip.getPath("/").copyRecursivelyTo(rootDir)
}
val versions = handleVersions(rootDir)
val libraries = handleServerDependencies(rootDir)
val versionsFile = rootDir.resolve(FileEntry.VERSIONS_LIST).also { it.parent.createDirectories() }
val librariesFile = rootDir.resolve(FileEntry.LIBRARIES_LIST).also { it.parent.createDirectories() }
versionsFile.bufferedWriter().use { writer ->
for (v in versions.sortedBy { it.id }) {
writer.append(v.toString()).append('\n')
}
}
librariesFile.bufferedWriter().use { writer ->
for (l in libraries.sortedBy { it.id }) {
writer.append(l.toString()).append('\n')
}
}
rootDir.resolve("META-INF/main-class").writeText(mainClass.get())
// copy version.json file
vanillaBundlerJar.path.openZip().use { fs ->
fs.getPath("/").resolve(FileEntry.VERSION_JSON).copyTo(rootDir.resolve("version.json"))
}
}
private fun handleServerDependencies(rootDir: Path): List<FileEntry<ModuleId>> {
val libraries = mutableListOf<FileEntry<ModuleId>>()
val changedLibraries = mutableListOf<LibraryChange>()
val serverLibraryEntries = FileEntry.parse(serverLibrariesList.path, ModuleId::parse)
val outputDir = rootDir.resolve("META-INF/libraries")
val dependencies = collectDependencies()
for (dep in dependencies) {
val serverLibrary = serverLibraryEntries.firstOrNull {
it.id.group == dep.module.group &&
it.id.name == dep.module.name &&
it.id.classifier == dep.module.classifier
}
if (serverLibrary != null) {
if (serverLibrary.id.version == dep.module.version) {
// nothing to do
libraries += serverLibrary
dep.copyTo(outputDir.resolve(dep.module.toPath()))
} else {
// we have a different version of this library
val newId = dep.module
val newPath = newId.toPath()
changedLibraries += LibraryChange(serverLibrary.id, serverLibrary.path, newId, newPath)
val jarFile = dep.copyTo(outputDir.resolve(newPath))
libraries += FileEntry(jarFile.sha256asHex(), newId, newPath)
}
} else {
// New dependency
val id = dep.module
val path = id.toPath()
val jarFile = dep.copyTo(outputDir.resolve(path))
libraries += FileEntry(jarFile.sha256asHex(), id, path)
}
}
// This file will be used to check library changes in the generatePaperclipPatches step
ensureParentExists(libraryChangesJson)
libraryChangesJson.path.bufferedWriter().use { writer ->
gson.toJson(changedLibraries, writer)
}
return libraries
}
private fun handleVersions(rootDir: Path): List<FileEntry<String>> {
val outputDir = rootDir.resolve("META-INF/versions")
return versionArtifacts.map { versionArtifact ->
val id = versionArtifact.id.get()
val versionPath = "$id/${versionArtifact.name}-$id.jar"
val inputFile = versionArtifact.file.path
val outputFile = outputDir.resolve(versionPath)
ensureParentExists(outputFile)
inputFile.copyTo(outputFile)
FileEntry(inputFile.sha256asHex(), id, versionPath)
}
}
private fun collectDependencies(): Set<ResolvedArtifactResult> {
return libraryArtifacts.map { config ->
config.incoming.artifacts.artifacts.filterTo(HashSet()) {
val id = it.id.componentIdentifier
id is ModuleComponentIdentifier || id is ProjectComponentIdentifier
}
}.getOrElse(hashSetOf<ResolvedArtifactResult>())
}
private fun ResolvedArtifactResult.copyTo(path: Path): Path {
ensureParentExists(path)
return file.toPath().copyTo(path, overwrite = true)
}
private val ResolvedArtifactResult.module: ModuleId
get() {
return when (val ident = id.componentIdentifier) {
is ModuleComponentIdentifier -> ModuleId.fromIdentifier(id)
is ProjectComponentIdentifier -> {
val capability = variant.capabilities.first()
val version = capability.version ?: throw PaperweightException("Unknown version for ${capability.group}:${capability.name}")
ModuleId(capability.group, capability.name, version)
}
else -> throw PaperweightException("Unknown artifact result type: ${ident::class.java.name}")
}
}
}

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -23,24 +23,16 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.DownloadService
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file
import java.math.BigInteger
import java.security.MessageDigest
import io.papermc.paperweight.util.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.*
// Not cached since this is Mojang's server jar
abstract class DownloadServerJar : BaseTask() {
@get:Input
abstract val downloadUrl: Property<String>
@get:Input
abstract val hash: Property<String>
@get:OutputFile
abstract val outputJar: RegularFileProperty
@ -48,25 +40,14 @@ abstract class DownloadServerJar : BaseTask() {
@get:Internal
abstract val downloader: Property<DownloadService>
@get:Nested
@get:Optional
abstract val expectedHash: Property<Hash>
override fun init() {
outputJar.convention(defaultOutput())
}
@TaskAction
fun run() {
val file = outputJar.asFile.get()
outputJar.file.parentFile.mkdirs()
downloader.get().download(downloadUrl, outputJar)
val digest = MessageDigest.getInstance("MD5")
val data = file.readBytes()
val hashResult = digest.digest(data)
val hashText = String.format("%032x", BigInteger(1, hashResult))
if (hash.get() != hashText) {
throw PaperweightException("Checksum failed, expected ${hash.get()}, actually got $hashText")
}
}
fun run() = downloader.get().download(downloadUrl, outputJar, expectedHash.orNull)
}

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,20 +22,21 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.zip
import io.papermc.paperweight.util.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class FilterJar : BaseTask() {
@get:InputFile
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:Input
abstract val includes: ListProperty<String>
@ -47,21 +48,11 @@ abstract class FilterJar : BaseTask() {
}
@TaskAction
fun run() {
val out = outputJar.file
val target = out.resolveSibling("${out.name}.dir")
target.mkdirs()
fs.copy {
from(archives.zipTree(inputJar)) {
for (inc in this@FilterJar.includes.get()) {
include(inc)
}
}
into(target)
}
zip(target, outputJar)
target.deleteRecursively()
open fun run() {
filterJar(
inputJar.path,
outputJar.path,
includes.get()
)
}
}

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,32 +22,33 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.file
import java.io.File
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
/**
* Because Spigot doesn't remap all classes, there are class and package name clashes if we don't do this in the source
* remap step. Other than that, we don't need this jar
*/
@CacheableTask
abstract class FilterSpigotExcludes : ZippedTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val excludesFile: RegularFileProperty
override fun run(rootDir: File) {
excludesFile.file.useLines { lines ->
override fun run(rootDir: Path) {
excludesFile.path.useLines { lines ->
for (line in lines) {
if (line.startsWith('#') || line.isBlank()) {
continue
}
val file = if (line.contains('/')) {
rootDir.resolve("$line.class")
} else {
rootDir.resolve("net/minecraft/server/$line.class")
}
file.delete()
rootDir.resolve("$line.class").deleteForcefully()
}
}
}

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,19 +22,23 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.Constants
import io.papermc.paperweight.util.MappingFormats
import io.papermc.paperweight.util.emptyMergeResult
import io.papermc.paperweight.util.ensureParentExists
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.path
import java.nio.file.FileSystems
import java.nio.file.Files
import dev.denwav.hypo.asm.AsmClassDataProvider
import dev.denwav.hypo.asm.hydrate.BridgeMethodHydrator
import dev.denwav.hypo.asm.hydrate.SuperConstructorHydrator
import dev.denwav.hypo.core.HypoContext
import dev.denwav.hypo.hydrate.HydrationManager
import dev.denwav.hypo.mappings.ChangeChain
import dev.denwav.hypo.mappings.MappingsCompletionManager
import dev.denwav.hypo.mappings.contributors.CopyMappingsDown
import dev.denwav.hypo.mappings.contributors.PropagateMappingsUp
import dev.denwav.hypo.mappings.contributors.RemoveUnusedMappings
import dev.denwav.hypo.model.ClassProviderRoot
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import javax.inject.Inject
import org.cadixdev.atlas.Atlas
import org.cadixdev.bombe.asm.jar.JarEntryRemappingTransformer
import kotlin.io.path.*
import org.cadixdev.lorenz.MappingSet
import org.cadixdev.lorenz.asm.LorenzRemapper
import org.cadixdev.lorenz.merge.FieldMergeStrategy
import org.cadixdev.lorenz.merge.MappingSetMerger
import org.cadixdev.lorenz.merge.MappingSetMergerHandler
@ -47,102 +51,137 @@ import org.cadixdev.lorenz.model.InnerClassMapping
import org.cadixdev.lorenz.model.MethodMapping
import org.cadixdev.lorenz.model.MethodParameterMapping
import org.cadixdev.lorenz.model.TopLevelClassMapping
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.submit
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.*
import org.gradle.jvm.toolchain.JavaLauncher
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkQueue
import org.gradle.workers.WorkerExecutor
abstract class GenerateMappings : DefaultTask() {
fun generateMappings(
vanillaJarPath: Path,
libraryPaths: List<Path>,
vanillaMappingsPath: Path,
paramMappingsPath: Path,
outputMappingsPath: Path,
workerExecutor: WorkerExecutor,
launcher: JavaLauncher,
jvmArgs: List<String> = listOf("-Xmx1G")
): WorkQueue {
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs(jvmArgs)
forkOptions.executable(launcher.executablePath.path.absolutePathString())
}
@get:InputFile
queue.submit(GenerateMappings.GenerateMappingsAction::class) {
vanillaJar.set(vanillaJarPath)
libraries.from(libraryPaths)
vanillaMappings.set(vanillaMappingsPath)
paramMappings.set(paramMappingsPath)
outputMappings.set(outputMappingsPath)
}
return queue
}
@CacheableTask
abstract class GenerateMappings : JavaLauncherTask() {
@get:Classpath
abstract val vanillaJar: RegularFileProperty
@get:Classpath
abstract val libraries: ConfigurableFileCollection
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val vanillaMappings: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val paramMappings: RegularFileProperty
@get:OutputFile
abstract val outputMappings: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
super.init()
jvmargs.convention(listOf("-Xmx1G"))
}
@TaskAction
fun run() {
val vanillaMappings = MappingFormats.PROGUARD.createReader(vanillaMappings.path).use { it.read() }.reverse()
val paramMappings = FileSystems.newFileSystem(paramMappings.path, null).use { fs ->
val path = fs.getPath("mappings", "mappings.tiny")
MappingFormats.TINY.read(path, "official", "named")
}
val merged = MappingSetMerger.create(
vanillaMappings,
paramMappings,
MergeConfig.builder()
.withFieldMergeStrategy(FieldMergeStrategy.STRICT)
.withMergeHandler(ParamsMergeHandler())
.build()
).merge()
// Fill out any missing inheritance info in the mappings
val tempMappingsFile = Files.createTempFile("mappings", "tiny")
val tempMappingsOutputFile = Files.createTempFile("mappings-out", "tiny")
val filledMerged = try {
MappingFormats.TINY.write(merged, tempMappingsFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs("-Xmx1G")
}
queue.submit(AtlasAction::class) {
inputJar.set(vanillaJar.file)
mappingsFile.set(tempMappingsFile.toFile())
outputMappingsFile.set(tempMappingsOutputFile.toFile())
}
queue.await()
MappingFormats.TINY.read(tempMappingsOutputFile, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
} finally {
Files.deleteIfExists(tempMappingsFile)
Files.deleteIfExists(tempMappingsOutputFile)
}
ensureParentExists(outputMappings)
MappingFormats.TINY.write(filledMerged, outputMappings.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
generateMappings(
vanillaJar.path,
libraries.files.map { it.toPath() },
vanillaMappings.path,
paramMappings.path,
outputMappings.path,
workerExecutor,
launcher.get(),
jvmargs.get()
)
}
abstract class AtlasAction : WorkAction<AtlasParameters> {
override fun execute() {
val mappings = MappingFormats.TINY.read(parameters.mappingsFile.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
interface GenerateMappingsParams : WorkParameters {
val vanillaJar: RegularFileProperty
val libraries: ConfigurableFileCollection
val vanillaMappings: RegularFileProperty
val paramMappings: RegularFileProperty
val outputMappings: RegularFileProperty
}
val tempOut = Files.createTempFile("remapped", "jar")
try {
Atlas().let { atlas ->
atlas.install { ctx -> JarEntryRemappingTransformer(LorenzRemapper(mappings, ctx.inheritanceProvider())) }
atlas.run(parameters.inputJar.path, tempOut)
abstract class GenerateMappingsAction : WorkAction<GenerateMappingsParams> {
override fun execute() {
val vanillaMappings = MappingFormats.PROGUARD.createReader(parameters.vanillaMappings.path).use { it.read() }.reverse()
val paramMappings = parameters.paramMappings.path.openZip().use { fs ->
val path = fs.getPath("mappings", "mappings.tiny")
MappingFormats.TINY.read(path, "official", "named")
}
val merged = MappingSetMerger.create(
vanillaMappings,
paramMappings,
MergeConfig.builder()
.withFieldMergeStrategy(FieldMergeStrategy.STRICT)
.withMergeHandler(ParamsMergeHandler())
.build()
).merge()
val filledMerged = HypoContext.builder()
.withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(parameters.vanillaJar.path)))
.withContextProvider(AsmClassDataProvider.of(parameters.libraries.toJarClassProviderRoots()))
.withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
.build().use { hypoContext ->
HydrationManager.createDefault()
.register(BridgeMethodHydrator.create())
.register(SuperConstructorHydrator.create())
.hydrate(hypoContext)
ChangeChain.create()
.addLink(RemoveUnusedMappings.create())
.addLink(PropagateMappingsUp.create())
.addLink(CopyMappingsDown.create())
.applyChain(merged, MappingsCompletionManager.create(hypoContext))
}
MappingFormats.TINY.write(mappings, parameters.outputMappingsFile.path, Constants.OBF_NAMESPACE, Constants.DEOBF_NAMESPACE)
} finally {
Files.deleteIfExists(tempOut)
}
ensureParentExists(parameters.outputMappings)
MappingFormats.TINY.write(filledMerged, parameters.outputMappings.path, OBF_NAMESPACE, DEOBF_NAMESPACE)
}
}
interface AtlasParameters : WorkParameters {
val inputJar: RegularFileProperty
val mappingsFile: RegularFileProperty
val outputMappingsFile: RegularFileProperty
}
}
class ParamsMergeHandler : MappingSetMergerHandler {
@ -155,6 +194,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): MergeResult<TopLevelClassMapping?> {
throw IllegalStateException("Unexpectedly merged class: ${left.fullObfuscatedName}")
}
override fun mergeDuplicateTopLevelClassMappings(
left: TopLevelClassMapping,
right: TopLevelClassMapping,
@ -176,6 +216,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): MergeResult<InnerClassMapping?> {
throw IllegalStateException("Unexpectedly merged class: ${left.fullObfuscatedName}")
}
override fun mergeDuplicateInnerClassMappings(
left: InnerClassMapping,
right: InnerClassMapping,
@ -198,6 +239,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): FieldMapping? {
throw IllegalStateException("Unexpectedly merged field: ${left.fullObfuscatedName}")
}
override fun mergeDuplicateFieldMappings(
left: FieldMapping,
strictRightDuplicate: FieldMapping?,
@ -209,6 +251,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): FieldMapping? {
return target.createFieldMapping(left.signature, left.deobfuscatedName)
}
override fun addLeftFieldMapping(
left: FieldMapping,
target: ClassMapping<*, *>,
@ -226,6 +269,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): MergeResult<MethodMapping?> {
throw IllegalStateException("Unexpectedly merged method: ${left.fullObfuscatedName}")
}
override fun mergeDuplicateMethodMappings(
left: MethodMapping,
standardRightDuplicate: MethodMapping?,
@ -258,6 +302,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): MergeResult<TopLevelClassMapping?> {
return emptyMergeResult()
}
override fun addRightInnerClassMapping(
right: InnerClassMapping?,
target: ClassMapping<*, *>?,
@ -265,6 +310,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): MergeResult<InnerClassMapping?> {
return emptyMergeResult()
}
override fun addRightFieldMapping(
right: FieldMapping?,
target: ClassMapping<*, *>?,
@ -272,6 +318,7 @@ class ParamsMergeHandler : MappingSetMergerHandler {
): FieldMapping? {
return null
}
override fun addRightMethodMapping(
right: MethodMapping?,
target: ClassMapping<*, *>?,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,21 +22,31 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.path
import io.papermc.paperweight.util.*
import kotlin.io.path.*
import org.cadixdev.at.AccessTransformSet
import org.cadixdev.at.io.AccessTransformFormats
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class MergeAccessTransforms : BaseTask() {
@get:InputFiles
abstract val inputFiles: ListProperty<RegularFile>
@get:Optional
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val firstFile: RegularFileProperty
@get:Optional
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val secondFile: RegularFileProperty
@get:OutputFile
abstract val outputFile: RegularFileProperty
@ -47,8 +57,10 @@ abstract class MergeAccessTransforms : BaseTask() {
@TaskAction
fun run() {
val ats = inputFiles.get()
.map { AccessTransformFormats.FML.read(it.asFile.toPath()) }
val ats = sequenceOf(firstFile.pathOrNull, secondFile.pathOrNull)
.filterNotNull()
.filter { it.exists() }
.map { AccessTransformFormats.FML.read(it) }
val outputAt = AccessTransformSet.create()
for (at in ats) {

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,99 +22,107 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.Git
import io.papermc.paperweight.util.file
import java.io.File
import io.papermc.paperweight.util.*
import java.nio.file.Path
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Executors
import java.util.concurrent.Future
import javax.inject.Inject
import kotlin.io.path.*
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Console
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.options.Option
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
abstract class RebuildPaperPatches : ControllableOutputTask() {
@UntrackedTask(because = "RebuildGitPatches should always run when requested")
abstract class RebuildGitPatches : ControllableOutputTask() {
@get:InputDirectory
abstract val inputDir: DirectoryProperty
@get:Console
abstract val server: Property<Boolean>
@get:Input
abstract val baseRef: Property<String>
@get:OutputDirectory
abstract val patchDir: DirectoryProperty
@get:Internal
@get:Option(option = "filter-patches", description = "Controls if patches should be cleaned up, defaults to true")
@get:Input
abstract val filterPatches: Property<Boolean>
@get:Inject
abstract val providers: ProviderFactory
override fun init() {
printOutput.convention(true)
filterPatches.convention(true)
server.convention(false)
filterPatches.convention(
providers.gradleProperty("paperweight.filter-patches")
.map { it.toBoolean() }
.orElse(true)
)
}
@TaskAction
fun run() {
val what = inputDir.file.name
val patchFolder = patchDir.file
val what = inputDir.path.name
val patchFolder = patchDir.path
if (!patchFolder.exists()) {
patchFolder.mkdirs()
patchFolder.createDirectories()
}
if (printOutput.get()) {
println("Formatting patches for $what...")
logger.lifecycle("Formatting patches for $what...")
}
if (inputDir.file.resolve(".git/rebase-apply").exists()) {
if (inputDir.path.resolve(".git/rebase-apply").exists()) {
// in middle of a rebase, be smarter
if (printOutput.get()) {
println("REBASE DETECTED - PARTIAL SAVE")
val last = inputDir.file.resolve(".git/rebase-apply/last").readText().trim().toInt()
val next = inputDir.file.resolve(".git/rebase-apply/next").readText().trim().toInt()
val orderedFiles = patchFolder.listFiles { f -> f.name.endsWith(".patch") }!!
logger.lifecycle("REBASE DETECTED - PARTIAL SAVE")
val last = inputDir.path.resolve(".git/rebase-apply/last").readText().trim().toInt()
val next = inputDir.path.resolve(".git/rebase-apply/next").readText().trim().toInt()
val orderedFiles = patchFolder.useDirectoryEntries("*.patch") { it.toMutableList() }
orderedFiles.sort()
for (i in 1..last) {
if (i < next) {
orderedFiles[i].delete()
orderedFiles[i].deleteForcefully()
}
}
}
} else {
patchFolder.deleteRecursively()
patchFolder.mkdirs()
patchFolder.deleteRecursive()
patchFolder.createDirectories()
}
Git(inputDir.file)(
val git = Git(inputDir.path)
git("fetch", "--all", "--prune").runSilently(silenceErr = true)
git(
"format-patch",
"--zero-commit", "--full-index", "--no-signature", "--no-stat", "-N",
"-o", patchFolder.absolutePath,
if (server.get()) "base" else "upstream/upstream"
"--diff-algorithm=myers", "--zero-commit", "--full-index", "--no-signature", "--no-stat", "-N",
"-o", patchFolder.absolutePathString(),
baseRef.get()
).executeSilently()
val patchDirGit = Git(patchFolder)
patchDirGit("add", "-A", ".").executeSilently()
if (filterPatches.get()) {
cleanupPatches(patchDirGit)
}
} else {
if (printOutput.get()) {
val saved = patchFolder.listDirectoryEntries("*.patch").size
if (printOutput.get()) {
println(" Patches saved for $what to ${patchFolder.name}/")
logger.lifecycle("Saved $saved patches for $what to ${layout.projectDirectory.path.relativize(patchFolder)}/")
}
}
}
private fun cleanupPatches(git: Git) {
val patchFiles = patchDir.file.listFiles { f -> f.name.endsWith(".patch") } ?: emptyArray()
val patchFiles = patchDir.path.useDirectoryEntries("*.patch") { it.toMutableList() }
if (patchFiles.isEmpty()) {
return
}
patchFiles.sortBy { it.name }
patchFiles.sort()
val noChangesPatches = ConcurrentLinkedQueue<File>()
val noChangesPatches = ConcurrentLinkedQueue<Path>()
val futures = mutableListOf<Future<*>>()
// Calling out to git over and over again for each `git diff --staged` command is really slow from the JVM
@ -123,13 +131,13 @@ abstract class RebuildPaperPatches : ControllableOutputTask() {
try {
for (patch in patchFiles) {
futures += executor.submit {
val hasNoChanges = git("diff", "--staged", patch.name).getText().lineSequence()
val hasNoChanges = git("diff", "--diff-algorithm=myers", "--staged", patch.name).getText().lineSequence()
.filter { it.startsWith('+') || it.startsWith('-') }
.filterNot { it.startsWith("+++") || it.startsWith("---") }
.all { it.startsWith("+index") || it.startsWith("-index") }
if (hasNoChanges) {
noChangesPatches += patch
noChangesPatches.add(patch)
}
}
}
@ -140,14 +148,16 @@ abstract class RebuildPaperPatches : ControllableOutputTask() {
}
if (noChangesPatches.isNotEmpty()) {
git("reset", "HEAD", *noChangesPatches.map { it.name }.toTypedArray()).executeSilently()
git("checkout", "--", *noChangesPatches.map { it.name }.toTypedArray()).executeSilently()
for (chunk in noChangesPatches.chunked(50)) {
git("reset", "HEAD", *chunk.map { it.name }.toTypedArray()).executeSilently()
git("checkout", "--", *chunk.map { it.name }.toTypedArray()).executeSilently()
}
}
if (printOutput.get()) {
for (patch in patchFiles) {
println(patch.name)
}
val saved = patchFiles.size - noChangesPatches.size
val relDir = layout.projectDirectory.path.relativize(patchDir.path)
logger.lifecycle("Saved modified patches ($saved/${patchFiles.size}) for ${inputDir.path.name} to $relDir/")
}
}
}

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,21 +22,26 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.Constants
import io.papermc.paperweight.util.MappingFormats
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.path
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import org.cadixdev.at.io.AccessTransformFormats
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class RemapAccessTransform : BaseTask() {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val inputFile: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val mappings: RegularFileProperty
@get:OutputFile
@ -49,7 +54,7 @@ abstract class RemapAccessTransform : BaseTask() {
@TaskAction
fun run() {
val at = AccessTransformFormats.FML.read(inputFile.path)
val mappingSet = MappingFormats.TINY.read(mappings.path, Constants.SPIGOT_NAMESPACE, Constants.DEOBF_NAMESPACE)
val mappingSet = MappingFormats.TINY.read(mappings.path, SPIGOT_NAMESPACE, DEOBF_NAMESPACE)
val resultAt = at.remap(mappingSet)
AccessTransformFormats.FML.write(outputFile.path, resultAt)

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,51 +22,48 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.MappingFormats
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.ensureDeleted
import io.papermc.paperweight.util.ensureParentExists
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.path
import io.papermc.paperweight.util.*
import javax.inject.Inject
import kotlin.io.path.*
import org.cadixdev.atlas.Atlas
import org.cadixdev.bombe.asm.jar.JarEntryRemappingTransformer
import org.cadixdev.lorenz.asm.LorenzRemapper
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.submit
import org.gradle.kotlin.dsl.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import org.objectweb.asm.commons.Remapper
abstract class RemapJarAtlas : BaseTask() {
@CacheableTask
abstract class RemapJarAtlas : JavaLauncherTask() {
@get:InputFile
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:InputFile
abstract val mappingsFile: RegularFileProperty
@get:Input
abstract val packageVersion: Property<String>
@get:Input
abstract val fromNamespace: Property<String>
@get:Input
abstract val toNamespace: Property<String>
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Internal
abstract val jvmargs: ListProperty<String>
@get:Inject
abstract val workerExecutor: WorkerExecutor
override fun init() {
outputJar.convention(defaultOutput())
super.init()
jvmargs.convention(listOf("-Xmx1G"))
}
@TaskAction
@ -75,27 +72,22 @@ abstract class RemapJarAtlas : BaseTask() {
ensureDeleted(outputJar)
val queue = workerExecutor.processIsolation {
forkOptions.jvmArgs("-Xmx1G")
forkOptions.jvmArgs(jvmargs.get())
forkOptions.executable(launcher.get().executablePath.path.absolutePathString())
}
queue.submit(AtlasAction::class) {
inputJar.set(this@RemapJarAtlas.inputJar.file)
outputJar.set(this@RemapJarAtlas.outputJar.file)
mappingsFile.set(this@RemapJarAtlas.mappingsFile.file)
inputJar.set(this@RemapJarAtlas.inputJar.get())
outputJar.set(this@RemapJarAtlas.outputJar.get())
packageVersion.set(this@RemapJarAtlas.packageVersion.get())
toNamespace.set(this@RemapJarAtlas.toNamespace.get())
fromNamespace.set(this@RemapJarAtlas.fromNamespace.get())
}
}
abstract class AtlasAction : WorkAction<AtlasParameters> {
override fun execute() {
val mappings = MappingFormats.TINY.read(parameters.mappingsFile.path, parameters.fromNamespace.get(), parameters.toNamespace.get())
val oldPack = "net/minecraft/server"
val newPack = "$oldPack/v${parameters.packageVersion.get()}"
val oldPack = "net/minecraft"
val newPack = "$oldPack/server/v${parameters.packageVersion.get()}"
Atlas().let { atlas ->
atlas.install { ctx -> JarEntryRemappingTransformer(LorenzRemapper(mappings, ctx.inheritanceProvider())) }
atlas.install { JarEntryRemappingTransformer(PackageRemapper(oldPack, newPack)) }
atlas.run(parameters.inputJar.path, parameters.outputJar.path)
}
@ -105,9 +97,6 @@ abstract class RemapJarAtlas : BaseTask() {
interface AtlasParameters : WorkParameters {
val inputJar: RegularFileProperty
val outputJar: RegularFileProperty
val mappingsFile: RegularFileProperty
val fromNamespace: Property<String>
val toNamespace: Property<String>
val packageVersion: Property<String>
}
}
@ -116,7 +105,7 @@ class PackageRemapper(private val oldPackage: String, private val newPackage: St
override fun map(internalName: String): String {
return if (internalName.startsWith(oldPackage)) {
internalName.replaceFirst(oldPackage, newPackage)
internalName.replaceBeforeLast('/', newPackage)
} else {
internalName
}

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,12 +22,9 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.Constants
import io.papermc.paperweight.util.MappingFormats
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.path
import java.util.jar.JarFile
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import kotlin.io.path.*
import org.cadixdev.at.AccessChange
import org.cadixdev.at.AccessTransform
import org.cadixdev.at.AccessTransformSet
@ -36,17 +33,26 @@ import org.cadixdev.at.io.AccessTransformFormats
import org.cadixdev.bombe.type.MethodDescriptor
import org.cadixdev.bombe.type.signature.MethodSignature
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class RemapSpigotAt : BaseTask() {
@get:InputFile
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val spigotAt: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val mapping: RegularFileProperty
@get:OutputFile
@ -60,8 +66,8 @@ abstract class RemapSpigotAt : BaseTask() {
fun run() {
val outputAt = AccessTransformSet.create()
spigotAt.file.useLines { lines ->
JarFile(inputJar.file).use { jarFile ->
spigotAt.path.useLines { lines ->
inputJar.path.openZip().use { jarFile ->
for (line in lines) {
if (line.isBlank() || line.startsWith('#')) {
continue
@ -84,7 +90,7 @@ abstract class RemapSpigotAt : BaseTask() {
)
} else {
// either field or class
if (jarFile.getJarEntry("$desc.class") == null) {
if (jarFile.getPath("$desc.class").notExists()) {
// field
val index = desc.lastIndexOf('/')
val className = desc.substring(0, index)
@ -99,7 +105,7 @@ abstract class RemapSpigotAt : BaseTask() {
}
}
val mappings = MappingFormats.TINY.read(mapping.path, Constants.SPIGOT_NAMESPACE, Constants.DEOBF_NAMESPACE)
val mappings = MappingFormats.TINY.read(mapping.path, SPIGOT_NAMESPACE, DEOBF_NAMESPACE)
val remappedAt = outputAt.remap(mappings)
AccessTransformFormats.FML.write(outputFile.path, remappedAt)

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,14 +22,18 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
@CacheableTask
abstract class SetupMcLibraries : DefaultTask() {
@get:Input
@ -40,12 +44,16 @@ abstract class SetupMcLibraries : DefaultTask() {
@TaskAction
fun run() {
val list = dependencies.get().sorted()
setupMinecraftLibraries(dependencies.get(), outputFile.path)
}
}
outputFile.file.bufferedWriter().use { writer ->
for (line in list) {
writer.appendln(line)
}
fun setupMinecraftLibraries(dependencies: List<String>, outputFile: Path) {
val list = dependencies.sorted()
outputFile.bufferedWriter().use { writer ->
for (line in list) {
writer.appendLine(line)
}
}
}

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -23,61 +23,77 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.Constants.paperTaskOutput
import io.papermc.paperweight.util.cache
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.ensureDeleted
import io.papermc.paperweight.util.runJar
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.util.concurrent.ThreadLocalRandom
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class SpigotDecompileJar : BaseTask() {
@CacheableTask
abstract class SpigotDecompileJar : JavaLauncherTask() {
@get:InputFile
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:InputFile
@get:Classpath
abstract val fernFlowerJar: RegularFileProperty
@get:Input
abstract val decompileCommand: Property<String>
@get:OutputFile
abstract val outputJar: RegularFileProperty
@get:Input
abstract val memory: Property<String>
override fun init() {
super.init()
memory.convention("4G")
outputJar.convention(defaultOutput())
}
@TaskAction
fun run() {
val inputJarFile = inputJar.asFile.get()
val inputJarPath = inputJarFile.canonicalPath
val inputJarFile = inputJar.path
val inputJarPath = inputJarFile.absolutePathString()
val outputJarFile = outputJar.asFile.get()
val outputJarFile = outputJar.path
val decomp = outputJarFile.resolveSibling("decomp" + ThreadLocalRandom.current().nextInt())
try {
if (!decomp.exists() && !decomp.mkdirs()) {
throw PaperweightException("Failed to create output directory: $decomp")
try {
decomp.createDirectories()
} catch (e: Exception) {
throw PaperweightException("Failed to create output directory: $decomp", e)
}
val cmd = decompileCommand.get().split(" ").let { it.subList(3, it.size - 2) }.toMutableList()
cmd += inputJarPath
cmd += decomp.canonicalPath
cmd += decomp.absolutePathString()
val logFile = layout.cache.resolve(paperTaskOutput("log"))
logFile.delete()
logFile.deleteForcefully()
runJar(fernFlowerJar, workingDir = layout.cache, logFile = logFile, args = *cmd.toTypedArray())
launcher.runJar(
objects.fileCollection().from(fernFlowerJar),
workingDir = layout.cache,
logFile = logFile,
jvmArgs = listOf("-Xmx${memory.get()}"),
args = cmd.toTypedArray()
)
ensureDeleted(outputJarFile)
decomp.resolve(inputJarFile.name).renameTo(outputJarFile)
decomp.resolve(inputJarFile.name).moveTo(outputJarFile)
} finally {
decomp.deleteRecursively()
decomp.deleteRecursive()
}
}
}

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -23,39 +23,49 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.Constants.paperTaskOutput
import io.papermc.paperweight.util.cache
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.runJar
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.*
abstract class SpigotRemapJar : BaseTask() {
@CacheableTask
abstract class SpigotRemapJar : JavaLauncherTask() {
@get:InputFile
@get:Classpath
abstract val inputJar: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val classMappings: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val memberMappings: RegularFileProperty
@get:Input
abstract val mcVersion: Property<String>
@get:InputFile
abstract val packageMappings: RegularFileProperty
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val accessTransformers: RegularFileProperty
@get:Input
abstract val workDirName: Property<String>
@get:InputFile
@get:Classpath
abstract val specialSourceJar: RegularFileProperty
@get:InputFile
@get:Classpath
abstract val specialSource2Jar: RegularFileProperty
@get:Input
abstract val classMapCommand: Property<String>
@get:Input
abstract val memberMapCommand: Property<String>
@get:Input
abstract val finalMapCommand: Property<String>
@ -63,37 +73,42 @@ abstract class SpigotRemapJar : BaseTask() {
abstract val outputJar: RegularFileProperty
override fun init() {
super.init()
outputJar.convention(defaultOutput())
}
@TaskAction
fun run() {
val inputJarPath = inputJar.asFile.get().canonicalPath
val inputJarPath = inputJar.path.absolutePathString()
val outputJarFile = outputJar.asFile.get()
val outputJarPath = outputJarFile.canonicalPath
val outputJarFile = outputJar.path
val outputJarPath = outputJarFile.absolutePathString()
val classJarFile = outputJarFile.resolveSibling(outputJarFile.name + ".classes")
val membersJarFile = outputJarFile.resolveSibling(outputJarFile.name + ".members")
val classJarPath = classJarFile.canonicalPath
val membersJarPath = membersJarFile.canonicalPath
val classJarPath = classJarFile.absolutePathString()
val membersJarPath = membersJarFile.absolutePathString()
val classMappingPath = classMappings.asFile.get().canonicalPath
val memberMappingsPath = memberMappings.asFile.get().canonicalPath
val packageMappingsPath = packageMappings.asFile.get().canonicalPath
val accessTransformersPath = accessTransformers.asFile.get().canonicalPath
val classMappingPath = classMappings.path.absolutePathString()
val accessTransformersPath = accessTransformers.path.absolutePathString()
val spigotMembersPath = memberMappings.path.absolutePathString()
val work = layout.projectDirectory.file(workDirName.get())
val spigotEmptyMappings = layout.cache.resolve("spigot-empty-package-mappings.csrg")
spigotEmptyMappings.writeText("")
try {
try {
val logFile = layout.cache.resolve(paperTaskOutput("class.log"))
logFile.delete()
runJar(
specialSource2Jar,
logFile.deleteForcefully()
launcher.runJar(
objects.fileCollection().from(specialSource2Jar),
workingDir = work,
logFile = logFile,
args = *doReplacements(classMapCommand.get(), inputJarPath, classMappingPath, classJarPath) {
args = doReplacements(classMapCommand.get(), inputJarPath, classMappingPath, classJarPath) {
// ignore excludes, we actually want to map every class
it != "-e"
}
@ -104,12 +119,12 @@ abstract class SpigotRemapJar : BaseTask() {
try {
val logFile = layout.cache.resolve(paperTaskOutput("member.log"))
logFile.delete()
runJar(
specialSource2Jar,
logFile.deleteForcefully()
launcher.runJar(
objects.fileCollection().from(specialSource2Jar),
workingDir = work,
logFile = logFile,
args = *doReplacements(memberMapCommand.get(), classJarPath, memberMappingsPath, membersJarPath)
args = doReplacements(memberMapCommand.get(), classJarPath, spigotMembersPath, membersJarPath)
)
} catch (e: Exception) {
throw PaperweightException("Failed to apply member mappings", e)
@ -117,16 +132,16 @@ abstract class SpigotRemapJar : BaseTask() {
try {
val logFile = layout.cache.resolve(paperTaskOutput("final.log"))
logFile.delete()
runJar(
specialSourceJar,
logFile.deleteForcefully()
launcher.runJar(
objects.fileCollection().from(specialSourceJar),
workingDir = work,
logFile = logFile,
args = *doReplacements(
args = doReplacements(
finalMapCommand.get(),
membersJarPath,
accessTransformersPath,
packageMappingsPath,
spigotEmptyMappings.absolutePathString(),
outputJarPath
)
)
@ -134,8 +149,9 @@ abstract class SpigotRemapJar : BaseTask() {
throw PaperweightException("Failed to create remapped jar", e)
}
} finally {
classJarFile.delete()
membersJarFile.delete()
classJarFile.deleteForcefully()
membersJarFile.deleteForcefully()
spigotEmptyMappings.deleteForcefully()
}
}

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,30 +22,25 @@
package io.papermc.paperweight.tasks
import io.papermc.paperweight.util.defaultOutput
import io.papermc.paperweight.util.ensureDeleted
import io.papermc.paperweight.util.file
import io.papermc.paperweight.util.fileOrNull
import io.papermc.paperweight.util.unzip
import io.papermc.paperweight.util.zip
import java.io.File
import java.util.concurrent.ThreadLocalRandom
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
abstract class ZippedTask : BaseTask() {
@get:InputFile
@get:Optional
@get:Classpath
abstract val inputZip: RegularFileProperty
@get:OutputFile
abstract val outputZip: RegularFileProperty
abstract fun run(rootDir: File)
abstract fun run(rootDir: Path)
override fun init() {
outputZip.convention(defaultOutput("zip"))
@ -53,15 +48,15 @@ abstract class ZippedTask : BaseTask() {
@TaskAction
fun exec() {
val outputZipFile = outputZip.file
val outputZipFile = outputZip.path
val outputDir = findOutputDir(outputZipFile)
try {
val input = inputZip.fileOrNull
val input = inputZip.pathOrNull
if (input != null) {
unzip(input, outputDir)
} else {
outputDir.mkdirs()
outputDir.createDirectories()
}
run(outputDir)
@ -70,15 +65,7 @@ abstract class ZippedTask : BaseTask() {
zip(outputDir, outputZipFile)
} finally {
outputDir.deleteRecursively()
outputDir.deleteRecursive()
}
}
private fun findOutputDir(baseFile: File): File {
var dir: File
do {
dir = baseFile.resolveSibling("${baseFile.name}-" + ThreadLocalRandom.current().nextInt())
} while (dir.exists())
return dir
}
}

View file

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

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -23,13 +23,15 @@
package io.papermc.paperweight.tasks.patchremap
import io.papermc.paperweight.PaperweightException
import io.papermc.paperweight.util.Git
import java.io.File
import io.papermc.paperweight.util.*
import java.nio.file.Path
import kotlin.io.path.*
class PatchApplier(
private val remappedBranch: String,
private val unmappedBranch: String,
targetDir: File
private val ignoreGitIgnore: Boolean,
targetDir: Path
) {
private val git = Git(targetDir)
@ -50,63 +52,73 @@ class PatchApplier(
git("checkout", unmappedBranch).executeSilently()
}
fun commitInitialSource() {
fun commitPlain(message: String) {
git(*Git.add(ignoreGitIgnore, ".")).executeSilently()
git("commit", "-m", message, "--author=Initial <auto@mated.null>").executeSilently()
}
fun createBranches() {
git("checkout", "-b", unmappedBranch).executeSilently()
git("add", ".").executeSilently()
git("commit", "-m", "Initial Source", "--author=Initial <auto@mated.null>").executeSilently()
git("branch", remappedBranch).executeSilently()
}
fun commitInitialRemappedSource() {
git("add", ".").executeSilently()
git(*Git.add(ignoreGitIgnore, ".")).executeSilently()
git("commit", "-m", "Initial Remapped Source", "--author=Initial <auto@mated.null>").executeSilently()
git("tag", remappedBaseTag)
git("tag", remappedBaseTag).executeSilently()
}
// fun commitRemappingDifferences(remapper: PatchSourceRemapWorker) {
// checkoutRemapped() // Switch to remapped branch without checkout out files
// remapper.remap() // Remap to new mappings
// println("Committing remap")
// git("add", ".").executeSilently()
// git("commit", "-m", "Remap", "--author=Initial <auto@mated.null>").executeSilently()
// checkoutOld()
// }
fun recordCommit() {
commitMessage = git("log", "--format=%B", "-n", "1", "HEAD").getText()
commitAuthor = git("log", "--format=%an <%ae>", "-n", "1", "HEAD").getText()
commitTime = git("log", "--format=%aD", "-n", "1", "HEAD").getText()
}
private fun clearCommit() {
commitMessage = null
commitAuthor = null
commitTime = null
}
fun commitChanges() {
println("Committing remapped changes to $remappedBranch")
val message = commitMessage ?: throw PaperweightException("commitMessage not set")
val author = commitAuthor ?: throw PaperweightException("commitAuthor not set")
val time = commitTime ?: throw PaperweightException("commitTime not set")
commitMessage = null
commitAuthor = null
commitTime = null
clearCommit()
git("add", ".").executeSilently()
git(*Git.add(ignoreGitIgnore, ".")).executeSilently()
git("commit", "-m", message, "--author=$author", "--date=$time").execute()
}
fun applyPatch(patch: File) {
println("Applying patch ${patch.name}")
val result = git("am", "--3way", "--ignore-whitespace", patch.absolutePath).runOut()
fun applyPatch(patch: Path) {
val result = git("am", "--3way", "--ignore-whitespace", patch.absolutePathString()).runOut()
if (result != 0) {
System.err.println("Patch failed to apply: $patch")
throw RuntimeException("Patch failed to apply: $patch")
}
}
fun generatePatches(target: File) {
target.deleteRecursively()
target.mkdirs()
fun generatePatches(target: Path) {
target.deleteRecursive()
target.createDirectories()
git("checkout", remappedBranch).executeSilently()
git(
"format-patch", "--zero-commit", "--full-index", "--no-signature", "--no-stat", "-N", "-o",
target.absolutePath, remappedBaseTag
).runOut()
"format-patch", "--diff-algorith=myers", "--zero-commit", "--full-index", "--no-signature", "--no-stat", "-N", "-o",
target.absolutePathString(), remappedBaseTag
).executeOut()
}
fun isUnfinishedPatch(): Boolean {
if (git("branch", "--show-current").getText().trim() != unmappedBranch) {
return false
}
git("update-index", "--refresh").executeSilently()
if (git("diff-index", "--diff-algorithm=myers", "--quiet", "HEAD", "--").runSilently() == 0) {
return git("log", unmappedBranch, "-1", "--pretty=%B").getText().trim() !=
git("log", remappedBranch, "-1", "--pretty=%B").getText().trim()
}
throw PaperweightException("Unknown state: repo has uncommitted changes")
}
}

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,9 +22,10 @@
package io.papermc.paperweight.tasks.patchremap
import io.papermc.paperweight.util.Constants
import java.nio.file.Files
import io.papermc.paperweight.util.*
import io.papermc.paperweight.util.constants.*
import java.nio.file.Path
import kotlin.io.path.*
import org.cadixdev.at.AccessTransformSet
import org.cadixdev.lorenz.MappingSet
import org.cadixdev.mercury.Mercury
@ -57,7 +58,7 @@ class PatchSourceRemapWorker(
fun remap() {
setup()
println("mapping to ${Constants.DEOBF_NAMESPACE}")
println("mapping to $DEOBF_NAMESPACE")
merc.rewrite(inputDir, outputDir)
@ -65,20 +66,13 @@ class PatchSourceRemapWorker(
}
private fun setup() {
outputDir.deleteRecursively()
Files.createDirectories(outputDir)
outputDir.deleteRecursive()
outputDir.createDirectories()
}
private fun cleanup() {
inputDir.deleteRecursively()
Files.move(outputDir, inputDir)
outputDir.deleteRecursively()
}
private fun Path.deleteRecursively() {
if (Files.notExists(this)) {
return
}
Files.walk(this).use { stream -> stream.sorted(Comparator.reverseOrder()).forEach(Files::delete) }
inputDir.deleteRecursive()
outputDir.moveTo(inputDir)
outputDir.deleteRecursive()
}
}

View file

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

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -25,11 +25,9 @@ package io.papermc.paperweight.util
data class BuildDataInfo(
val minecraftVersion: String,
val serverUrl: String,
val minecraftHash: String,
val accessTransforms: String,
val classMappings: String,
val memberMappings: String,
val packageMappings: String,
val classMapCommand: String,
val memberMapCommand: String,
val finalMapCommand: String,

View file

@ -1,7 +1,7 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2020 Kyle Wood (DemonWav)
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
@ -22,15 +22,4 @@
package io.papermc.paperweight.util
data class MinecraftManifest(
internal val latest: Map<String, *>,
internal val versions: List<ManifestVersion>
)
data class ManifestVersion(
internal val id: String,
internal val type: String,
internal val time: String,
internal val releaseTime: String,
internal val url: String
)
data class ClassNameChange(val obfName: String, val deobfName: String)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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