├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src └── main ├── kotlin └── io │ └── github │ └── p03w │ └── machete │ ├── MachetePlugin.kt │ ├── config │ ├── MachetePluginExtension.kt │ └── optimizations │ │ ├── JIJConfig.kt │ │ ├── JsonConfig.kt │ │ ├── PngConfig.kt │ │ ├── XmlConfig.kt │ │ └── lossy │ │ ├── LVTStripConfig.kt │ │ └── SourceFileStripConfig.kt │ ├── core │ ├── JarOptimizer.kt │ ├── OxipngManager.kt │ ├── libs │ │ ├── json │ │ │ ├── JsonFormatError.kt │ │ │ ├── JsonMinifier.kt │ │ │ ├── JsonParserStringWrapper.kt │ │ │ └── JsonValue.kt │ │ └── xml │ │ │ └── XMLMinifier.kt │ └── passes │ │ ├── ClassFilePass.kt │ │ ├── JarOptimizationPass.kt │ │ ├── JsonPass.kt │ │ ├── PngPass.kt │ │ └── XmlPass.kt │ ├── patches │ ├── AddMinecraftFileTypesPatch.kt │ ├── Patch.kt │ └── Patches.kt │ ├── tasks │ ├── DumpTasksWithOutputJarsTask.kt │ ├── OptimizeJarsTask.kt │ └── UnpackOxipngTask.kt │ └── util │ ├── FileUtil.kt │ ├── KnownGoodTasks.kt │ ├── StringUtil.kt │ ├── TryCatchAll.kt │ └── invokeProcess.kt └── resources └── oxipng ├── OXIPNG_LICENSE ├── oxipng-apple ├── oxipng-linux └── oxipng-windows.exe /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://www.paypal.com/donate/?hosted_button_id=73KL89UWZDDQQ"] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Machete 2 | 3 | Machete is a gradle plugin that attempts to optimize the size of output JARs 4 | through both individual file optimizations and overall compression increases inspired by the [Detonater](https://github.com/EnnuiL/Detonater) project. 5 | 6 | Simply applying the plugin should be enough to apply any optimizations in a standard environment, 7 | as it will collect output JARs that are known to be usable artifacts and optimize them after the `build` task. 8 | 9 | **Please note that this plugin works best on high-resource density projects**. Code-heavy ones will have minimal success and may even inflate overall. 10 | 11 | ### Optimizations 12 | 13 | - JSON files are minimized by reading+writing through a custom formatter, which strips any whitespace. 14 | - PNG files are run through the [Oxipng](https://github.com/shssoichiro/oxipng) project on maximum compression and metadata removal 15 | - Nested JAR files are unpacked and have the same optimizations run on them 16 | - XML files have extra whitespace removed 17 | - The final result is then compressed with DEFLATE level 9, providing modest overall compression (bytecode doesn't compress well unfortunately) 18 | 19 | There are also some disabled-by-default optimizations as they are technically lossy on the behavior of the jar. 20 | 21 | - Local Variable Table stripping, disabled because this table is used for the "helpful NPEs" feature in java 14+ 22 | - Source file stripping, disabled because this is used to give the file a class was compiled from in error messages 23 | 24 | More optimizations are planned as well, feel free to open an issue! 25 | 26 | ### Installation 27 | 28 | Machete is available on the gradle plugin portal under `io.github.p03w.machete`, simply apply 29 | the plugin to your project, and it should work out of the box. 30 | 31 | See [here](https://plugins.gradle.org/plugin/io.github.p03w.machete) for in-depth install instructions. 32 | 33 | ### Configuration 34 | 35 | To configure the plugin, use the `machete` block. This allows you to 36 | 37 | - Add or remove tasks to pull output JARs from (`additionalTasks`/`ignoredTasks`, also please consider opening a PR if you use these) 38 | - Disable overwriting the original artifacts (`keepOriginal`) 39 | - Enable or disable specific optimizations (`optimizations`) 40 | 41 | An example full config may look like: 42 | ``` 43 | machete { 44 | // Also optimize the output of task "foo" 45 | additionalTasks.add("foo") 46 | // Do not optimize the output of "bar" 47 | ignoredTasks.add("bar") 48 | 49 | // Keep the original copies in the build directory 50 | keepOriginal = true 51 | 52 | // Disable task hooking (if you want to manage it manually or seperately) 53 | finalizeAfter = "" 54 | 55 | // Disable the JIJ, PNG, and JSON optimizations 56 | jij.enabled = false 57 | png.enabled = false 58 | json.enabled = false 59 | 60 | // Enable all lossy optimizations 61 | lvtStriping.enabled = true 62 | sourceFileStriping.enabled = true 63 | 64 | // Make the PNG optimization (even though disabled here, shush) 65 | // Use less optimization and no alpha optimizations 66 | png.alpha = false 67 | png.optimizationLevel = 2 68 | } 69 | ``` 70 | 71 | To locate tasks that can be added, use the `dumpTasksWithOutputJars` task, that will automatically list any tasks with output JARs. 72 | 73 | ### Supported 3rd party plugins 74 | 75 | This is a list of currently supported 3rd party plugins that Machete can automatically optimize the output of. 76 | Please see [KnownGoodTasks.kt](https://github.com/P03W/Machete/blob/master/src/main/kotlin/io/github/p03w/machete/util/KnownGoodTasks.kt) for this support. 77 | If you are using a plugin that doesn't specify its output artifacts, I'm sorry, but I can't add support, 78 | please consider poking the developer about using that system (although im sure the code here is garbage as well LMAO, 79 | gradle have good docs challenge (impossible)). 80 | 81 | - [shadow](https://github.com/johnrengelman/shadow) 82 | - [fabric-loom](https://github.com/FabricMC/fabric-loom/) and most derivatives 83 | 84 | --- 85 | 86 | ### Notes on 2.0.0 for end users 87 | - Optimization tasks now hook after `assemble` rather than the task that they're optimizing the output of, this is the most likely to break things but I believe it should be fine for most usecases 88 | - Hooking on `assemble` can be changed or disabled if you want better control 89 | - If using `keepOriginal`, the output will now be cached 90 | - Gradle managed this previously, but this makes the caching explicit 91 | - Oxipng is de-duplicated now -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 3 | 4 | plugins { 5 | java 6 | kotlin("jvm") version "1.6.20" 7 | id("com.gradle.plugin-publish") version "1.0.0-rc-1" 8 | id("com.github.johnrengelman.shadow") version "7.1.2" 9 | } 10 | 11 | group = "io.github.p03w" 12 | version = "2.0.1" 13 | description = "A gradle plugin to optimize built jars through individual file optimizations and increased compression, works best on resource heavy jars" 14 | 15 | //region Dependencies 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | val asmVer = "9.3" 22 | shadow("org.ow2.asm:asm:$asmVer") 23 | shadow("org.ow2.asm:asm-tree:$asmVer") 24 | shadow("org.ow2.asm:asm-commons:$asmVer") 25 | } 26 | //endregion 27 | 28 | //region Task Configure 29 | tasks.withType { 30 | configurations = listOf( 31 | project.configurations.getByName("shadow") 32 | ) 33 | 34 | relocate("org.ow2.asm", "s_m.ow2.asm") 35 | 36 | minimize() 37 | } 38 | 39 | tasks.withType { 40 | kotlinOptions.jvmTarget = "1.8" 41 | kotlinOptions.freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn") 42 | } 43 | 44 | tasks.withType { 45 | enabled = false 46 | } 47 | 48 | tasks.withType { 49 | archiveBaseName.set("machete") 50 | archiveClassifier.set("") 51 | archiveVersion.set(project.version.toString()) 52 | } 53 | //endregion 54 | 55 | //region Plugin Configure 56 | gradlePlugin { 57 | plugins { 58 | create("machetePlugin") { 59 | id = "io.github.p03w.machete" 60 | displayName = "Machete" 61 | implementationClass = "io.github.p03w.machete.MachetePlugin" 62 | } 63 | } 64 | } 65 | 66 | pluginBundle { 67 | website = "https://github.com/SilverAndro/Machete" 68 | vcsUrl = "https://github.com/SilverAndro/Machete" 69 | description = project.description 70 | tags = listOf("jar", "build", "jvm", "compress") 71 | } 72 | //endregion 73 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverAndro/Machete/7251af92cf07fdd4be836d0ecad0bed46f77609e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverAndro/Machete/7251af92cf07fdd4be836d0ecad0bed46f77609e/gradlew -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "Machete" 2 | 3 | pluginManagement { 4 | repositories { 5 | gradlePluginPortal() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/MachetePlugin.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package io.github.p03w.machete 4 | 5 | import io.github.p03w.machete.config.MachetePluginExtension 6 | import io.github.p03w.machete.patches.patches 7 | import io.github.p03w.machete.tasks.DumpTasksWithOutputJarsTask 8 | import io.github.p03w.machete.tasks.OptimizeJarsTask 9 | import io.github.p03w.machete.tasks.UnpackOxipngTask 10 | import io.github.p03w.machete.util.capital 11 | import io.github.p03w.machete.util.knownGoodTasks 12 | import org.gradle.api.Plugin 13 | import org.gradle.api.Project 14 | 15 | class MachetePlugin : Plugin { 16 | override fun apply(project: Project) { 17 | val extension = project.extensions.create("machete", MachetePluginExtension::class.java) 18 | 19 | project.afterEvaluate { 20 | if (extension.enabled.get().not()) { 21 | project.logger.lifecycle("Machete was disabled on this build through the `enabled` flag!") 22 | return@afterEvaluate 23 | } 24 | 25 | val unpackOxipngTask = project.tasks.create("unpackOxipng", UnpackOxipngTask::class.java) 26 | 27 | val tasksToCheck = knownGoodTasks.toMutableSet() 28 | extension.additionalTasks.orNull?.let { 29 | tasksToCheck.addAll(it) 30 | } 31 | extension.ignoredTasks.orNull?.let { 32 | tasksToCheck.removeAll(it) 33 | } 34 | 35 | project.logger.info("All tasks to check: $tasksToCheck") 36 | 37 | tasksToCheck.forEach { taskName -> 38 | val found = project.tasks.findByName(taskName) 39 | if (found != null) { 40 | project.logger.info("Tasks $taskName exists! Generating a hook task") 41 | val toOptimize = found.outputs.files 42 | val optimizeTask = project.tasks.create( 43 | "optimizeOutputsOf${taskName.capital()}", 44 | OptimizeJarsTask::class.java 45 | ) { optimizeTask -> 46 | optimizeTask.group = "machete" 47 | optimizeTask.description = 48 | "An auto-generated task to optimize the output artifacts of $taskName" 49 | 50 | // Try and set the inputs and outputs 51 | optimizeTask.inputs.files(toOptimize) 52 | if (extension.keepOriginal.get().not()) { 53 | optimizeTask.outputs.files(toOptimize) 54 | } else { 55 | optimizeTask.outputs.files(toOptimize.map { file -> 56 | file.resolveSibling(file.nameWithoutExtension + "-optimized.jar") 57 | }) 58 | } 59 | 60 | // We can cache if we arent replacing anything 61 | // Gradle does handle this for us, but doesn't hurt to be explicit 62 | optimizeTask.outputs.cacheIf { extension.keepOriginal.get() } 63 | 64 | // Give everything its own sibling dir to prevent overlapping on parallel tasks 65 | optimizeTask.buildDir.set(project.buildDir.resolve("machete-build").resolve(taskName)) 66 | optimizeTask.extension.set(extension) 67 | 68 | // Make sure oxipng is set up before we do anything 69 | optimizeTask.dependsOn(unpackOxipngTask) 70 | } 71 | 72 | // Hook after build 73 | val after = extension.finalizeAfter.get() 74 | if (after.isNotBlank()) { 75 | project.tasks.getByName(after).finalizedBy(optimizeTask) 76 | } 77 | optimizeTask.dependsOn(found) 78 | } 79 | } 80 | 81 | // Try and apply any compatibility patches 82 | patches.forEach { 83 | project.logger.info("Checking if patch ${it::class.simpleName} should apply") 84 | if (it.shouldApply(project, extension)) { 85 | project.logger.info("Applying project patch ${it::class.simpleName}") 86 | it.patch(project, extension) 87 | } 88 | } 89 | } 90 | 91 | project.tasks.register("dumpTasksWithOutputJars", DumpTasksWithOutputJarsTask::class.java) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/config/MachetePluginExtension.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.config 2 | 3 | import io.github.p03w.machete.config.optimizations.JIJConfig 4 | import io.github.p03w.machete.config.optimizations.JsonConfig 5 | import io.github.p03w.machete.config.optimizations.PngConfig 6 | import io.github.p03w.machete.config.optimizations.XmlConfig 7 | import io.github.p03w.machete.config.optimizations.lossy.LVTStripConfig 8 | import io.github.p03w.machete.config.optimizations.lossy.SourceFileStripConfig 9 | import org.gradle.api.provider.Property 10 | import org.gradle.api.provider.SetProperty 11 | import org.gradle.api.tasks.Input 12 | import org.gradle.api.tasks.Nested 13 | 14 | @Suppress("LeakingThis") 15 | abstract class MachetePluginExtension { 16 | /** 17 | * A set of strings denoting additional tasks to pull output jars from 18 | */ 19 | @get:Input 20 | abstract val additionalTasks: SetProperty 21 | 22 | /** 23 | * A set of strings denoting tasks to exclude output jars from 24 | */ 25 | @get:Input 26 | abstract val ignoredTasks: SetProperty 27 | 28 | /** 29 | * If the plugin should do anything at all, mainly for debugging or other purposes such as not running on local builds 30 | */ 31 | @get:Input 32 | abstract val enabled: Property 33 | 34 | /** 35 | * If the original files should be kept by writing optimized ones with "-optimized" at the end of the file name 36 | */ 37 | @get:Input 38 | abstract val keepOriginal: Property 39 | 40 | /** 41 | * What task to attach to for finalization, empty string to disable, defaults to `assemble` 42 | */ 43 | @get:Input 44 | abstract val finalizeAfter: Property 45 | 46 | @get:Nested 47 | abstract val json: JsonConfig 48 | 49 | @get:Nested 50 | abstract val png: PngConfig 51 | 52 | @get:Nested 53 | abstract val jij: JIJConfig 54 | 55 | @get:Nested 56 | abstract val xml: XmlConfig 57 | 58 | @get:Nested 59 | abstract val lvtStriping: LVTStripConfig 60 | 61 | @get:Nested 62 | abstract val sourceFileStriping: SourceFileStripConfig 63 | 64 | init { 65 | enabled.convention(true) 66 | keepOriginal.convention(false) 67 | finalizeAfter.convention("assemble") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/config/optimizations/JIJConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.config.optimizations 2 | 3 | import org.gradle.api.provider.ListProperty 4 | import org.gradle.api.provider.Property 5 | import org.gradle.api.tasks.Input 6 | 7 | @Suppress("LeakingThis") 8 | abstract class JIJConfig { 9 | /** 10 | * A list of file extensions to also process as JSON files 11 | */ 12 | @get:Input 13 | abstract val extraFileExtensions: ListProperty 14 | 15 | @get:Input 16 | abstract val enabled: Property 17 | 18 | init { 19 | enabled.convention(true) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/config/optimizations/JsonConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.config.optimizations 2 | 3 | import org.gradle.api.provider.ListProperty 4 | import org.gradle.api.provider.Property 5 | import org.gradle.api.tasks.Input 6 | 7 | @Suppress("LeakingThis") 8 | abstract class JsonConfig { 9 | /** 10 | * A list of file extensions to also process as JSON files 11 | */ 12 | @get:Input 13 | abstract val extraFileExtensions: ListProperty 14 | 15 | @get:Input 16 | abstract val enabled: Property 17 | 18 | init { 19 | enabled.convention(true) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/config/optimizations/PngConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.config.optimizations 2 | 3 | import org.gradle.api.provider.ListProperty 4 | import org.gradle.api.provider.Property 5 | import org.gradle.api.tasks.Input 6 | 7 | @Suppress("LeakingThis") 8 | abstract class PngConfig { 9 | /** 10 | * A list of file extensions to also process as JSON files 11 | */ 12 | @get:Input 13 | abstract val extraFileExtensions: ListProperty 14 | 15 | @get:Input 16 | abstract val enabled: Property 17 | 18 | @get:Input 19 | abstract val optimizationLevel: Property 20 | 21 | @get:Input 22 | abstract val strip: Property 23 | 24 | @get:Input 25 | abstract val alpha: Property 26 | 27 | @get:Input 28 | abstract val expectReunpack: Property 29 | 30 | init { 31 | enabled.convention(true) 32 | optimizationLevel.convention(4) 33 | strip.convention(Strip.ALL) 34 | alpha.convention(true) 35 | expectReunpack.convention(false) 36 | } 37 | 38 | @Suppress("unused") 39 | enum class Strip(val flag: String) { 40 | NONE(""), 41 | SAFE("safe"), 42 | ALL("all") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/config/optimizations/XmlConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.config.optimizations 2 | 3 | import org.gradle.api.provider.ListProperty 4 | import org.gradle.api.provider.Property 5 | import org.gradle.api.tasks.Input 6 | 7 | @Suppress("LeakingThis") 8 | abstract class XmlConfig { 9 | /** 10 | * A list of file extensions to also process as JSON files 11 | */ 12 | @get:Input 13 | abstract val extraFileExtensions: ListProperty 14 | 15 | @get:Input 16 | abstract val enabled: Property 17 | 18 | init { 19 | enabled.convention(true) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/config/optimizations/lossy/LVTStripConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.config.optimizations.lossy 2 | 3 | import org.gradle.api.provider.Property 4 | import org.gradle.api.tasks.Input 5 | 6 | @Suppress("LeakingThis") 7 | abstract class LVTStripConfig { 8 | @get:Input 9 | abstract val enabled: Property 10 | 11 | init { 12 | enabled.convention(false) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/config/optimizations/lossy/SourceFileStripConfig.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.config.optimizations.lossy 2 | 3 | import org.gradle.api.provider.Property 4 | import org.gradle.api.tasks.Input 5 | 6 | @Suppress("LeakingThis") 7 | abstract class SourceFileStripConfig { 8 | @get:Input 9 | abstract val enabled: Property 10 | 11 | init { 12 | enabled.convention(false) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/JarOptimizer.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import io.github.p03w.machete.core.passes.* 5 | import io.github.p03w.machete.util.allWithExtension 6 | import io.github.p03w.machete.util.resolveAndMake 7 | import io.github.p03w.machete.util.resolveAndMakeSiblingDir 8 | import io.github.p03w.machete.util.unzip 9 | import org.gradle.api.Project 10 | import java.io.File 11 | import java.nio.file.Files 12 | import java.util.jar.JarEntry 13 | import java.util.jar.JarFile 14 | import java.util.jar.JarOutputStream 15 | import java.util.zip.Deflater 16 | import java.util.zip.ZipInputStream 17 | 18 | /** 19 | * Manages optimizing a jar 20 | */ 21 | class JarOptimizer( 22 | val workDir: File, 23 | val file: File, 24 | val config: MachetePluginExtension, 25 | val project: Project, 26 | val isChild: Boolean = false 27 | ) { 28 | private val children = mutableMapOf() 29 | private val toIgnore = mutableListOf() 30 | 31 | private val log = project.logger 32 | 33 | private val passes = buildList { 34 | if (config.png.enabled.get()) add(PngPass) 35 | if (config.json.enabled.get()) add(JsonPass) 36 | if (config.xml.enabled.get()) add(XmlPass) 37 | add(ClassFilePass) 38 | } 39 | 40 | fun unpack() { 41 | JarFile(file).use { jarFile -> 42 | jarFile.manifest?.entries?.forEach { (t, u) -> 43 | // File is signed! JVM will throw some nasty errors if we change this file at all and try to launch 44 | if (u.entries.find { it.key.toString().contains("Digest") } != null) { 45 | toIgnore.add(t.split("/").last()) 46 | log.info("[${project.name}] Will skip file ${t.split("/").last()} as it is signed") 47 | } 48 | } 49 | } 50 | 51 | ZipInputStream(file.inputStream().buffered()).use { 52 | it.unzip(workDir) 53 | } 54 | } 55 | 56 | private fun optimizeJarInJar() { 57 | workDir.allWithExtension("jar", config.jij.extraFileExtensions.get(), toIgnore) { file -> 58 | val unpack = 59 | JarOptimizer(workDir.resolveAndMakeSiblingDir(file.nameWithoutExtension), file, config, project, true) 60 | unpack.unpack() 61 | unpack.optimize() 62 | 63 | val outJar = workDir.resolveAndMakeSiblingDir("tmpJars").resolveAndMake(file.name) 64 | 65 | unpack.repackTo(outJar) 66 | children[file.relativeTo(workDir).path] = outJar 67 | } 68 | } 69 | 70 | fun optimize() { 71 | passes.forEach { 72 | workDir.walkBottomUp().filter { !toIgnore.contains(it.name) }.forEach { file -> 73 | if (it.shouldRunOnFile(file, config, log)) { 74 | it.processFile(file, config, log, workDir, project) 75 | } 76 | } 77 | } 78 | 79 | if (config.jij.enabled.get()) optimizeJarInJar() 80 | } 81 | 82 | fun repackTo(file: File) { 83 | file.delete() 84 | val jar = JarOutputStream(file.outputStream().buffered()) 85 | 86 | if (isChild) { 87 | jar.setLevel(Deflater.NO_COMPRESSION) 88 | } else { 89 | jar.setLevel(Deflater.BEST_COMPRESSION) 90 | } 91 | 92 | jar.use { 93 | fun File.pathInJar(): String { 94 | return this.relativeTo(workDir).path.replace("\\", "/") 95 | } 96 | 97 | // .jars are handled by the children list, so that we can place them properly 98 | workDir.walkBottomUp().toList().filter { 99 | it.isFile && (it.extension != "jar" || !config.jij.enabled.get()) 100 | }.forEach { optimizedFile -> 101 | val entry = JarEntry(optimizedFile.pathInJar()) 102 | jar.putNextEntry(entry) 103 | Files.copy(optimizedFile.toPath(), it) 104 | jar.closeEntry() 105 | } 106 | 107 | children.forEach { (path, childJar) -> 108 | val entry = JarEntry(path.replace("\\", "/")) 109 | jar.putNextEntry(entry) 110 | Files.copy(childJar.toPath(), it) 111 | jar.closeEntry() 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/OxipngManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core 2 | 3 | import io.github.p03w.machete.config.optimizations.PngConfig 4 | import io.github.p03w.machete.util.* 5 | import org.slf4j.LoggerFactory 6 | import java.io.File 7 | import java.nio.file.Files 8 | import java.nio.file.StandardCopyOption 9 | import java.nio.file.attribute.PosixFilePermissions 10 | import kotlin.io.path.absolute 11 | 12 | /** 13 | * Manages the Oxipng file 14 | */ 15 | object OxipngManager { 16 | lateinit var tempDir: File 17 | private val platform: Platform 18 | private val useNative = doesProcessRun("oxipng") 19 | private lateinit var oxipng: String 20 | 21 | private val logger = LoggerFactory.getLogger("Machete") 22 | 23 | init { 24 | // Get the OS name 25 | val osName = System.getProperty("os.name") 26 | // Try to guess the platform 27 | platform = when { 28 | osName.startsWith("Windows") -> Platform.WINDOWS 29 | osName.startsWith("Mac") || osName.startsWith("Darwin") -> Platform.APPLE 30 | else -> Platform.LINUX 31 | } 32 | } 33 | 34 | fun unpackOxipng(name: String) { 35 | if (useNative) { 36 | logger.info("Using native oxipng install instead of unpacking") 37 | return 38 | } 39 | 40 | // Get the file specific to this platform 41 | val oxipngStream = when (platform) { 42 | Platform.WINDOWS -> this::class.java.getResourceAsStream("/oxipng/oxipng-windows.exe") 43 | Platform.APPLE -> this::class.java.getResourceAsStream("/oxipng/oxipng-apple") 44 | Platform.LINUX -> this::class.java.getResourceAsStream("/oxipng/oxipng-linux") 45 | } ?: throw IllegalStateException("Could not unpack oxipng binary for platform ${platform.name}") 46 | 47 | // Grab the license 48 | val licenseStream = this::class.java.getResourceAsStream("/oxipng/OXIPNG_LICENSE") 49 | 50 | // Make sure its .exe if windows, executable if linux/mac 51 | val file = if (platform == Platform.WINDOWS) { 52 | tempDir.resolveAndMake("oxipng.exe") 53 | } else { 54 | tempDir.resolveAndMake("oxipng") 55 | } 56 | 57 | logger.info("Detected platform is $platform for project $name") 58 | // Copy out oxipng, overwriting any previous copies 59 | Files.copy(oxipngStream, file.toPath().absolute(), StandardCopyOption.REPLACE_EXISTING) 60 | // Try to make the file executable 61 | file.setExecutable(true) 62 | if (platform != Platform.WINDOWS) { 63 | // Linux D: 64 | tryCatchAll( 65 | { invokeProcess("chmod", "+x", file.absolutePath) }, 66 | { Files.setPosixFilePermissions(file.toPath(), PosixFilePermissions.fromString("rwxr-x---")) } 67 | ) { it.printStackTrace() } 68 | } 69 | if (file.canExecute().not()) { 70 | logger.error("Could not set executable bit for oxipng!\n" + 71 | "Seems you've run into an edge case I didn't consider :(\n" + 72 | "I've disabled png optimization due to this error, open an issue?") 73 | return 74 | } 75 | 76 | // Unpack or display the license, in compliance with the MIT license which states: 77 | // "The above copyright notice and this permission notice shall be included in all 78 | // copies or substantial portions of the Software." 79 | if (licenseStream != null) { 80 | Files.copy( 81 | licenseStream, 82 | file.resolveAndMakeSibling("OXIPNG_LICENSE").toPath(), 83 | StandardCopyOption.REPLACE_EXISTING 84 | ) 85 | } else { 86 | logger.warn("Failed to unpack oxipng license, oxipng is licensed under the MIT license, Copyright (c) 2016 Joshua Holmer") 87 | } 88 | 89 | logger.info("Oxipng should be at ${file.absolutePath}") 90 | if (file.exists().not()) { 91 | logger.error("Oxipng didn't get created? Should be at ${file.absolutePath} but OS reports file doesn't exist, disabled png optimization.") 92 | return 93 | } 94 | 95 | this.oxipng = file.absolutePath 96 | } 97 | 98 | fun optimize(file: File, config: PngConfig, name: String) { 99 | if (this::oxipng.isInitialized || useNative) { 100 | if (!useNative && File(oxipng).exists().not()) { 101 | if (config.expectReunpack.get()) { 102 | logger.warn("Oxipng binary was removed? Re-unpacking in attempt to recover (use `png.expectReunpack = true` to suppress)") 103 | } 104 | unpackOxipng(name) 105 | } 106 | 107 | val args = mutableListOf( 108 | if (!useNative) oxipng else "oxipng", 109 | file.absolutePath, 110 | "-o", config.optimizationLevel.get().toString(), 111 | "--out", file.absolutePath 112 | ) 113 | 114 | if (config.strip.get() != PngConfig.Strip.NONE) { 115 | args.add("--strip") 116 | args.add(config.strip.get().flag) 117 | } 118 | 119 | if (config.alpha.get()) { 120 | args.add("-a") 121 | } 122 | 123 | // Run oxipng with every optimization enabled 124 | invokeProcess(*args.toTypedArray()) 125 | } 126 | } 127 | 128 | enum class Platform { 129 | WINDOWS, 130 | APPLE, 131 | LINUX 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/libs/json/JsonFormatError.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.libs.json 2 | 3 | class JsonFormatError(override val message: String) : Exception() 4 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/libs/json/JsonMinifier.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.libs.json 2 | 3 | import io.github.p03w.machete.util.matches 4 | 5 | // Compliant with EMCA-404 rev. 2 6 | // https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf 7 | // Some lenience added for code simplicity and UX 8 | class JsonMinifier(private val original: String) { 9 | private val input = JsonParserStringWrapper(original) 10 | private val root: JsonValue? 11 | 12 | init { 13 | root = parse(input) 14 | } 15 | 16 | private fun parse(input: JsonParserStringWrapper): JsonValue { 17 | input.takeAllOf(wsRegex) 18 | return when (input.current()) { 19 | '{' -> parseObject(input) 20 | '[' -> parseArray(input) 21 | else -> throw JsonFormatError("Unrecognized starting character \"${input.current()}\"") 22 | } 23 | } 24 | 25 | private fun parseAny(input: JsonParserStringWrapper): JsonValue { 26 | input.takeAllOf(wsRegex) 27 | return when (val start = input.current()) { 28 | '{' -> parseObject(input) 29 | '[' -> parseArray(input) 30 | '"' -> parseString(input) 31 | else -> { 32 | // Guess the type 33 | if (start == 't' || start == 'f' || start == 'n') return parseFixed(input) 34 | if (numberRegex.matches(start)) return parseNumber(input) 35 | throw JsonFormatError("Unknown type start \"$start\"") 36 | } 37 | } 38 | } 39 | 40 | private fun parseNumber(input: JsonParserStringWrapper): JsonNumberValue { 41 | input.takeAllOf(wsRegex) 42 | return JsonNumberValue(input.takeAllOf(numberRegex)) 43 | } 44 | 45 | private fun parseFixed(input: JsonParserStringWrapper): JsonFixedValue { 46 | input.takeAllOf(wsRegex) 47 | return when (val taken = input.take()) { 48 | 't' -> { 49 | input.take('r'); input.take('u'); input.take('e') 50 | JsonFixedValue("true") 51 | } 52 | 'f' -> { 53 | input.take('a'); input.take('l'); input.take('s'); input.take('e') 54 | JsonFixedValue("false") 55 | } 56 | 'n' -> { 57 | input.take('u'); input.take('l'); input.take('l') 58 | JsonFixedValue("null") 59 | } 60 | else -> throw JsonFormatError("Unknown fixed starting character \"$taken\"") 61 | } 62 | } 63 | 64 | private fun parseObject(input: JsonParserStringWrapper): JsonObjectValue { 65 | input.takeAllOf(wsRegex) 66 | val obj = JsonObjectValue() 67 | 68 | input.take('{') 69 | 70 | while (true) { 71 | input.takeAllOf(wsRegex) 72 | when (input.current()) { 73 | '"' -> { 74 | val key = parseString(input) 75 | input.takeAllOf(wsRegex) 76 | input.take(':') 77 | val value = parseAny(input) 78 | obj.put(key, value) 79 | } 80 | ',' -> input.take() 81 | '}' -> break 82 | else -> throw JsonFormatError("Unknown character in object definition \"${input.peek()}\"") 83 | } 84 | } 85 | 86 | input.take('}') 87 | 88 | return obj 89 | } 90 | 91 | private fun parseArray(input: JsonParserStringWrapper): JsonArrayValue { 92 | input.takeAllOf(wsRegex) 93 | 94 | val array = JsonArrayValue() 95 | 96 | input.take('[') 97 | 98 | while (true) { 99 | input.takeAllOf(wsRegex) 100 | when (input.current()) { 101 | ',' -> input.take() 102 | ']' -> break 103 | else -> { 104 | val value = parseAny(input) 105 | array.put(value) 106 | } 107 | } 108 | } 109 | 110 | input.take(']') 111 | 112 | return array 113 | } 114 | 115 | private fun parseString(input: JsonParserStringWrapper): JsonStringValue { 116 | input.takeAllOf(wsRegex) 117 | return JsonStringValue(buildString { 118 | append(input.take('"')) 119 | // Handle escaping 120 | // It just so happens that (any odd number of escapes) = (1 true quote escape) 121 | // So count up the escapes and eat until the next quote if odd 122 | do { 123 | append(input.takeUntil('"')) 124 | val escapeCount = input.countBeforeOf('\\') 125 | } while (escapeCount % 2 == 1) 126 | }) 127 | } 128 | 129 | override fun toString(): String { 130 | return root?.getOutput() ?: original 131 | } 132 | 133 | companion object { 134 | val wsRegex = Regex("[ \r\n\t]") 135 | val numberRegex = Regex("[-\\d.eE+]") 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/libs/json/JsonParserStringWrapper.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.libs.json 2 | 3 | import io.github.p03w.machete.util.matches 4 | 5 | class JsonParserStringWrapper(private val string: String) { 6 | private var index = 0 7 | 8 | fun current() = string[index] 9 | fun peek() = string[index + 1] 10 | fun take() = string[index++] 11 | fun take(required: Char) = take().also { 12 | if (it != required) throw JsonFormatError("Expected to read \"$required\" but read \"$it\"") 13 | } 14 | 15 | fun takeUntil(delim: Char): String { 16 | return buildString { 17 | while (current() != delim) { 18 | append(take()) 19 | } 20 | append(take()) 21 | } 22 | } 23 | 24 | fun countBeforeOf(char: Char): Int { 25 | var count = 0 26 | for (x in index-2 downTo 0) { 27 | if (string[x] == char) count++ else return count 28 | } 29 | return count 30 | } 31 | 32 | fun takeAllOf(regex: Regex): String { 33 | return buildString { 34 | while (regex.matches(current())) { 35 | append(take()) 36 | } 37 | } 38 | } 39 | 40 | override fun toString(): String { 41 | return string.substring(index) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/libs/json/JsonValue.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.libs.json 2 | 3 | sealed class JsonValue { 4 | abstract fun getOutput(): String 5 | } 6 | 7 | class JsonFixedValue(val representation: String) : JsonValue() { 8 | override fun getOutput(): String { 9 | return representation 10 | } 11 | } 12 | 13 | class JsonNumberValue(val representation: String) : JsonValue() { 14 | override fun getOutput(): String { 15 | return representation 16 | } 17 | } 18 | 19 | class JsonStringValue(val representation: String) : JsonValue() { 20 | override fun getOutput(): String { 21 | return representation 22 | } 23 | } 24 | 25 | class JsonArrayValue : JsonValue() { 26 | private val list = mutableListOf() 27 | 28 | fun put(value: JsonValue) { 29 | list.add(value) 30 | } 31 | 32 | override fun getOutput(): String { 33 | return buildString { 34 | append("[") 35 | 36 | list.forEachIndexed { index, value -> 37 | append(value.getOutput()) 38 | 39 | if (index != list.lastIndex) { 40 | append(",") 41 | } 42 | } 43 | 44 | append("]") 45 | } 46 | } 47 | } 48 | 49 | class JsonObjectValue : JsonValue() { 50 | // Can have duplicates, and there isn't really a reasonable de-duplication strategy that won't upset someone 51 | private val map = LinkedHashMap() 52 | 53 | fun put(key: JsonStringValue, value: JsonValue) { 54 | map[key] = value 55 | } 56 | 57 | override fun getOutput(): String { 58 | return buildString { 59 | append("{") 60 | 61 | var count = 0 62 | val last = map.size - 1 63 | map.forEach { (key, value) -> 64 | append(key.getOutput()) 65 | append(":") 66 | append(value.getOutput()) 67 | 68 | if (count != last) { 69 | append(",") 70 | count++ 71 | } 72 | } 73 | 74 | append("}") 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/libs/xml/XMLMinifier.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.libs.xml 2 | 3 | class XMLMinifier(private val original: String) { 4 | override fun toString(): String { 5 | return emptyTagRegex.replace(original, "$1") 6 | } 7 | 8 | companion object { 9 | val emptyTagRegex = Regex("(<.*?>)\\s*") 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/passes/ClassFilePass.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.passes 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import org.gradle.api.Project 5 | import org.objectweb.asm.ClassReader 6 | import org.objectweb.asm.ClassWriter 7 | import org.objectweb.asm.tree.ClassNode 8 | import org.slf4j.Logger 9 | import java.io.File 10 | import java.util.* 11 | 12 | object ClassFilePass : JarOptimizationPass { 13 | private enum class StripData { 14 | LVT, 15 | SOURCE_FILE 16 | } 17 | 18 | override fun shouldRunOnFile(file: File, config: MachetePluginExtension, log: Logger): Boolean { 19 | val ext = file.extension 20 | return ext == "class" && ( 21 | config.sourceFileStriping.enabled.get() || 22 | config.lvtStriping.enabled.get() 23 | ) 24 | } 25 | 26 | override fun processFile(file: File, config: MachetePluginExtension, log: Logger, workDir: File, project: Project) { 27 | val toStrip = EnumSet.noneOf(StripData::class.java) 28 | if (config.lvtStriping.enabled.get()) toStrip.add(StripData.LVT) 29 | if (config.sourceFileStriping.enabled.get()) toStrip.add(StripData.SOURCE_FILE) 30 | 31 | if (toStrip.isNotEmpty()) { 32 | val reader = file.inputStream().buffered().use { 33 | ClassReader(it) 34 | } 35 | 36 | val node = ClassNode() 37 | reader.accept(node, 0) 38 | 39 | if (toStrip.contains(StripData.SOURCE_FILE)) node.sourceFile = null 40 | 41 | if (toStrip.contains(StripData.LVT)) { 42 | node.methods.forEach { 43 | it.localVariables?.clear() 44 | } 45 | } 46 | 47 | val writer = ClassWriter(0) 48 | node.accept(writer) 49 | 50 | file.outputStream().use { stream -> 51 | stream.write(writer.toByteArray()) 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/passes/JarOptimizationPass.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.passes 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import org.gradle.api.Project 5 | import org.slf4j.Logger 6 | import java.io.File 7 | 8 | interface JarOptimizationPass { 9 | fun shouldRunOnFile(file: File, config: MachetePluginExtension, log: Logger): Boolean 10 | fun processFile(file: File, config: MachetePluginExtension, log: Logger, workDir: File, project: Project) 11 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/passes/JsonPass.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.passes 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import io.github.p03w.machete.core.libs.json.JsonMinifier 5 | import org.gradle.api.Project 6 | import org.slf4j.Logger 7 | import java.io.File 8 | 9 | object JsonPass : JarOptimizationPass { 10 | override fun shouldRunOnFile(file: File, config: MachetePluginExtension, log: Logger): Boolean { 11 | val ext = file.extension 12 | return ext == "json" || config.json.extraFileExtensions.get().contains(ext) 13 | } 14 | 15 | override fun processFile(file: File, config: MachetePluginExtension, log: Logger, workDir: File, project: Project) { 16 | val text = file.bufferedReader().use { 17 | it.readText() 18 | } 19 | 20 | file.bufferedWriter().use { 21 | try { 22 | val final = JsonMinifier(text) 23 | it.write(final.toString()) 24 | } catch (err: Throwable) { 25 | log.warn("Failed to optimize ${file.relativeTo(workDir).path}") 26 | err.printStackTrace() 27 | it.write(text) 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/passes/PngPass.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.passes 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import io.github.p03w.machete.core.OxipngManager 5 | import org.gradle.api.Project 6 | import org.slf4j.Logger 7 | import java.io.File 8 | 9 | object PngPass : JarOptimizationPass { 10 | override fun shouldRunOnFile(file: File, config: MachetePluginExtension, log: Logger): Boolean { 11 | val ext = file.extension 12 | return ext == "png" || config.png.extraFileExtensions.get().contains(ext) 13 | } 14 | 15 | override fun processFile(file: File, config: MachetePluginExtension, log: Logger, workDir: File, project: Project) { 16 | try { 17 | OxipngManager.optimize(file, config.png, project.name) 18 | } catch (err: Throwable) { 19 | log.warn("Failed to optimize ${file.relativeTo(workDir).path}") 20 | err.printStackTrace() 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/core/passes/XmlPass.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.core.passes 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import io.github.p03w.machete.core.libs.xml.XMLMinifier 5 | import org.gradle.api.Project 6 | import org.slf4j.Logger 7 | import java.io.File 8 | 9 | object XmlPass : JarOptimizationPass { 10 | override fun shouldRunOnFile(file: File, config: MachetePluginExtension, log: Logger): Boolean { 11 | val ext = file.extension 12 | return ext == "xml" || config.xml.extraFileExtensions.get().contains(ext) 13 | } 14 | 15 | override fun processFile(file: File, config: MachetePluginExtension, log: Logger, workDir: File, project: Project) { 16 | val text = file.bufferedReader().use { 17 | it.readText() 18 | } 19 | 20 | file.bufferedWriter().use { 21 | try { 22 | val final = XMLMinifier(text) 23 | it.write(final.toString()) 24 | } catch (err: Throwable) { 25 | log.warn("Failed to optimize ${file.relativeTo(workDir).path}") 26 | err.printStackTrace() 27 | it.write(text) 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/patches/AddMinecraftFileTypesPatch.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.patches 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import org.gradle.api.Project 5 | 6 | object AddMinecraftFileTypesPatch : Patch { 7 | private val minecraftPlugins = setOf( 8 | "net.minecraftforge.gradle", 9 | "fabric-loom", 10 | "org.quiltmc.loom" 11 | ) 12 | 13 | override fun shouldApply(project: Project, config: MachetePluginExtension): Boolean { 14 | return minecraftPlugins.any { 15 | project.plugins.hasPlugin(it) 16 | } 17 | } 18 | 19 | override fun patch(project: Project, config: MachetePluginExtension) { 20 | config.json.extraFileExtensions.addAll(listOf( 21 | "mcmeta" 22 | )) 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/patches/Patch.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.patches 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import org.gradle.api.Project 5 | 6 | interface Patch { 7 | fun shouldApply(project: Project, config: MachetePluginExtension): Boolean 8 | fun patch(project: Project, config: MachetePluginExtension) 9 | } 10 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/patches/Patches.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.patches 2 | 3 | val patches = listOf( 4 | AddMinecraftFileTypesPatch 5 | ) 6 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/tasks/DumpTasksWithOutputJarsTask.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.tasks 2 | 3 | import org.gradle.api.DefaultTask 4 | import org.gradle.api.tasks.TaskAction 5 | 6 | abstract class DumpTasksWithOutputJarsTask : DefaultTask() { 7 | @TaskAction 8 | fun dumpTasksWithOutputJars() { 9 | project.tasks.forEach { task -> 10 | val files = task.outputs.files.filter { it.extension == "jar" } 11 | if (files.isEmpty.not()) { 12 | val output = buildString { 13 | appendLine(task.name) 14 | files.map { it.path }.forEach { 15 | appendLine("- $it") 16 | } 17 | } 18 | println(output) 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/tasks/OptimizeJarsTask.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.tasks 2 | 3 | import io.github.p03w.machete.config.MachetePluginExtension 4 | import io.github.p03w.machete.core.JarOptimizer 5 | import io.github.p03w.machete.util.resolveAndMakeSibling 6 | import org.gradle.api.DefaultTask 7 | import org.gradle.api.provider.Property 8 | import org.gradle.api.tasks.Input 9 | import org.gradle.api.tasks.Nested 10 | import org.gradle.api.tasks.TaskAction 11 | import java.io.File 12 | 13 | abstract class OptimizeJarsTask : DefaultTask() { 14 | @get:Input 15 | abstract val buildDir: Property 16 | 17 | @get:Nested 18 | abstract val extension: Property 19 | 20 | @TaskAction 21 | fun optimizeJars() { 22 | inputs.files.forEach { 23 | val tempJarDir = buildDir.get().resolve("root-jar") 24 | tempJarDir.deleteRecursively() 25 | tempJarDir.mkdirs() 26 | 27 | val optimizer = JarOptimizer(tempJarDir, it, extension.get(), project) 28 | optimizer.unpack() 29 | optimizer.optimize() 30 | 31 | if (extension.get().keepOriginal.get()) { 32 | optimizer.repackTo(it.resolveAndMakeSibling(it.nameWithoutExtension + "-optimized.jar")) 33 | } else { 34 | optimizer.repackTo(it) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/tasks/UnpackOxipngTask.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.tasks 2 | 3 | import io.github.p03w.machete.core.OxipngManager 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.tasks.TaskAction 6 | import java.io.File 7 | 8 | abstract class UnpackOxipngTask : DefaultTask() { 9 | @TaskAction 10 | fun unpackOxipng() { 11 | val buildDir = File(project.buildDir.absolutePath).resolve("machete-build") 12 | buildDir.mkdirs() 13 | 14 | OxipngManager.tempDir = buildDir 15 | OxipngManager.unpackOxipng(project.name) 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/util/FileUtil.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.util 2 | 3 | import java.io.File 4 | import java.nio.file.Files 5 | import java.nio.file.StandardCopyOption 6 | import java.util.zip.ZipInputStream 7 | 8 | /** 9 | * Unzips a zip file to a directory 10 | */ 11 | fun ZipInputStream.unzip(outputDir: File) { 12 | var entry = nextEntry 13 | while (entry != null) { 14 | val resolvedPath = outputDir.resolve(entry.name).normalize().toPath() 15 | if (!resolvedPath.startsWith(outputDir.path)) { 16 | throw RuntimeException("Zip slip somehow, don't do that: " + entry.name) 17 | } 18 | 19 | if (entry.isDirectory) { 20 | Files.createDirectories(resolvedPath) 21 | } else { 22 | Files.createDirectories(resolvedPath.parent) 23 | Files.copy(this, resolvedPath, StandardCopyOption.REPLACE_EXISTING) 24 | } 25 | 26 | entry = nextEntry 27 | } 28 | } 29 | 30 | /** 31 | * Resolves a sibling directory, calls .mkdirs, and returns the new directory 32 | */ 33 | fun File.resolveAndMakeSiblingDir(relativeFile: String): File { 34 | val res = resolveSibling(relativeFile) 35 | res.mkdirs() 36 | return res 37 | } 38 | 39 | /** 40 | * Resolves a directory, calls .mkdirs, and returns the new directory 41 | */ 42 | fun File.resolveAndMakeDir(relativeFile: String): File { 43 | val res = resolve(relativeFile) 44 | res.mkdirs() 45 | return res 46 | } 47 | 48 | /** 49 | * Resolves a sibling file, creates parent dirs, calls .createNewFile, and returns the new file 50 | */ 51 | fun File.resolveAndMakeSibling(relativeFile: String): File { 52 | val res = resolveSibling(relativeFile) 53 | res.mkdirs() 54 | res.createNewFile() 55 | return res 56 | } 57 | 58 | /** 59 | * Resolves a file, creates parent dirs, calls .createNewFile, and returns the new file 60 | */ 61 | fun File.resolveAndMake(relativeFile: String): File { 62 | val res = resolve(relativeFile) 63 | res.mkdirs() 64 | res.createNewFile() 65 | return res 66 | } 67 | 68 | /** 69 | * Walks the entire sub file tree bottom-up, and runs [action] on any file with the specified extension 70 | */ 71 | inline fun File.allWithExtension( 72 | ext: String, 73 | extra: List = listOf(), 74 | ignoreList: List, 75 | action: (File) -> Unit 76 | ) { 77 | walkBottomUp() 78 | .toList() 79 | .filter { 80 | (it.extension == ext || extra.any {extra -> it.name.contains(extra)}) && !ignoreList.contains(it.name) 81 | } 82 | .forEach(action) 83 | } 84 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/util/KnownGoodTasks.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.util 2 | 3 | // Tasks that are known to produce .jar files that may be of actual interest to end users 4 | val knownGoodTasks = setOf( 5 | // java-base 6 | "jar", 7 | 8 | // fabric-loom 9 | "remapJar", 10 | 11 | // shadow 12 | "shadowJar" 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/util/StringUtil.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.util 2 | 3 | import java.util.* 4 | 5 | fun String.capital() = replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } 6 | 7 | fun Regex.matches(char: Char) = matches(char.toString()) 8 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/util/TryCatchAll.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.util 2 | 3 | inline fun tryCatchAll(vararg actions: () -> Unit, handler: (Throwable) -> Unit) { 4 | actions.forEach { 5 | try { 6 | it() 7 | } catch (err: Throwable) { 8 | handler(err) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/p03w/machete/util/invokeProcess.kt: -------------------------------------------------------------------------------- 1 | package io.github.p03w.machete.util 2 | 3 | /** 4 | * A utility method that starts a process with output and error redirected to the current 5 | * stdout and stderr before waiting for its termination 6 | */ 7 | fun invokeProcess(vararg args: String) { 8 | val process = ProcessBuilder(*args).also { 9 | it.redirectOutput(ProcessBuilder.Redirect.INHERIT) 10 | it.redirectError(ProcessBuilder.Redirect.INHERIT) 11 | }.start() 12 | process.waitFor() 13 | } 14 | 15 | /** 16 | * A utility method that checks if a process can execute successfully by running it and checking for errors 17 | */ 18 | fun doesProcessRun(vararg args: String): Boolean { 19 | return try { 20 | val process = ProcessBuilder(*args).start() 21 | process.waitFor() 22 | true 23 | } catch (any: Throwable) { 24 | false 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/oxipng/OXIPNG_LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Joshua Holmer 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/main/resources/oxipng/oxipng-apple: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverAndro/Machete/7251af92cf07fdd4be836d0ecad0bed46f77609e/src/main/resources/oxipng/oxipng-apple -------------------------------------------------------------------------------- /src/main/resources/oxipng/oxipng-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverAndro/Machete/7251af92cf07fdd4be836d0ecad0bed46f77609e/src/main/resources/oxipng/oxipng-linux -------------------------------------------------------------------------------- /src/main/resources/oxipng/oxipng-windows.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilverAndro/Machete/7251af92cf07fdd4be836d0ecad0bed46f77609e/src/main/resources/oxipng/oxipng-windows.exe --------------------------------------------------------------------------------