From f246d2882c09c47099abf74a158fc0452c44952e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=CC=88rg=20Prante?= Date: Sat, 19 Feb 2022 23:05:50 +0100 Subject: [PATCH] add shadow, jflex, jacc plugins, update to gradle 7.4 --- LICENSE.txt | 202 ++ NOTICE.txt | 0 build.gradle | 2 +- gradle-plugin-asciidoctor/NOTICE.txt | 10 + gradle-plugin-asciidoctor/build.gradle | 10 +- gradle-plugin-asciidoctor/gradle.properties | 2 +- .../asciidoctor/AsciidoctorExtension.groovy | 2 +- gradle-plugin-docker/build.gradle | 16 +- gradle-plugin-git/LICENSE.txt | 202 ++ gradle-plugin-git/build.gradle | 22 +- gradle-plugin-jacc/LICENSE.txt | 202 ++ gradle-plugin-jacc/README.md | 52 + gradle-plugin-jacc/build.gradle | 41 + gradle-plugin-jacc/gradle.properties | 1 + .../xbib/gradle/plugin/jacc/JaccPlugin.groovy | 80 + .../xbib/gradle/plugin/jacc/JaccTask.groovy | 33 + .../gradle/plugin/jacc/JaccPluginTest.groovy | 65 + .../src/test/jacc/simpleCalc.jacc | 104 ++ gradle-plugin-jflex/LICENSE.txt | 202 ++ gradle-plugin-jflex/README.md | 79 + gradle-plugin-jflex/build.gradle | 41 + gradle-plugin-jflex/gradle.properties | 1 + .../gradle/plugin/jflex/JFlexExtension.groovy | 67 + .../gradle/plugin/jflex/JFlexPlugin.groovy | 90 + .../xbib/gradle/plugin/jflex/JFlexTask.groovy | 91 + .../gradle/plugin/test/JFlexPluginTest.groovy | 194 ++ .../org/xbib/gradle/plugin/test/Test.jflex | 43 + gradle-plugin-rpm/NOTICE.txt | 8 + gradle-plugin-rpm/build.gradle | 14 +- .../org/xbib/gradle/plugin/RpmPlugin.groovy | 6 - gradle-plugin-shadow/build.gradle | 45 + gradle-plugin-shadow/gradle.properties | 1 + .../src/docs/asciidoc/00-intro.adoc | 28 + .../src/docs/asciidoc/01-getting-started.adoc | 96 + .../src/docs/asciidoc/10-configuring.adoc | 106 ++ .../docs/asciidoc/11-filtering-contents.adoc | 27 + .../asciidoc/12-controlling-dependencies.adoc | 118 ++ .../docs/asciidoc/13-controlling-merging.adoc | 151 ++ .../docs/asciidoc/14-package-relocation.adoc | 72 + .../src/docs/asciidoc/15-minimizing.adoc | 25 + .../docs/asciidoc/16-reproducible-builds.adoc | 16 + .../src/docs/asciidoc/20-custom-tasks.adoc | 20 + .../src/docs/asciidoc/40-publishing.adoc | 69 + .../asciidoc/50-multi-project-builds.adoc | 21 + .../docs/asciidoc/60-shadowing-plugins.adoc | 49 + .../src/docs/asciidoc/80-examples.adoc | 0 .../src/docs/asciidoc/99-about.adoc | 23 + .../src/docs/asciidoc/index.adoc | 32 + .../plugin/shadow/ShadowBasePlugin.groovy | 21 + .../plugin/shadow/ShadowExtension.groovy | 38 + .../plugin/shadow/ShadowJavaPlugin.groovy | 58 + .../gradle/plugin/shadow/ShadowPlugin.groovy | 31 + .../gradle/plugin/shadow/ShadowStats.groovy | 81 + .../shadow/impl/RelocatorRemapper.groovy | 94 + .../plugin/shadow/internal/Clazz.groovy | 85 + .../plugin/shadow/internal/Clazzpath.groovy | 228 +++ .../shadow/internal/ClazzpathUnit.groovy | 40 + .../internal/DefaultDependencyFilter.groovy | 126 ++ .../internal/DefaultZipCompressor.groovy | 28 + .../internal/DependenciesClassRemapper.groovy | 144 ++ .../shadow/internal/DependencyFilter.groovy | 76 + .../shadow/internal/DependencyUtils.groovy | 44 + .../shadow/internal/ServiceStream.groovy | 0 .../internal/StringBuilderWriter.groovy | 142 ++ .../shadow/internal/UnusedTracker.groovy | 44 + .../plugin/shadow/internal/Utils.groovy | 629 +++++++ .../shadow/internal/ZipCompressor.groovy | 11 + .../relocation/RelocateClassContext.groovy | 14 + .../relocation/RelocatePathContext.groovy | 13 + .../plugin/shadow/relocation/Relocator.groovy | 17 + .../shadow/relocation/SimpleRelocator.groovy | 156 ++ .../tasks/ConfigureShadowRelocation.groovy | 50 + .../tasks/DefaultInheritManifest.groovy | 99 + .../shadow/tasks/InheritManifest.groovy | 10 + .../shadow/tasks/ShadowCopyAction.groovy | 503 +++++ .../plugin/shadow/tasks/ShadowJar.groovy | 398 ++++ .../plugin/shadow/tasks/ShadowSpec.groovy | 51 + .../ApacheLicenseResourceTransformer.groovy | 35 + .../ApacheNoticeResourceTransformer.groovy | 184 ++ .../transformers/AppendingTransformer.groovy | 43 + .../DontIncludeResourceTransformer.groovy | 37 + .../GroovyExtensionModuleTransformer.groovy | 89 + .../IncludeResourceTransformer.groovy | 42 + .../ManifestResourceTransformer.groovy | 78 + .../PropertiesFileTransformer.groovy | 220 +++ .../ServiceFileTransformer.groovy | 145 ++ .../shadow/transformers/Transformer.groovy | 18 + .../transformers/TransformerContext.groovy | 24 + .../plugin/shadow/internal/ServiceStream.java | 28 + .../shadow/zip/AbstractUnicodeExtraField.java | 159 ++ .../plugin/shadow/zip/AsiExtraField.java | 299 +++ .../CentralDirectoryParsingZipExtraField.java | 20 + .../plugin/shadow/zip/ExtraFieldUtils.java | 272 +++ .../shadow/zip/FallbackZipEncoding.java | 72 + .../plugin/shadow/zip/GeneralPurposeBit.java | 193 ++ .../gradle/plugin/shadow/zip/JarMarker.java | 87 + .../plugin/shadow/zip/NioZipEncoding.java | 100 + .../shadow/zip/Simple8BitZipEncoding.java | 250 +++ .../shadow/zip/UnicodeCommentExtraField.java | 51 + .../shadow/zip/UnicodePathExtraField.java | 48 + .../gradle/plugin/shadow/zip/UnixStat.java | 38 + .../shadow/zip/UnparseableExtraFieldData.java | 94 + .../shadow/zip/UnrecognizedExtraField.java | 130 ++ .../zip/UnsupportedZipFeatureException.java | 76 + .../Zip64ExtendedInformationExtraField.java | 323 ++++ .../gradle/plugin/shadow/zip/Zip64Mode.java | 27 + .../shadow/zip/Zip64RequiredException.java | 30 + .../plugin/shadow/zip/ZipConstants.java | 43 + .../shadow/zip/ZipEightByteInteger.java | 210 +++ .../gradle/plugin/shadow/zip/ZipEncoding.java | 65 + .../plugin/shadow/zip/ZipEncodingHelper.java | 232 +++ .../gradle/plugin/shadow/zip/ZipEntry.java | 760 ++++++++ .../plugin/shadow/zip/ZipExtraField.java | 60 + .../gradle/plugin/shadow/zip/ZipFile.java | 1003 ++++++++++ .../gradle/plugin/shadow/zip/ZipLong.java | 171 ++ .../plugin/shadow/zip/ZipOutputStream.java | 1646 +++++++++++++++++ .../gradle/plugin/shadow/zip/ZipShort.java | 139 ++ .../gradle/plugin/shadow/zip/ZipUtil.java | 252 +++ .../shadow/ConfigureShadowPluginSpec.groovy | 43 + .../gradle/plugin/shadow/FilteringSpec.groovy | 378 ++++ .../plugin/shadow/PublishingSpec.groovy | 78 + .../plugin/shadow/RelocationSpec.groovy | 323 ++++ .../plugin/shadow/ShadowPluginSpec.groovy | 626 +++++++ .../plugin/shadow/TransformerSpec.groovy | 636 +++++++ .../shadow/internal/ClazzpathTest.groovy | 176 ++ .../internal/DependencyUtilsTest.groovy | 32 + .../plugin/shadow/internal/UtilsTest.groovy | 46 + .../SimpleRelocatorParameterTest.groovy | 30 + .../relocation/SimpleRelocatorTest.groovy | 137 ++ ...pacheLicenseResourceTransformerTest.groovy | 39 + ...ceResourceTransformerParameterTests.groovy | 62 + ...ApacheNoticeResourceTransformerTest.groovy | 37 + .../AppendingTransformerTest.groovy | 38 + .../PropertiesFileTransformerSpec.groovy | 126 ++ .../ServiceFileTransformerSpec.groovy | 63 + .../TransformerSpecSupport.groovy | 55 + .../TransformerTestSupport.groovy | 13 + .../plugin/shadow/util/AppendableJar.groovy | 25 + .../util/AppendableMavenFileModule.groovy | 70 + .../util/AppendableMavenFileRepository.groovy | 15 + .../plugin/shadow/util/JarBuilder.groovy | 47 + .../shadow/util/PluginSpecification.groovy | 136 ++ .../plugin/shadow/util/file/ExecOutput.groovy | 13 + .../plugin/shadow/util/file/Results.groovy | 58 + .../util/file/TestDirectoryProvider.java | 17 + .../plugin/shadow/util/file/TestFile.java | 310 ++++ .../shadow/util/file/TestFileHelper.groovy | 218 +++ .../file/TestNameTestDirectoryProvider.java | 134 ++ .../util/file/TestWorkspaceBuilder.groovy | 39 + .../shadow/util/repo/AbstractModule.groovy | 125 ++ .../repo/maven/AbstractMavenModule.groovy | 330 ++++ .../repo/maven/DefaultMavenMetaData.groovy | 33 + .../util/repo/maven/MavenDependency.groovy | 19 + .../util/repo/maven/MavenFileModule.groovy | 55 + .../repo/maven/MavenFileRepository.groovy | 23 + .../util/repo/maven/MavenMetaData.groovy | 6 + .../shadow/util/repo/maven/MavenModule.groovy | 45 + .../shadow/util/repo/maven/MavenPom.groovy | 42 + .../util/repo/maven/MavenRepository.groovy | 12 + .../shadow/util/repo/maven/MavenScope.groovy | 44 + .../src/test/jars/plexus-utils-1.4.1.jar | Bin 0 -> 188648 bytes .../test/jars/test-artifact-1.0-SNAPSHOT.jar | Bin 0 -> 3115 bytes .../test/jars/test-project-1.0-SNAPSHOT.jar | Bin 0 -> 3906 bytes .../src/test/resources/asm-6.0_BETA.jar | Bin 0 -> 56452 bytes .../asm-6.0_BETA/META-INF/MANIFEST.MF | 17 + .../src/test/resources/components-1.xml | 48 + .../src/test/resources/components-2.xml | 48 + .../test/resources/components-expected.xml | 55 + .../src/test/resources/jar1-missing.jar | Bin 0 -> 87105 bytes .../jar1-missing/META-INF/LICENSE.txt | 203 ++ .../jar1-missing/META-INF/MANIFEST.MF | 3 + .../jar1-missing/META-INF/NOTICE.txt | 6 + .../commons-io/commons-io/pom.properties | 5 + .../maven/commons-io/commons-io/pom.xml | 353 ++++ .../src/test/resources/jar1.jar | Bin 0 -> 87776 bytes .../test/resources/jar1/META-INF/LICENSE.txt | 203 ++ .../test/resources/jar1/META-INF/MANIFEST.MF | 15 + .../test/resources/jar1/META-INF/NOTICE.txt | 6 + .../commons-io/commons-io/pom.properties | 5 + .../maven/commons-io/commons-io/pom.xml | 353 ++++ .../src/test/resources/jar2.jar | Bin 0 -> 143847 bytes .../test/resources/jar2/META-INF/LICENSE.txt | 201 ++ .../test/resources/jar2/META-INF/MANIFEST.MF | 44 + .../test/resources/jar2/META-INF/NOTICE.txt | 15 + .../commons-compress/pom.properties | 5 + .../commons-compress/pom.xml | 229 +++ .../src/test/resources/jar3using1.jar | Bin 0 -> 1189 bytes .../resources/jar3using1/META-INF/MANIFEST.MF | 3 + .../src/test/resources/jar3using1/Main.java | 13 + .../src/test/resources/jar3using1/build.sh | 4 + .../src/test/resources/junit-3.8.2.jar | Bin 0 -> 120640 bytes .../resources/test-artifact-1.0-SNAPSHOT.jar | Bin 0 -> 3115 bytes .../resources/test-project-1.0-SNAPSHOT.jar | Bin 0 -> 3906 bytes gradle.properties | 10 - gradle/compile/groovy.gradle | 8 +- gradle/compile/java.gradle | 18 +- gradle/documentation/asciidoc.gradle | 1 - gradle/ide/idea.gradle | 5 - gradle/test/junit5.gradle | 11 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 35 + 202 files changed, 20381 insertions(+), 102 deletions(-) create mode 100644 LICENSE.txt create mode 100644 NOTICE.txt create mode 100644 gradle-plugin-asciidoctor/NOTICE.txt create mode 100644 gradle-plugin-git/LICENSE.txt create mode 100644 gradle-plugin-jacc/LICENSE.txt create mode 100755 gradle-plugin-jacc/README.md create mode 100755 gradle-plugin-jacc/build.gradle create mode 100644 gradle-plugin-jacc/gradle.properties create mode 100644 gradle-plugin-jacc/src/main/groovy/org/xbib/gradle/plugin/jacc/JaccPlugin.groovy create mode 100644 gradle-plugin-jacc/src/main/groovy/org/xbib/gradle/plugin/jacc/JaccTask.groovy create mode 100644 gradle-plugin-jacc/src/test/groovy/org/xbib/gradle/plugin/jacc/JaccPluginTest.groovy create mode 100644 gradle-plugin-jacc/src/test/jacc/simpleCalc.jacc create mode 100644 gradle-plugin-jflex/LICENSE.txt create mode 100755 gradle-plugin-jflex/README.md create mode 100755 gradle-plugin-jflex/build.gradle create mode 100644 gradle-plugin-jflex/gradle.properties create mode 100644 gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexExtension.groovy create mode 100755 gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexPlugin.groovy create mode 100755 gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexTask.groovy create mode 100644 gradle-plugin-jflex/src/test/groovy/org/xbib/gradle/plugin/test/JFlexPluginTest.groovy create mode 100644 gradle-plugin-jflex/src/test/jflex/org/xbib/gradle/plugin/test/Test.jflex create mode 100644 gradle-plugin-rpm/NOTICE.txt create mode 100644 gradle-plugin-shadow/build.gradle create mode 100644 gradle-plugin-shadow/gradle.properties create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/00-intro.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/01-getting-started.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/10-configuring.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/11-filtering-contents.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/12-controlling-dependencies.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/13-controlling-merging.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/14-package-relocation.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/15-minimizing.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/16-reproducible-builds.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/20-custom-tasks.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/40-publishing.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/50-multi-project-builds.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/60-shadowing-plugins.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/80-examples.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/99-about.adoc create mode 100644 gradle-plugin-shadow/src/docs/asciidoc/index.adoc create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowBasePlugin.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowExtension.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowJavaPlugin.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowPlugin.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowStats.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/impl/RelocatorRemapper.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Clazz.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Clazzpath.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ClazzpathUnit.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DefaultDependencyFilter.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DefaultZipCompressor.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependenciesClassRemapper.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyFilter.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyUtils.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ServiceStream.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/StringBuilderWriter.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/UnusedTracker.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Utils.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ZipCompressor.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/RelocateClassContext.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/RelocatePathContext.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/Relocator.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocator.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ConfigureShadowRelocation.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/DefaultInheritManifest.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/InheritManifest.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowCopyAction.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowJar.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowSpec.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheLicenseResourceTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/AppendingTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/DontIncludeResourceTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/GroovyExtensionModuleTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/IncludeResourceTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ManifestResourceTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/PropertiesFileTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ServiceFileTransformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/Transformer.groovy create mode 100644 gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerContext.groovy create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/internal/ServiceStream.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/AbstractUnicodeExtraField.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/AsiExtraField.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/CentralDirectoryParsingZipExtraField.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ExtraFieldUtils.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/FallbackZipEncoding.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/GeneralPurposeBit.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/JarMarker.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/NioZipEncoding.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Simple8BitZipEncoding.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnicodeCommentExtraField.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnicodePathExtraField.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnixStat.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnparseableExtraFieldData.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnrecognizedExtraField.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnsupportedZipFeatureException.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64ExtendedInformationExtraField.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64Mode.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64RequiredException.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipConstants.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEightByteInteger.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEncoding.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEncodingHelper.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEntry.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipExtraField.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipFile.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipLong.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipOutputStream.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipShort.java create mode 100644 gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipUtil.java create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/ConfigureShadowPluginSpec.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/FilteringSpec.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/PublishingSpec.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/RelocationSpec.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/ShadowPluginSpec.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/TransformerSpec.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/ClazzpathTest.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyUtilsTest.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/UtilsTest.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocatorParameterTest.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocatorTest.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheLicenseResourceTransformerTest.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformerParameterTests.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformerTest.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/AppendingTransformerTest.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/PropertiesFileTransformerSpec.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ServiceFileTransformerSpec.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerSpecSupport.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerTestSupport.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableJar.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableMavenFileModule.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableMavenFileRepository.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/JarBuilder.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/PluginSpecification.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/ExecOutput.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/Results.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestDirectoryProvider.java create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestFile.java create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestFileHelper.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestNameTestDirectoryProvider.java create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestWorkspaceBuilder.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/AbstractModule.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/AbstractMavenModule.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/DefaultMavenMetaData.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenDependency.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenFileModule.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenFileRepository.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenMetaData.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenModule.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenPom.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenRepository.groovy create mode 100644 gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenScope.groovy create mode 100644 gradle-plugin-shadow/src/test/jars/plexus-utils-1.4.1.jar create mode 100644 gradle-plugin-shadow/src/test/jars/test-artifact-1.0-SNAPSHOT.jar create mode 100644 gradle-plugin-shadow/src/test/jars/test-project-1.0-SNAPSHOT.jar create mode 100644 gradle-plugin-shadow/src/test/resources/asm-6.0_BETA.jar create mode 100644 gradle-plugin-shadow/src/test/resources/asm-6.0_BETA/META-INF/MANIFEST.MF create mode 100644 gradle-plugin-shadow/src/test/resources/components-1.xml create mode 100644 gradle-plugin-shadow/src/test/resources/components-2.xml create mode 100644 gradle-plugin-shadow/src/test/resources/components-expected.xml create mode 100644 gradle-plugin-shadow/src/test/resources/jar1-missing.jar create mode 100644 gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/LICENSE.txt create mode 100644 gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/MANIFEST.MF create mode 100644 gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/NOTICE.txt create mode 100644 gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/maven/commons-io/commons-io/pom.properties create mode 100644 gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/maven/commons-io/commons-io/pom.xml create mode 100644 gradle-plugin-shadow/src/test/resources/jar1.jar create mode 100644 gradle-plugin-shadow/src/test/resources/jar1/META-INF/LICENSE.txt create mode 100644 gradle-plugin-shadow/src/test/resources/jar1/META-INF/MANIFEST.MF create mode 100644 gradle-plugin-shadow/src/test/resources/jar1/META-INF/NOTICE.txt create mode 100644 gradle-plugin-shadow/src/test/resources/jar1/META-INF/maven/commons-io/commons-io/pom.properties create mode 100644 gradle-plugin-shadow/src/test/resources/jar1/META-INF/maven/commons-io/commons-io/pom.xml create mode 100644 gradle-plugin-shadow/src/test/resources/jar2.jar create mode 100644 gradle-plugin-shadow/src/test/resources/jar2/META-INF/LICENSE.txt create mode 100644 gradle-plugin-shadow/src/test/resources/jar2/META-INF/MANIFEST.MF create mode 100644 gradle-plugin-shadow/src/test/resources/jar2/META-INF/NOTICE.txt create mode 100644 gradle-plugin-shadow/src/test/resources/jar2/META-INF/maven/org.apache.commons/commons-compress/pom.properties create mode 100644 gradle-plugin-shadow/src/test/resources/jar2/META-INF/maven/org.apache.commons/commons-compress/pom.xml create mode 100644 gradle-plugin-shadow/src/test/resources/jar3using1.jar create mode 100644 gradle-plugin-shadow/src/test/resources/jar3using1/META-INF/MANIFEST.MF create mode 100644 gradle-plugin-shadow/src/test/resources/jar3using1/Main.java create mode 100644 gradle-plugin-shadow/src/test/resources/jar3using1/build.sh create mode 100644 gradle-plugin-shadow/src/test/resources/junit-3.8.2.jar create mode 100644 gradle-plugin-shadow/src/test/resources/test-artifact-1.0-SNAPSHOT.jar create mode 100644 gradle-plugin-shadow/src/test/resources/test-project-1.0-SNAPSHOT.jar diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..e69de29 diff --git a/build.gradle b/build.gradle index c42a779..228883d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ wrapper { - gradleVersion = "${project.property('gradle.wrapper.version')}" + gradleVersion = libs.versions.gradle.get() distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle-plugin-asciidoctor/NOTICE.txt b/gradle-plugin-asciidoctor/NOTICE.txt new file mode 100644 index 0000000..e5d5b73 --- /dev/null +++ b/gradle-plugin-asciidoctor/NOTICE.txt @@ -0,0 +1,10 @@ + +This work is based on an old (before June 2018) 1.5.6 version of + +https://github.com/asciidoctor/asciidoctor-gradle-plugin + +You can find remnants in the official code under org.asciidoctor.gradle.compat, when it was created with + +https://github.com/ysb33r/asciidoctor-gradle-plugin/commit/1ad29b2eba915f7bce3cb004e3e9582578c8ac72 + +License: Apache 2.0 diff --git a/gradle-plugin-asciidoctor/build.gradle b/gradle-plugin-asciidoctor/build.gradle index 2eb724b..2bb28b3 100644 --- a/gradle-plugin-asciidoctor/build.gradle +++ b/gradle-plugin-asciidoctor/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java-gradle-plugin' - id 'com.gradle.plugin-publish' version '0.18.0' + alias(libs.plugins.publish) } apply plugin: 'java-gradle-plugin' @@ -10,10 +10,10 @@ apply from: rootProject.file('gradle/compile/groovy.gradle') dependencies { api gradleApi() - implementation "org.asciidoctor:asciidoctorj:${project.property('asciidoctorj.version')}" - implementation "org.jruby:jruby:${project.property('jruby.version')}" - testImplementation "org.spockframework:spock-core:${project.property('spock.version')}" - testImplementation "org.jsoup:jsoup:${project.property('jsoup.version')}" + implementation libs.asciidoctorj + implementation libs.jruby + testImplementation libs.spock.core + testImplementation libs.jsoup } gradlePlugin { diff --git a/gradle-plugin-asciidoctor/gradle.properties b/gradle-plugin-asciidoctor/gradle.properties index b2cd265..6449a81 100644 --- a/gradle-plugin-asciidoctor/gradle.properties +++ b/gradle-plugin-asciidoctor/gradle.properties @@ -1 +1 @@ -version = 2.5.2.0 +version = 2.5.2.1 diff --git a/gradle-plugin-asciidoctor/src/main/groovy/org/xbib/gradle/plugin/asciidoctor/AsciidoctorExtension.groovy b/gradle-plugin-asciidoctor/src/main/groovy/org/xbib/gradle/plugin/asciidoctor/AsciidoctorExtension.groovy index 190dd6b..cf033f1 100644 --- a/gradle-plugin-asciidoctor/src/main/groovy/org/xbib/gradle/plugin/asciidoctor/AsciidoctorExtension.groovy +++ b/gradle-plugin-asciidoctor/src/main/groovy/org/xbib/gradle/plugin/asciidoctor/AsciidoctorExtension.groovy @@ -4,7 +4,7 @@ import org.gradle.api.Project class AsciidoctorExtension { - String version = '2.5.3' + String version = '2.5.2' boolean addDefaultRepositories = true diff --git a/gradle-plugin-docker/build.gradle b/gradle-plugin-docker/build.gradle index aa8c30a..04bcdc0 100644 --- a/gradle-plugin-docker/build.gradle +++ b/gradle-plugin-docker/build.gradle @@ -1,28 +1,18 @@ plugins { id 'java-gradle-plugin' - id 'groovy' - id 'com.gradle.plugin-publish' version '0.18.0' + alias(libs.plugins.publish) } -apply plugin: 'groovy' apply plugin: 'java-gradle-plugin' apply plugin: 'com.gradle.plugin-publish' +apply from: rootProject.file('gradle/compile/groovy.gradle') + dependencies { api gradleApi() testImplementation gradleTestKit() } -compileGroovy { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -compileTestGroovy { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - gradlePlugin { plugins { dockerPlugin { diff --git a/gradle-plugin-git/LICENSE.txt b/gradle-plugin-git/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/gradle-plugin-git/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/gradle-plugin-git/build.gradle b/gradle-plugin-git/build.gradle index 43de1f4..5126cdf 100644 --- a/gradle-plugin-git/build.gradle +++ b/gradle-plugin-git/build.gradle @@ -1,29 +1,19 @@ plugins { id 'java-gradle-plugin' - id 'groovy' - id 'com.gradle.plugin-publish' version '0.18.0' + alias(libs.plugins.publish) } -apply plugin: 'groovy' apply plugin: 'java-gradle-plugin' apply plugin: 'com.gradle.plugin-publish' +apply from: rootProject.file('gradle/compile/groovy.gradle') + dependencies { api gradleApi() - api "org.xbib.groovy:groovy-git:${project.property('groovy-git.version')}" + api libs.groovy.git testImplementation gradleTestKit() - testImplementation "org.spockframework:spock-core:${project.property('spock.version')}" - testImplementation "junit:junit:${project.property('junit4.version')}" -} - -compileGroovy { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -compileTestGroovy { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + testImplementation libs.spock.core + testImplementation libs.junit4 } gradlePlugin { diff --git a/gradle-plugin-jacc/LICENSE.txt b/gradle-plugin-jacc/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/gradle-plugin-jacc/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/gradle-plugin-jacc/README.md b/gradle-plugin-jacc/README.md new file mode 100755 index 0000000..15188ca --- /dev/null +++ b/gradle-plugin-jacc/README.md @@ -0,0 +1,52 @@ +# gradle-plugin-jacc + +A Gradle plugin for [Jacc](http://web.cecs.pdx.edu/~mpj/jacc/) + +## Usage + + plugins { + id 'org.xbib.gradle.plugin.jacc' + } + + apply plugin: 'org.xbib.gradle.plugin.jacc' + +Gradle will look for your jacc files in the source sets you specified. +By default, it looks with the pattern `**/*.jacc` under `src/main/jacc` +and `src/test/jacc`. + +You can set up the source sets like this: + + sourceSets { + main { + jacc { + srcDir "src/main/jacc" + } + java { + srcDir "build/my-generated-sources/jacc" + } + } + } + +The lastJava `srcDir` definition will be used as the base for the Jacc target path. +If not given, the Jacc target path for generated Java source follows the pattern: + +`${project.buildDir}/generated/sources/jacc` + +The Jacc target path will be added automaticlly to the java compile task source directory +of the source set. + +# License + +Copyright (C) 2015-2020 Jörg Prante + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/gradle-plugin-jacc/build.gradle b/gradle-plugin-jacc/build.gradle new file mode 100755 index 0000000..abfd039 --- /dev/null +++ b/gradle-plugin-jacc/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'java-gradle-plugin' + alias(libs.plugins.publish) +} + +apply plugin: 'java-gradle-plugin' +apply plugin: 'com.gradle.plugin-publish' + +apply from: rootProject.file('gradle/compile/groovy.gradle') +apply from: rootProject.file('gradle/test/junit5.gradle') + +dependencies { + api gradleApi() + implementation libs.jacc + testImplementation gradleTestKit() +} + +gradlePlugin { + plugins { + jaccPlugin { + id = 'org.xbib.gradle.plugin.jacc' + implementationClass = 'org.xbib.gradle.plugin.jacc.JaccPlugin' + } + } +} + +if (project.hasProperty('gradle.publish.key')) { + pluginBundle { + website = scmUrl + vcsUrl = scmUrl + plugins { + jaccPlugin { + id = 'org.xbib.gradle.plugin.jacc' + version = project.version + description = 'Gradle Jacc plugin' + displayName = 'Gradle Jacc plugin' + tags = ['jacc'] + } + } + } +} diff --git a/gradle-plugin-jacc/gradle.properties b/gradle-plugin-jacc/gradle.properties new file mode 100644 index 0000000..749ca75 --- /dev/null +++ b/gradle-plugin-jacc/gradle.properties @@ -0,0 +1 @@ +version = 1.4.0 diff --git a/gradle-plugin-jacc/src/main/groovy/org/xbib/gradle/plugin/jacc/JaccPlugin.groovy b/gradle-plugin-jacc/src/main/groovy/org/xbib/gradle/plugin/jacc/JaccPlugin.groovy new file mode 100644 index 0000000..8aa6bad --- /dev/null +++ b/gradle-plugin-jacc/src/main/groovy/org/xbib/gradle/plugin/jacc/JaccPlugin.groovy @@ -0,0 +1,80 @@ +package org.xbib.gradle.plugin.jacc + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.TaskProvider + +class JaccPlugin implements Plugin { + + private static final Logger logger = Logging.getLogger(JaccPlugin) + + @Override + void apply(Project project) { + project.with { + apply plugin: 'java-library' + addSourceSetExtensions(project) + } + project.afterEvaluate { + addTasks(project) + } + } + + + private static void addSourceSetExtensions(Project project) { + project.sourceSets.all { SourceSet sourceSet -> + createSourceSetExtension(project, sourceSet) + createConfiguration(project, sourceSet) + } + } + + private static void createSourceSetExtension(Project project, SourceSet sourceSet) { + String name = sourceSet.name + SourceDirectorySet sourceDirectorySet = project.objects.sourceDirectorySet(name, "${name} Jacc source") + sourceSet.extensions.add('jacc', sourceDirectorySet) + sourceDirectorySet.srcDir("src/${name}/jacc") + sourceDirectorySet.include("**/*.jacc") + } + + + private static void createConfiguration(Project project, SourceSet sourceSet) { + String configName = sourceSet.name + 'Jacc' + if (project.configurations.findByName(configName) == null) { + logger.info "create configuration ${configName}" + project.configurations.create(configName) { + visible = false + transitive = true + extendsFrom = [] + } + } + } + + private static void addTasks(Project project) { + project.sourceSets.all { SourceSet sourceSet -> + addTaskForSourceSet(project, sourceSet) + } + } + + private static void addTaskForSourceSet(Project project, SourceSet sourceSet) { + String taskName = sourceSet.getTaskName('generate', 'jacc') + SourceDirectorySet sourceDirectorySet = sourceSet.extensions.getByName('jacc') as SourceDirectorySet + File targetFile = sourceSet.java && sourceSet.java.srcDirs ? sourceSet.java.srcDirs.last() : + project.file("${project.buildDir}/generated/sources/${sourceSet.name}") + if (sourceDirectorySet.asList()) { + TaskProvider taskProvider = project.tasks.register(taskName, JaccTask) { + group = 'jacc' + description = 'Generates code from Jacc files in ' + sourceSet.name + source = sourceDirectorySet.asList() + target = targetFile + } + logger.info "created ${taskName} for sources ${sourceDirectorySet.asList()} and target ${targetFile}" + project.tasks.findByName(sourceSet.compileJavaTaskName).dependsOn taskProvider + if (sourceSet.java && sourceSet.java.srcDirs) { + sourceSet.java.srcDirs += targetFile + } + } + } +} diff --git a/gradle-plugin-jacc/src/main/groovy/org/xbib/gradle/plugin/jacc/JaccTask.groovy b/gradle-plugin-jacc/src/main/groovy/org/xbib/gradle/plugin/jacc/JaccTask.groovy new file mode 100644 index 0000000..e44523a --- /dev/null +++ b/gradle-plugin-jacc/src/main/groovy/org/xbib/gradle/plugin/jacc/JaccTask.groovy @@ -0,0 +1,33 @@ +package org.xbib.gradle.plugin.jacc + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import org.xbib.jacc.Jacc + +class JaccTask extends DefaultTask { + + @InputFiles + Iterable source + + @OutputDirectory + File target + + @TaskAction + void generateAndTransformJacc() throws Exception { + source.each { file -> + String pkg = getPackageName(file) + File fullTarget = new File(target, pkg.replace('.','/')) + project.mkdir(fullTarget) + Jacc.main([file.absolutePath, '-d', fullTarget] as String[]) + } + } + + static String getPackageName(File file) { + String string = file.readLines().find { line -> + line.startsWith('package') + } + return string == null ? '' : string.substring(8, string.length() - 1) + } +} diff --git a/gradle-plugin-jacc/src/test/groovy/org/xbib/gradle/plugin/jacc/JaccPluginTest.groovy b/gradle-plugin-jacc/src/test/groovy/org/xbib/gradle/plugin/jacc/JaccPluginTest.groovy new file mode 100644 index 0000000..7a91ceb --- /dev/null +++ b/gradle-plugin-jacc/src/test/groovy/org/xbib/gradle/plugin/jacc/JaccPluginTest.groovy @@ -0,0 +1,65 @@ +package org.xbib.gradle.plugin.jacc + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +import static org.junit.jupiter.api.Assertions.* + +class JaccPluginTest { + + private File projectDir + + private File settingsFile + + private File buildFile + + @BeforeEach + void setup(@TempDir File testProjectDir) throws IOException { + this.projectDir = testProjectDir + this.settingsFile = new File(testProjectDir, "settings.gradle") + this.buildFile = new File(testProjectDir, "build.gradle") + } + + @Test + void testJacc() { + String settingsFileContent = ''' +rootProject.name = 'jacc-test' +''' + settingsFile.write(settingsFileContent) + String buildFileContent = ''' +plugins { + id 'org.xbib.gradle.plugin.jacc' +} + +sourceSets { + test { + jacc { + srcDir "${System.getProperty('user.dir')}/src/test/jacc" + } + java { + srcDir "${System.getProperty('user.dir')}/build/my-generated-sources/jacc" + } + } +} + +''' + buildFile.write(buildFileContent) + BuildResult result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(":build", "--info") + .withPluginClasspath() + .forwardOutput() + .build() + assertEquals(TaskOutcome.SUCCESS, result.task(":build").getOutcome()) + + File file = new File("${System.getProperty('user.dir')}/build/my-generated-sources/jacc") + if (file.exists()) { + List list = Arrays.asList(file.listFiles()) + assertEquals(2, list.size()) + } + } +} diff --git a/gradle-plugin-jacc/src/test/jacc/simpleCalc.jacc b/gradle-plugin-jacc/src/test/jacc/simpleCalc.jacc new file mode 100644 index 0000000..3cd512f --- /dev/null +++ b/gradle-plugin-jacc/src/test/jacc/simpleCalc.jacc @@ -0,0 +1,104 @@ +// To compile and run this program using jacc and Sun's JDK: +// +// jacc simpleCalc.jacc +// javac Calc.java CalcTokens.java +// java Calc +// ... enter arithmetic expressions ... hit EOF to terminate +// + +%class Calc +%interface CalcTokens +%semantic int : yylval +%get token +%next yylex() + +%token '+' '-' '*' '/' '(' ')' ';' INTEGER +%left '+' '-' +%left '*' '/' + +%% + +prog : prog ';' expr { System.out.println($3); } + | expr { System.out.println($1); } + ; +expr : expr '+' expr { $$ = $1 + $3; } + | expr '-' expr { $$ = $1 - $3; } + | expr '*' expr { $$ = $1 * $3; } + | expr '/' expr { $$ = $1 / $3; } + | '(' expr ')' { $$ = $2; } + | INTEGER { $$ = $1; } + ; + +%% + + private void yyerror(String msg) { + System.out.println("ERROR: " + msg); + System.exit(1); + } + + private int c; + + /** Read a single input character from standard input. + */ + private void nextChar() { + if (c>=0) { + try { + c = System.in.read(); + } catch (Exception e) { + c = (-1); + } + } + } + + int token; + int yylval; + + /** Read the next token and return the + * corresponding integer code. + */ + int yylex() { + for (;;) { + // Skip whitespace + while (c==' ' || c=='\n' || c=='\t' || c=='\r') { + nextChar(); + } + if (c<0) { + return (token=ENDINPUT); + } + switch (c) { + case '+' : nextChar(); + return token='+'; + case '-' : nextChar(); + return token='-'; + case '*' : nextChar(); + return token='*'; + case '/' : nextChar(); + return token='/'; + case '(' : nextChar(); + return token='('; + case ')' : nextChar(); + return token=')'; + case ';' : nextChar(); + return token=';'; + default : if (Character.isDigit((char)c)) { + int n = 0; + do { + n = 10*n + (c - '0'); + nextChar(); + } while (Character.isDigit((char)c)); + yylval = n; + return token=INTEGER; + } else { + yyerror("Illegal character "+c); + nextChar(); + } + } + } + } + + public static void main(String[] args) { + Calc calc = new Calc(); + calc.nextChar(); // prime the character input stream + calc.yylex(); // prime the token input stream + calc.parse(); // parse the input + } diff --git a/gradle-plugin-jflex/LICENSE.txt b/gradle-plugin-jflex/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/gradle-plugin-jflex/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/gradle-plugin-jflex/README.md b/gradle-plugin-jflex/README.md new file mode 100755 index 0000000..e75e4d0 --- /dev/null +++ b/gradle-plugin-jflex/README.md @@ -0,0 +1,79 @@ +# gradle-plugin-jflex + +A Gradle plugin for [JFlex](http://jflex.de) + +## Usage + + plugins { + id "org.xbib.gradle.plugin.jflex" version "1.4.0" + } + +Gradle will look for your JFlex files in the source sets you specified. +By default, it looks with the pattern `**/*.jflex` under `src/main/jflex` +and `src/test/jflex`. + +You can set up the source sets like this: + + sourceSets { + main { + jflex { + srcDir "src/main/jflex" + } + java { + srcDir "$buildDir/my-generated-sources/jflex" + } + } + } + +The lastJava `srcDir` definition will be used as the base for the JFLex target path. +If not given, the JFlex target path for generated Java source follows the pattern: + +`${project.buildDir}/generated/sources/jflex` + +The JFlex target path will be added automatically to the java compile task source directory +of the source set. + +## Parameter support + +The following parameters can be set in a global `jflex` extension +in the gradle script. See also https://jflex.de/manual.html + +| Name | Description | +| ------- | ---------- | +| encoding | the file encoding | +| rootDirectory | the root directory used by JFlex (modification discouraged since the directories are derived from gradle source set) +| skel | uses external skeleton in UTF-8 encoding. This is mainly for JFlex maintenance and special low level customisations. Use only when you know what you are doing! JFlex comes with a skeleton file in the src directory that reflects exactly the internal, pre-compiled skeleton and can be used with the skel option. | +| verbose | display generation progress messages (disabled by default) | +| jlex | tries even harder to comply to JLex interpretation of specs | +| no_minimize | skip the DFA minimisation step during scanner generation | +| no_backup | don't write backup files if this is true | +| unused_warning | warn about unused macros (by default false) | +| progress | progress dots will be printed (by default false) | +| dot | If true, jflex will write graphviz .dot files for generated automata (by default, false) | +| time | If true, jflex will print time statistics about the generation process (by default false) | +| dump | If true, you will be flooded with information, e.g. dfa tables (by default, false) | +| legacy_dot | dot (.) meta character matches [^\n] instead of [^\n\r\u000B\u000C\u0085\u2028\u2029] | +| statistics | print output statistics (by default, false) | + +## Credits + +gradle-plugin-jflex is a plugin based on +[gradle-jflex-plugin](https://github.com/thomaslee/gradle-jflex-plugin) +which was written by [Tom Lee](http://tomlee.co). + +# License + +Copyright (C) 2015-2020 Jörg Prante + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/gradle-plugin-jflex/build.gradle b/gradle-plugin-jflex/build.gradle new file mode 100755 index 0000000..7863040 --- /dev/null +++ b/gradle-plugin-jflex/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'java-gradle-plugin' + alias(libs.plugins.publish) +} + +apply plugin: 'java-gradle-plugin' +apply plugin: 'com.gradle.plugin-publish' + +apply from: rootProject.file('gradle/compile/groovy.gradle') +apply from: rootProject.file('gradle/test/junit5.gradle') + +dependencies { + api gradleApi() + implementation libs.jflex + testImplementation gradleTestKit() +} + +gradlePlugin { + plugins { + jflexPlugin { + id = 'org.xbib.gradle.plugin.jflex' + implementationClass = 'org.xbib.gradle.plugin.jflex.JFlexPlugin' + } + } +} + +if (project.hasProperty('gradle.publish.key')) { + pluginBundle { + website = scmUrl + vcsUrl = scmUrl + plugins { + jflexPlugin { + id = 'org.xbib.gradle.plugin.jflex' + version = project.version + description = 'Gradle JFlex plugin' + displayName = 'Gradle JFlex plugin' + tags = ['jflex'] + } + } + } +} diff --git a/gradle-plugin-jflex/gradle.properties b/gradle-plugin-jflex/gradle.properties new file mode 100644 index 0000000..f16669e --- /dev/null +++ b/gradle-plugin-jflex/gradle.properties @@ -0,0 +1 @@ +version = 1.6.0 diff --git a/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexExtension.groovy b/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexExtension.groovy new file mode 100644 index 0000000..239d320 --- /dev/null +++ b/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexExtension.groovy @@ -0,0 +1,67 @@ +package org.xbib.gradle.plugin.jflex + +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional + +class JFlexExtension { + + @Input + @Optional + String encoding + + @Input + @Optional + File rootDirectory + + @Input + @Optional + File skel + + @Input + @Optional + Boolean verbose = false + + @Input + @Optional + Boolean jlex = false + + @Input + @Optional + Boolean no_minimize = false + + @Input + @Optional + Boolean no_backup = false + + @Input + @Optional + Boolean unused_warning = false + + @Input + @Optional + Boolean progress = false + + @Input + @Optional + Boolean time = false + + @Input + @Optional + Boolean dot = false + + @Input + @Optional + Boolean dump = false + + @Input + @Optional + Boolean legacy_dot = false + + @Input + @Optional + Boolean statistics = false + + @Input + @Optional + Boolean writeIntoJavaSrc = false +} diff --git a/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexPlugin.groovy b/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexPlugin.groovy new file mode 100755 index 0000000..cbf54dd --- /dev/null +++ b/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexPlugin.groovy @@ -0,0 +1,90 @@ +package org.xbib.gradle.plugin.jflex + +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.tasks.SourceSet +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.TaskProvider + +class JFlexPlugin implements Plugin { + + private static final Logger logger = Logging.getLogger(JFlexPlugin) + + @Override + void apply(Project project) { + logger.info "JFlex plugin says hello" + project.with { + apply plugin: 'java-library' + createJflexExtension(project) + addSourceSetExtensions(project) + } + project.afterEvaluate { + addJFlexTasks(project) + } + } + + private static void addSourceSetExtensions(Project project) { + project.sourceSets.all { SourceSet sourceSet -> + createSourceSetExtension(project, sourceSet) + createConfiguration(project, sourceSet) + } + } + + private static void createSourceSetExtension(Project project, SourceSet sourceSet) { + String name = sourceSet.name + SourceDirectorySet sourceDirectorySet = project.objects.sourceDirectorySet(name, "${name} JFlex source") + sourceSet.extensions.add('jflex', sourceDirectorySet) + sourceDirectorySet.srcDir("src/${name}/jflex") + sourceDirectorySet.include("**/*.jflex") + } + + private static void createConfiguration(Project project, SourceSet sourceSet) { + String configName = sourceSet.name + capitalize('jflex' as CharSequence) + if (project.configurations.findByName(configName) == null) { + logger.info "create configuration ${configName}" + project.configurations.create(configName) { + visible = false + transitive = true + extendsFrom = [] + } + } + } + + private static void createJflexExtension(Project project) { + project.extensions.create ('jflex', JFlexExtension) + } + + private static void addJFlexTasks(Project project) { + project.sourceSets.all { SourceSet sourceSet -> + addJFlexTaskForSourceSet(project, sourceSet) + } + } + + private static void addJFlexTaskForSourceSet(Project project, SourceSet sourceSet) { + String taskName = sourceSet.getTaskName('generate', 'jflex') + SourceDirectorySet sourceDirectorySet = sourceSet.extensions.getByName('jflex') as SourceDirectorySet + File targetFile = project.file("${project.buildDir}/generated/sources/${sourceSet.name}") + if (sourceDirectorySet.asList()) { + TaskProvider taskProvider = project.tasks.register(taskName, JFlexTask) { + group = 'jflex' + description = 'Generates code from JFlex files in ' + sourceSet.name + source = sourceDirectorySet.asList() + target = targetFile + theSourceSet = sourceSet + } + logger.info "created ${taskName} for sources ${sourceDirectorySet.asList()} and target ${targetFile}" + project.tasks.named(sourceSet.compileJavaTaskName).configure({ + dependsOn taskProvider + }) + if (sourceSet.java && sourceSet.java.srcDirs) { + sourceSet.java.srcDirs += targetFile + } + } + } + + private static String capitalize(CharSequence charSequence) { + return charSequence.length() == 0 ? "" : "" + Character.toUpperCase(charSequence.charAt(0)) + charSequence.subSequence(1, charSequence.length()) + } +} diff --git a/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexTask.groovy b/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexTask.groovy new file mode 100755 index 0000000..3a8148f --- /dev/null +++ b/gradle-plugin-jflex/src/main/groovy/org/xbib/gradle/plugin/jflex/JFlexTask.groovy @@ -0,0 +1,91 @@ +package org.xbib.gradle.plugin.jflex + +import jflex.exceptions.GeneratorException +import jflex.generator.LexGenerator +import jflex.logging.Out +import jflex.option.Options +import jflex.skeleton.Skeleton +import org.gradle.api.DefaultTask +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.SourceSet +import org.gradle.api.tasks.StopActionException +import org.gradle.api.tasks.TaskAction + +import java.nio.charset.Charset + +@CacheableTask +class JFlexTask extends DefaultTask { + + private final Logger logger = Logging.getLogger(JFlexTask) + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + Iterable source + + @OutputDirectory + File target + + @Internal + SourceSet theSourceSet + + @TaskAction + void generateAndTransformJflex() throws Exception { + JFlexExtension ext = project.extensions.findByType(JFlexExtension) + Options.setRootDirectory(ext.rootDirectory ? ext.rootDirectory : new File("")) + Skeleton.readDefault() + if (ext.skel) { + Skeleton.readSkelFile(ext.skel) + } + Options.encoding = ext.encoding ? Charset.forName(ext.encoding) : Charset.defaultCharset() + Options.verbose = ext.verbose + Options.progress = ext.progress + Options.unused_warning = ext.unused_warning + Options.jlex = ext.jlex + Options.no_minimize = ext.no_minimize + Options.no_backup = ext.no_backup + Options.time = ext.time + Options.dot = ext.dot + Options.dump = ext.dump + Options.legacy_dot = ext.legacy_dot + // hack for writing directly into java source. Not recommended. + if (ext.writeIntoJavaSrc) { + if (theSourceSet.java && theSourceSet.java.srcDirs) { + logger.info "java sources: ${theSourceSet.java.srcDirs}" + target = theSourceSet.java.srcDirs.first() + logger.info "switching to first java source directory ${target}" + } else { + logger.warn "writing into java source not possible, is empty" + } + } + source.each { file -> + String pkg = getPackageName(file) + File fullTarget = new File(target, pkg.replace('.','/')) + project.mkdir(fullTarget) + Options.directory = fullTarget + logger.info "jflex task: source=${file} pkg=${pkg} dir=${target}" + try { + new LexGenerator(file).generate() + } catch (GeneratorException e) { + Logging.getLogger(JFlexTask).error("JFlex error: ${e.message}", e) + throw new StopActionException('an error occurred during JFlex code generation') + } + } + if (ext.statistics) { + Out.statistics() + } + } + + static String getPackageName(File file) { + String string = file.readLines().find { line -> + line.startsWith('package') + } + return string == null ? '' : string.substring(8, string.length() - 1) + } +} diff --git a/gradle-plugin-jflex/src/test/groovy/org/xbib/gradle/plugin/test/JFlexPluginTest.groovy b/gradle-plugin-jflex/src/test/groovy/org/xbib/gradle/plugin/test/JFlexPluginTest.groovy new file mode 100644 index 0000000..c447ac6 --- /dev/null +++ b/gradle-plugin-jflex/src/test/groovy/org/xbib/gradle/plugin/test/JFlexPluginTest.groovy @@ -0,0 +1,194 @@ +package org.xbib.gradle.plugin.test + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir + +import java.nio.file.Files + +import static org.junit.jupiter.api.Assertions.* + +class JFlexPluginTest { + + private File projectDir + + private File settingsFile + + private File buildFile + + @BeforeEach + void setup(@TempDir File testProjectDir) throws IOException { + this.projectDir = testProjectDir + this.settingsFile = new File(testProjectDir, "settings.gradle") + this.buildFile = new File(testProjectDir, "build.gradle") + } + + @Test + void testJFlex() { + String settingsFileContent = ''' +rootProject.name = 'jflex-test' +''' + settingsFile.write(settingsFileContent) + String buildFileContent = ''' +plugins { + id 'org.xbib.gradle.plugin.jflex' +} + +sourceSets { + test { + jflex { + // point to our test directory where the jflex file lives + srcDir "${System.getProperty('user.dir')}/src/test/jflex" + } + } +} + +jflex { + verbose = true + dump = false + progress = false +} + +''' + buildFile.write(buildFileContent) + BuildResult result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(":build", "--info") + .withPluginClasspath() + .forwardOutput() + .build() + assertEquals(TaskOutcome.SUCCESS, result.task(":build").getOutcome()) + // default output dir + File target = new File(projectDir, "build/generated/sources/test") + boolean found = false + if (target.exists()) { + // check for generated output + assertEquals(1, target.listFiles().length) + target.eachFileRecurse { + if (it.isFile()) { + println "found: ${it}" + found = true + } + } + } else { + fail("directory not found: ${target}") + } + if (!found) { + fail("jflex output not found") + } + } + + + @Test + void testJFlexWriteIntoJavaSrc() { + Files.createDirectories(projectDir.toPath().resolve('src/main/java')) + Files.createDirectories(projectDir.toPath().resolve('src/test/java')) + String settingsFileContent = ''' +rootProject.name = 'jflex-test' +''' + settingsFile.write(settingsFileContent) + String buildFileContent = ''' +plugins { + id 'org.xbib.gradle.plugin.jflex' +} + +sourceSets { + main { + java { + srcDir "src/main/java" + } + jflex { + // point to our test directory where the jflex file lives + srcDir "${System.getProperty('user.dir')}/src/test/jflex" + } + } + test { + java { + srcDir "src/test/java" + } + } +} + +jflex { + verbose = true + dump = false + progress = false + // enable legacy behavior of writing directly into Java source directory. Not recommended. + writeIntoJavaSrc = true +} + +''' + buildFile.write(buildFileContent) + BuildResult result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(":build", "--info") + .withPluginClasspath() + .forwardOutput() + .build() + assertEquals(TaskOutcome.SUCCESS, result.task(":build").getOutcome()) + // search the Java source directory + File target = new File(projectDir, "src/main/java") + boolean found = false + if (target.exists()) { + // check for generated file + assertEquals(1, target.listFiles().length) + target.eachFileRecurse { + if (it.isFile()) { + println "found: ${it}" + found = true + } + } + } else { + fail("directory not found: ${target}") + } + if (!found) { + fail("jflex output not found") + } + } + + @Test + void testTaskIsNotStarted() { + String buildFileContent = ''' +plugins { + id 'org.xbib.gradle.plugin.jflex' +} + +sourceSets { + test { + jflex { + srcDir "${System.getProperty('user.dir')}/src/test/jflex" + } + java { + srcDir "${System.getProperty('user.dir')}/build/my-generated-sources/jflex" + } + } +} + +jflex { + verbose = false + dump = false + progress = false +} + +def configuredTasks = [] +tasks.configureEach { + configuredTasks << it +} + +gradle.buildFinished { + def configuredTaskPaths = configuredTasks*.path + assert configuredTaskPaths == [':help',':clean'] +} +''' + buildFile.write(buildFileContent) + GradleRunner.create() + .withProjectDir(projectDir) + .withArguments(":help") + .withPluginClasspath() + .forwardOutput() + .build() + } +} diff --git a/gradle-plugin-jflex/src/test/jflex/org/xbib/gradle/plugin/test/Test.jflex b/gradle-plugin-jflex/src/test/jflex/org/xbib/gradle/plugin/test/Test.jflex new file mode 100644 index 0000000..fb0331c --- /dev/null +++ b/gradle-plugin-jflex/src/test/jflex/org/xbib/gradle/plugin/test/Test.jflex @@ -0,0 +1,43 @@ +package org.xbib.gradle.plugin.test; +import java.io.IOException; + +%% + +%class Test +%int +%unicode +%line +%column + +%{ + int token; + double yylval; + + int nextToken() { + try { + return token = yylex(); + } catch (IOException e) { + return token = -1; + } + } + + int getToken() { + return token; + } + + double getSemantic() { + return yylval; + } +%} + +ws = [ \t\f] +digit = [0-9] +number = {digit}+(\.{digit}+)?(E[+\-]?{digit}+)? + +%% + \r|\n|\r\n { return 0; } + {ws}+ { } + {number} { yylval = Double.parseDouble(yytext()); return 1; } + [+\-*/()=] { return (int)(yytext().charAt(0)); } + "*+" { return 2; } + . { throw new Error(yytext()); } diff --git a/gradle-plugin-rpm/NOTICE.txt b/gradle-plugin-rpm/NOTICE.txt new file mode 100644 index 0000000..e65dfa4 --- /dev/null +++ b/gradle-plugin-rpm/NOTICE.txt @@ -0,0 +1,8 @@ + +Very loosely based on a 2018 version of nebula.rpm + +https://github.com/nebula-plugins/gradle-ospackage-plugin + +but completely revised, using the xbib rpm library, not redline rpm. + +Lincense: Apache 2.0 diff --git a/gradle-plugin-rpm/build.gradle b/gradle-plugin-rpm/build.gradle index 5801f91..9460103 100644 --- a/gradle-plugin-rpm/build.gradle +++ b/gradle-plugin-rpm/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java-gradle-plugin' - id 'com.gradle.plugin-publish' version '0.18.0' + alias(libs.plugins.publish) } apply plugin: 'java-gradle-plugin' @@ -10,20 +10,10 @@ apply from: rootProject.file('gradle/compile/groovy.gradle') dependencies { api gradleApi() - api "org.xbib:rpm-core:${project.property('rpm.version')}" + api libs.rpm testImplementation gradleTestKit() } -compileGroovy { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - -compileTestGroovy { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 -} - gradlePlugin { plugins { rpmPlugin { diff --git a/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmPlugin.groovy b/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmPlugin.groovy index 81b9d53..f78f877 100644 --- a/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmPlugin.groovy +++ b/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmPlugin.groovy @@ -1,19 +1,13 @@ package org.xbib.gradle.plugin -import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.BasePlugin -import org.gradle.util.GradleVersion class RpmPlugin implements Plugin { @Override void apply(Project project) { - String version = '6.4' - if (GradleVersion.current() < GradleVersion.version(version)) { - throw new GradleException("need Gradle ${version} or higher") - } project.plugins.apply(BasePlugin) project.ext.Rpm = Rpm.class } diff --git a/gradle-plugin-shadow/build.gradle b/gradle-plugin-shadow/build.gradle new file mode 100644 index 0000000..247989e --- /dev/null +++ b/gradle-plugin-shadow/build.gradle @@ -0,0 +1,45 @@ +plugins { + id 'java-gradle-plugin' + alias(libs.plugins.publish) +} + +apply plugin: 'java-gradle-plugin' +apply plugin: 'com.gradle.plugin-publish' + +apply from: rootProject.file('gradle/compile/groovy.gradle') +apply from: rootProject.file('gradle/test/junit5.gradle') + +dependencies { + api gradleApi() + implementation libs.asm + implementation libs.asm.commons + implementation libs.asm.util + testImplementation gradleTestKit() + testImplementation libs.spock.core + testImplementation libs.spock.junit4 +} + +gradlePlugin { + plugins { + shadowPlugin { + id = 'org.xbib.gradle.plugin.shadow' + implementationClass = 'org.xbib.gradle.plugin.shadow.ShadowPlugin' + } + } +} + +if (project.hasProperty('gradle.publish.key')) { + pluginBundle { + website = scmUrl + vcsUrl = scmUrl + plugins { + shadowPlugin { + id = 'org.xbib.gradle.plugin.shadow' + version = project.version + description = 'Shadow plugin for Gradle' + displayName = 'Shadow plugin for Gradle' + tags = [ 'shadow' ] + } + } + } +} diff --git a/gradle-plugin-shadow/gradle.properties b/gradle-plugin-shadow/gradle.properties new file mode 100644 index 0000000..f185967 --- /dev/null +++ b/gradle-plugin-shadow/gradle.properties @@ -0,0 +1 @@ +version = 2.0.0 diff --git a/gradle-plugin-shadow/src/docs/asciidoc/00-intro.adoc b/gradle-plugin-shadow/src/docs/asciidoc/00-intro.adoc new file mode 100644 index 0000000..1391666 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/00-intro.adoc @@ -0,0 +1,28 @@ +:tests: ../../test/groovy/org/xbib/gradle/plugin/shadow +== Introduction + +Shadow is a Gradle plugin for combining dependency classes and resources with a project's into a single +output Jar. +The combined Jar is often referred to a __fat-jar__ or __uber-jar__. +Shadow utilizes `JarInputStream` and `JarOutputStream` to efficiently process dependent libraries +into the output jar without incurring the I/O overhead of expanding the jars to disk. + +=== Benefits of Shadow + +Shadowing a project output has a major use case: + +. Bundling and relocating common dependencies in libraries to avoid classpath conflicts + +==== Bundling + +Dependency bundling and relocation is the main use case for __library__ authors. +The goal of a bundled library is to create a pre-packaged dependency for other libraries or applications to utilize. +Often in these scenarios, a library may contain a dependency that a downstream library or application also uses. +In __some__ cases, different versions of this common dependency can cause an issue in either the upstream library or +the downstream application. +These issues often manifest themselves as binary incompatibilities in either the library or application code. + +By utilizing Shadow's ability to __relocate__ the package names for dependencies, a library author can ensure that the +library's dependencies will not conflict with the same dependency being declared by the downstream application. + +include::01-getting-started.adoc[] diff --git a/gradle-plugin-shadow/src/docs/asciidoc/01-getting-started.adoc b/gradle-plugin-shadow/src/docs/asciidoc/01-getting-started.adoc new file mode 100644 index 0000000..2ee621a --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/01-getting-started.adoc @@ -0,0 +1,96 @@ +=== Getting Started + +[source,groovy,subs="+attributes"] +---- +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'org.xbib.gradle.plugin:gradle-plugin-shadow:{project-version}' + } +} + +apply plugin: 'java' +apply plugin: 'org.xbib.gradle.plugin.shadow' +---- + +Alternatively, the Gradle Plugin syntax can be used: + +[source,groovy,subs="+attributes"] +---- +plugins { + id 'java' + id 'org.xbib.gradle.plugin.shadow' version '{project-version}' +} +---- + +Shadow is a reactive plugin. +This means that applying Shadow by itself will perform no configuration on your project. +Instead, Shadow __reacts__ to the application of other plugins to decorate the project. + +This means, that for most users, the `java` or `groovy` plugins must be __explicitly__ applied +to have the desired effect. + +=== Default Java/Groovy Tasks + +In the presence of the `java` or `groovy` plugins, Shadow will automatically configure the +following behavior: + +* Adds a `shadowJar` task to the project. +* Adds a `shadow` configuration to the project. +* Configures the `shadowJar` task to include all sources from the project's `main` sourceSet. +* Configures the `shadowJar` task to bundle all dependencies from the `runtime` configuration. +* Configures the __classifier__ attribute of the `shadowJar` task to be `'all'` . +* Configures the `shadowJar` task to generate a `Manifest` with: +** Inheriting all configuration from the standard `jar` task. +** Adds a `Class-Path` attribute to the `Manifest` that appends all dependencies from the `shadow` configuration +* Configures the `shadowJar` task to __exclude__ any JAR index or cryptographic signature files matching the following patterns: +** `META-INF/INDEX.LIST` +** `META-INF/*.SF` +** `META-INF/*.DSA` +** `META-INF/*.RSA` +* Creates and registers the `shadow` component in the project (used for integrating with `maven-publish`). +* Configures the `uploadShadow` task (as part of the `maven` plugin) with the following behavior: +** Removes the `compile` and `runtime` configurations from the `pom.xml` file mapping. +** Adds the `shadow` configuration to the `pom.xml` file as `RUNTIME` scope. + +=== Shadowing Gradle Plugins + +Shadow is capable of automatically configuring package relocation for your dependencies. +This is useful especially when building Gradle plugins where you want your dependencies to not conflict with versions +provided by the Gradle runtime. + +[source,groovy,subs="+attributes"] +---- +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'org.xbib.gradle.plugin:shadow:{project-version}' + } +} + +apply plugin: 'org.xbib.gradle.plugin.shadow' +apply plugin: 'java' +---- + +Alternatively, the Gradle Plugin syntax can be used: + +[source,groovy,subs="+attributes"] +---- +plugins { + id 'java' + id 'org.xbib.gradle.plugin.plugin-shadow' version '{project-version}' +} +---- + +Applying the `plugin-shadow` plugin is the same as applying the standard `shadow` plugin with the additional creation +of the `configureRelocationShadowJar` task. +This task runs before the `shadowJar` task and scans the packages present in the dependencies that will be merged into +the final jar and automatically configures relocation for them. +By default the tasks relocates all packages to the `shadow.` prefix. +For example `org.jdom2.JDOMException` becomes `shadow.org.jdom2.JDOMException` + +For more details see the sectinon <> \ No newline at end of file diff --git a/gradle-plugin-shadow/src/docs/asciidoc/10-configuring.adoc b/gradle-plugin-shadow/src/docs/asciidoc/10-configuring.adoc new file mode 100644 index 0000000..4305ca0 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/10-configuring.adoc @@ -0,0 +1,106 @@ +== Configuring Shadow + +The link:{api}/tasks/ShadowJar.html[`ShadowJar`] task type extends from Gradle's +https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html[`Jar`] type. +This means that all attributes and methods available on `Jar` are also available on +link:{api}/tasks/ShadowJar.html[`ShadowJar`]. +Refer the __Gradle User Guide__ for https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html[Jar] for +details. + +=== Configuring Output Name + +Shadow configures the default `shadowJar` task to set the output JAR's `destinationDir`, `baseName`, `appendix`, +`version`, and `extension` to the same default values as Gradle does for all `Jar` tasks. +Additionally, it configures the `classifier` to be `all`. + +If working with a Gradle project with the name `myApp` and version `1.0`, the default `shadowJar` task will output a +file at: `build/libs/myApp-1.0-all.jar` + +As with all `Jar` tasks in Gradle, these values can be overridden: + +.Output to `build/libs/shadow.jar` +[source,groovy,indent=0] +---- +include::{tests}/ShadowPluginSpec.groovy[tags=rename] +---- + +=== Configuring the Runtime Classpath + +Each Java JAR file contains a manifest file that provides meta data about the contents of the JAR file itself. +When using a shadowed JAR file as an executable JAR, it is assumed that all necessary runtime classes are contained +within the JAR itself. +There may be situations where the desire is to **not** bundle select dependencies into the shadowed JAR file but +they are still required for runtime execution. + +In these scenarios, Shadow creates a `shadow` configuration to declare these dependencies. +Dependencies added to the `shadow` configuration are *not* bundled into the output JAR. +Think of `configurations.shadow` as unmerged, runtime dependencies. +The integration with the `maven` and `maven-publish` plugins will automatically configure dependencies added +to `configurations.shadow` as `RUNTIME` scope dependencies in the resulting POM file. + +Additionally, Shadow automatically configures the manifest of the `shadowJar` task to contain a `Class-Path` entry +in the JAR manifest. +The value of the `Class-Path` entry is the name of all dependencies resolved in the `shadow` configuration +for the project. + +[source,groovy,indent=0] +---- +include::{tests}/ShadowPluginSpec.groovy[tags=shadowConfig] +---- + +Inspecting the `META-INF/MANIFEST.MF` entry in the JAR file will reveal the following attribute: +[source,property,indent=0] +---- +Class-Path: junit-3.8.2.jar +---- + +When deploying a shadowed JAR as an execution JAR, it is important to note that any non-bundled runtime dependencies +**must** be deployed in the location specified in the `Class-Path` entry in the manifest. + +=== Configuring the JAR Manifest + +Beyond the automatic configuration of the `Class-Path` entry, the `shadowJar` manifest is configured in a number of ways. +First, the manifest for the `shadowJar` task is configured to __inherit__ from the manifest of the standard `jar` task. +This means that any configuration performed on the `jar` task will propagate to the `shadowJar` tasks. + +[source,groovy,indent=0] +---- +include::{tests}/ShadowPluginSpec.groovy[tags=jarManifest] +---- + +Inspecting the `META-INF/MANIFEST.MF` entry in the JAR file will revel the following attribute: +[source,property,indent=0] +---- +Class-Path: /libs/a.jar +---- + +If it is desired to inherit a manifest from a JAR task other than the standard `jar` task, the `inheritFrom` methods +on the `shadowJar.manifest` object can be used to configure the upstream. + +[source,groovy,indent=0] +---- + +task testJar(type: Jar) { + manifest { + attributes 'Description': 'This is an application JAR' + } +} + +shadowJar { + manifest { + inheritFrom project.tasks.testJar.manifest + } +} +---- + +include::11-filtering-contents.adoc[] + +include::12-controlling-dependencies.adoc[] + +include::13-controlling-merging.adoc[] + +include::14-package-relocation.adoc[] + +include::15-minimizing.adoc[] + +include::16-reproducible-builds.adoc[] diff --git a/gradle-plugin-shadow/src/docs/asciidoc/11-filtering-contents.adoc b/gradle-plugin-shadow/src/docs/asciidoc/11-filtering-contents.adoc new file mode 100644 index 0000000..a8825a3 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/11-filtering-contents.adoc @@ -0,0 +1,27 @@ +=== Filtering Shadow Jar Contents + +The final contents of a shadow JAR can be filtered using the `exclude` and `include` methods inherited from Gradle's +`Jar` task type. + +Refer to the https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html[Jar] documentation for details +on the various versions of the methods and their behavior. + +When using `exclude`/`include` with a `ShadowJar` task, the resulting copy specs are applied to the __final__ JAR +contents. +This means that, the configuration is applied to the individual files from both the project source set or __any__ +of the dependencies to be merged. + +.Exclude a file from Shadow Jar +[source,groovy,indent=0] +---- +include::{tests}/FilteringSpec.groovy[tags=excludeFile] +---- + +Excludes and includes can be combined just like a normal `Jar` task, with `excludes` taking precendence over `includes`. +Additionally, ANT style patterns can be used to match multiple files. + +.Configuring output using ANT patterns +[source,groovy,indent=0] +---- +include::{tests}/FilteringSpec.groovy[tags=excludeOverInclude] +---- \ No newline at end of file diff --git a/gradle-plugin-shadow/src/docs/asciidoc/12-controlling-dependencies.adoc b/gradle-plugin-shadow/src/docs/asciidoc/12-controlling-dependencies.adoc new file mode 100644 index 0000000..b64c119 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/12-controlling-dependencies.adoc @@ -0,0 +1,118 @@ +=== Configuring Shadowed Dependencies + +Shadow configures the default `shadowJar` task to merge all dependencies from the project's `runtime` configuration +into the final JAR. +The configurations to from which to source dependencies for the merging can be configured using the `configurations` property +of the link:{api}/tasks/ShadowJar.html[`ShadowJar`] task type. + +[source,groovy,indent=0] +---- +shadowJar { + configurations = [project.configurations.compile] +} +---- + +The above code sample would configure the `shadowJar` task to merge depdencies from only the `compile` configuration. +This means any dependency declared in the `runtime` configuration would be **not** be included in the final JAR. + +[NOTE] +==== +Note the literal use of `project.configurations` when setting the `configurations` attribute of a +link:{api}/tasks/ShadowJar.html[`ShadowJar`] task. +This is **required**. It maybe be tempting to specify `configurations = [configurations.compile]` but this will not +have the intended effect, as `configurations.compile` will try to delegate to the `configurations` property of the +the link:{api}/tasks/ShadowJar.html[`ShadowJar`] task instead of the `project` +==== + +=== Embedding Jar Files Inside Your Shadow Jar + +Because of the way that Gradle handles dependency configuration, from a plugin perspective, shadow is unable to distinguish between a jar file configured as a dependency and a jar file included in the resource folder. This means that any jar found in a resource directory will be merged into the shadow jar the same as any other dependency. If your intention is to embed the jar inside, you must rename the jar as to not end with `.jar` before the shadow task begins. + +=== Filtering Dependencies + +Individual dependencies can be filtered from the final JAR by using the `dependencies` block of a +link:{api}/tasks/ShadowJar.html[`ShadowJar`] task. +Dependency filtering does **not** apply to transitive dependencies. +That is, excluding a dependency does not exclude any of its dependencies from the final JAR. + +The `dependency` blocks provides a number of methods for resolving dependencies using the notations familiar from +Gradle's `configurations` block. + +.Exclude an Module Dependency +[source,groovy,indent=0] +---- +include::{tests}/FilteringSpec.groovy[tags=excludeDep] +---- + +.Exclude a Project Dependency +[source,groovy,indent=0] +---- +include::{tests}/FilteringSpec.groovy[tags=excludeProject] +---- + +[NOTE] +==== +While not being able to filter entire transitive dependency graphs might seem like an oversight, it is necessary +because it would not be possible to intelligently determine the build author's intended results when there is a +common dependency between two 1st level dependencies when one is excluded and the other is not. +==== + +==== Using Regex Patterns to Filter Dependencies + +Dependencies can be filtered using regex patterns. +Coupled with the `::` notation for dependencies, this allows for excluding/including +using any of these individual fields. + +.Exclude Any Version of a Dependency +[source,groovy,indent=0] +---- +include::{tests}/FilteringSpec.groovy[tags=excludeDepWildcard] +---- + +Any of the individual fields can be safely absent and will function as though a wildcard was specified. + +.Ignore Dependency Version +[source,groovy,indent=0] +---- +shadowJar { + dependencies { + exclude(dependency('shadow:d')) + } +} +---- + +The above code snippet is functionally equivalent to the previous example. + +This same patten can be used for any of the dependency notation fields. + +.Ignoring An Artifact Regardless of Group +[source,groovy,indent=0] +---- +shadowJar { + dependencies { + exclude(dependency(':d:1.0')) + } +} +---- + +.Excluding All Artifacts From Group +[source,groovy,indent=0] +---- +shadowJar { + dependencies { + exclude(dependency('shadow::1.0')) + } +} +---- + +==== Programmatically Selecting Dependencies to Filter + +If more complex decisions are needed to select the dependencies to be included, the +link:{api}/tasks/ShadowJar.html#dependencies(Action)[`dependencies`] block provides a +method that accepts a `Closure` for selecting dependencies. + +.Selecting Dependencies to Filter With a Spec +[source,groovy,indent=0] +---- +include::{tests}/FilteringSpec.groovy[tags=excludeSpec] +---- diff --git a/gradle-plugin-shadow/src/docs/asciidoc/13-controlling-merging.adoc b/gradle-plugin-shadow/src/docs/asciidoc/13-controlling-merging.adoc new file mode 100644 index 0000000..de121ae --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/13-controlling-merging.adoc @@ -0,0 +1,151 @@ +=== Controlling JAR Content Merging + +Shadow allows for customizing the process by which the output JAR is generated through the +link:{api}/transformers/Transformer.html[`Transformer`] interface. +This is a concept that has been carried over from the original Maven Shade implementation. +A link:{api}/transformers/Transformer.html[`Transformer`] is invoked for each entry in the JAR before being written to +the final output JAR. +This allows a link:{api}/transformers/Transformer.html[`Transformer`] to determine if it should process a particular +entry and apply any modifications beforewriting the stream to the output. + +.Adding a Transformer +[source,groovy,indent=0] +---- +shadowJar { + transform(MyTransformer.class) +} +---- + +Additionally, a `Transformer` can accept a `Closure` to configure the provided `Transformer`. + +.Configuring a Transformer +[source,groovy,indent=0] +---- +shadowJar { + transform(MyTransformer.class) { + enable = true + } +} +---- + +An instantiated instance of a `Transformer` can also be provided. + +.Adding a Transformer Instance +[source,groovy,indent=0] +---- +shadowJar { + transform(new MyTransformer(enabled: true)) +} +---- + +==== Merging Service Descriptor Files + +Java libraries often contain service descriptors files in the `META-INF/services` directory of the JAR. +A service descriptor typically contains a line delimited list of classes that are supported for a particular __service__. +At runtime, this file is read and used to configure library or application behavior. + +Multiple dependencies may use the same service descriptor file name. +In this case, it is generally desired to merge the content of each instance of the file into a single output file. +The link:{api}/transformers/ServiceFileTransformer.html[`ServiceFileTransformer`] class is used to perform this merging. +By default, it will merge each copy of a file under `META-INF/services` into a single file in the output JAR. + +.Merging Service Files +[source,groovy,indent=0] +---- +shadowJar { + mergeServiceFiles() +} +---- + +The above code snippet is a convenience syntax for calling +link:{api}/tasks/ShadowJar.html#transform(Class++++)[`transform(ServiceFileTransformer.class)`]. + +[NOTE] +==== +Groovy Extension Module descriptor files (located at `META-INF/services/org.codehaus.groovy.runtime.ExtensionModule`) +are ignored by the link:{api}/transformers/ServiceFileTransformer.html[`ServiceFileTransformer`]. +This is due to these files having a different syntax than standard service descriptor files. +Use the link:{api}/tasks/ShadowJar.html#mergeGroovyExtensionModules()[`mergeGroovyExtensionModules()`] method to merge +these files if your dependencies contain them. +==== + +===== Configuring the Location of Service Descriptor Files + +By default the link:{api}/transformers/ServiceFileTransformer.html[`ServiceFileTransformer`] is configured to merge +files in `META-INF/services`. +This directory can be overridden to merge descriptor files in a different location. + +.Merging Service Files in a Specific Directory +[source,groovy,indent=0] +---- +shadowJar { + mergeServiceFiles { + path = 'META-INF/custom' + } +} +---- + +===== Excluding/Including Specific Service Descriptor Files From Merging + +The link:{api}/transformers/ServiceFileTransformer.html[`ServiceFileTransformer`] class supports specifying specific +files to include or exclude from merging. + +.Excluding a Service Descriptor From Merging +[source,groovy,indent=0] +---- +shadowJar { + mergeServiceFiles { + exclude 'META-INF/services/com.acme.*' + } +} +---- + +==== Merging Groovy Extension Modules + +Shadow provides a specific transformer for dealing with Groovy extension module files. +This is due to their special syntax and how they need to be merged together. +The link:{api}/transformers/GroovyExtensionModuleTransformer.html[`GroovyExtensionModuleTransformer`] will handle these +files. +The link:{api}/tasks/ShadowJar.html[`ShadowJar`] task also provides a short syntax method to add this transformer. + +.Merging Groovy Extension Modules +[source,groovy,indent=0] +---- +shadowJar { + mergeGroovyExtensionModules() +} +---- + +==== Appending Text Files + +Generic text files can be appended together using the +link:{api}/transformers/AppendingTransformer.html[`AppendingTransformer`]. +Each file is appended using new lines to separate content. +The link:{api}/tasks/ShadowJar.html[`ShadowJar`] task provides a short syntax method of +link:{api}/tasks/ShadowJar.html#append(java.lang.String)[`append(String)`] to configure this transformer. + +.Appending a Property File +[source,groovy,indent=0] +---- +shadowJar { + append 'test.properties' +} +---- + +==== Appending XML Files + +XML files require a special transformer for merging. +The link:{api}/transformers/XmlAppendingTransformer.html[`XmlAppendingTransformer`] reads each XML document and merges +each root element into a single document. +There is no short syntax method for the link:{api}/transformers/XmlAppendingTransformer.html[`XmlAppendingTransformer`]. +It must be added using the link:{api}/tasks/ShadowJar.html#transform(++Class++)[`transform`] methods. + +.Appending a XML File +[source,groovy,indent=0] +---- +shadowJar { + tranform(XmlAppendingTransformer.class) { + resource = 'properties.xml' + } +} +---- \ No newline at end of file diff --git a/gradle-plugin-shadow/src/docs/asciidoc/14-package-relocation.adoc b/gradle-plugin-shadow/src/docs/asciidoc/14-package-relocation.adoc new file mode 100644 index 0000000..72b5816 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/14-package-relocation.adoc @@ -0,0 +1,72 @@ +=== Relocating Packages + +Shade is capable of scanning a project's classes and relocating specific dependencies to a new location. +This is often required when one of the dependencies is susceptible to breaking changes in versions or +to classpath pollution in a downstream project. + +[NOTE] +==== +Google's Guava and the ASM library are typical cases where package relocation can come in handy. +==== + +Shadow uses the ASM library to modify class byte code to replace the package name and any import +statements for a class. +Any non-class files that are stored within a package structure are also relocated to the new location. + +.Relocating a Package +[source,groovy,indent=0] +---- +include::{tests}/RelocationSpec.groovy[tags=relocate] +---- + +The code snippet will rewrite the location for any class in the `junit.framework` to be `shadow.junit`. +For example, the class `junit.textui.TestRunner` becomes `shadow.junit.TestRunner`. +In the resulting JAR, the class file is relocated from `junit/textui/TestRunner.class` to +`shadow/junit/TestRunner.class`. + +[NOTE] +==== +Relocation operates at a package level. +It is not necessary to specify any patterns for matching, it will operate simply on the prefix +provided. +==== + +[NOTE] +==== +Relocation will be applied globally to all instance of the matched prefix. +That is, it does **not** scope to __only__ the dependencies being shadowed. +Be specific as possible when configuring relocation as to avoid unintended relocations. +==== + +==== Filtering Relocation + +Specific classes or files can be `included`/`excluded` from the relocation operation if necessary. + +.Configuring Filtering for Relocation +[source,groovy,indent=0] +---- +include::{tests}/RelocationSpec.groovy[tags=relocateFilter] +---- + +==== Automatically Relocating Dependencies +Shadow ships with a task that can be used to automatically configure all packages from all dependencies to be relocated. +To configure automatic dependency relocation, declare a task of type `ConfigureShadowRelocation` and configure the +`target` parameter to be the `ShadowJar` task you wish to auto configure. You will also need to declared a task +dependency so the tasks execute in the correct order. +.Configure Auto Relocation +[source,groovy] +---- +task relocateShadowJar(type: ConfigureShadowRelocation) { + target = tasks.shadowJar + prefix = "myapp" // Default value is "shadow" +} +tasks.shadowJar.dependsOn tasks.relocateShadowJar +---- +[NOTE] +==== +Configuring package auto relocation can add significant time to the shadow process as it will process all dependencies +in the configurations declared to be shadowed. By default, this is the `runtime` or `runtimeClasspath` configurations. +Be mindful that some Gradle plugins (such as `java-gradle-plugin` will automatically add dependencies to your class path +(e.g. `java-gradle-plugin` automatically adds the full Gradle API to your `compile` configuratinon. You may need to +remove these dependencies if you do not intend to shadow them into your library. +==== diff --git a/gradle-plugin-shadow/src/docs/asciidoc/15-minimizing.adoc b/gradle-plugin-shadow/src/docs/asciidoc/15-minimizing.adoc new file mode 100644 index 0000000..fd5b5dc --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/15-minimizing.adoc @@ -0,0 +1,25 @@ +=== Minimizing + +Shadow can automatically remove all classes of dependencies that are not used by the project, thereby minimizing the resulting shadowed JAR. + +.Minimizing an shadow JAR +[source,groovy,indent=0] +---- +shadowJar { + minimize() +} +---- + +A dependency can be excluded from the minimization process thereby forcing it's inclusion the shadow JAR. +This is useful when the dependency analyzer cannot find the usage of a class programmatically, for example if the class +is loaded dynamically via `Class.forName(String)`. + +.Force a class to be retained during minimization +[source,groovy,indent=0] +---- +shadowJar { + minimize { + exclude(dependency('org.scala-lang:.*:.*')) + } +} +---- diff --git a/gradle-plugin-shadow/src/docs/asciidoc/16-reproducible-builds.adoc b/gradle-plugin-shadow/src/docs/asciidoc/16-reproducible-builds.adoc new file mode 100644 index 0000000..77278cb --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/16-reproducible-builds.adoc @@ -0,0 +1,16 @@ +=== Reproducible Builds + +Because JAR files contain the timestamp of the included files, it is often difficult to create reproducible builds +from a source commit that results in a hash identical file. +Gradle supports reproducible JAR creation by setting the timestamps of included files to a consistent value. +Shadow includes support for overriding file timestamps. By default, Shadow will preserve +the file timestamps when creating the Shadow JAR. To set timestamps to a consistent value (1980/1/1 00:00:00), +set the `preserveFileTimestamps` property to `false` on the `ShadowJar` task. + +.Reset file timestamps +[source,groovy,indent=0] +---- +shadowJar { + preserveFileTimestamps = false +} +---- \ No newline at end of file diff --git a/gradle-plugin-shadow/src/docs/asciidoc/20-custom-tasks.adoc b/gradle-plugin-shadow/src/docs/asciidoc/20-custom-tasks.adoc new file mode 100644 index 0000000..812e3b5 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/20-custom-tasks.adoc @@ -0,0 +1,20 @@ +== Creating a Custom ShadowJar Task + +The built in `shadowJar` task only provides an output for the `main` source set of the project. +It is possible to add arbitrary link:{api}/tasks/ShadowJar.html[`ShadowJar`] tasks to a project. +When doing so, ensure that the `configurations` property is specified to inform Shadow which dependencies to merge +into the output. + +.Shadowing Test Sources and Dependencies +[source,groovy,indent=0] +---- +task testJar(type: ShadowJar) { + classifier = 'tests' + from sourceSets.test.output + configurations = [project.configurations.testRuntime] +} +---- + +The code snippet above will geneated a shadowed JAR contain both the `main` and `test` sources as well as all `runtime` +and `testRuntime` dependencies. +The file is output to `build/libs/--tests.jar`. diff --git a/gradle-plugin-shadow/src/docs/asciidoc/40-publishing.adoc b/gradle-plugin-shadow/src/docs/asciidoc/40-publishing.adoc new file mode 100644 index 0000000..1ecf075 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/40-publishing.adoc @@ -0,0 +1,69 @@ +== Publishing Shadow JARs + +=== Publishing with Maven-Publish Plugin + +The Shadow plugin will automatically configure the necessary tasks in the presence of Gradle's +`maven-publish` plugin. +The plugin provides the `component` method from the `shadow` extension to configure the +publication with the necessary artifact and dependencies in the POM file. + +.Publishing a Shadow JAR with the Maven-Publish Plugin +[source,groovy,indent=0] +---- +apply plugin: 'java' +apply plugin: 'maven-publish' +apply plugin: 'org.xbib.plugin.gradle.shadow' + +publishing { + publications { + shadow(MavenPublication) { publication -> + project.shadow.component(publication) + } + } + repositories { + maven { + url "http://repo.myorg.com" + } + } +} +---- + +=== Publishing with Maven Plugin + +The Shadow plugin will automatically configure the necessary tasks in the presence of Gradle's +`maven` plugin. +To publish the JAR, simply configure the publish location for the `uploadShadow` task and execute it. + +.Publishing a Shadow JAR with the Maven Plugin +[source,groovy,indent=0] +---- +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'org.xbib.gradle.plugin.shadow' + +uploadShadow { + repositories { + mavenDeployer { + repository(url: "http://repo.myorg.com") + } + } +} +---- + +=== Shadow Configuration and Publishing + +The Shadow plugin provides a custom configuration (`configurations.shadow`) to specify +runtime dependencies that are *not* merged into the final JAR file. +When configuring publishing with the Shadow plugin, the dependencies in the `shadow` +configuration, are translated to become `RUNTIME` scoped dependencies of the +published artifact. + +No other dependencies are automatically configured for inclusion in the POM file. +For example, excluded dependencies are *not* automatically added to the POM file or +if the configuration for merging are modified by specifying +`shadowJar.configurations = [configurations.myconfiguration]`, there is no automatic +configuration of the POM file. + +This automatic configuration occurs _only_ when using the above methods for +configuring publishing. If this behavior is not desirable, then publishing *must* +be manually configured. diff --git a/gradle-plugin-shadow/src/docs/asciidoc/50-multi-project-builds.adoc b/gradle-plugin-shadow/src/docs/asciidoc/50-multi-project-builds.adoc new file mode 100644 index 0000000..e69d069 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/50-multi-project-builds.adoc @@ -0,0 +1,21 @@ +== Using Shadow in Multi-Project Builds + +When using Shadow in a multi-project build, project dependencies will be treated the same as +external dependencies. +That is a project dependency will be merged into the `shadowJar` output of the project that +is applying the Shadow plugin. + +=== Depending on the Shadow Jar from Another Project + +In a multi-project build there may be one project that applies Shadow and another that +requires the shadowed JAR as a dependency. +In this case, use Gradle's normal dependency declaration mechanism to depend on the `shadow` +configuration of the shadowed project. + +.Depending On Shadow Output of Project +[source,groovy,indent=0] +---- +dependencies { + compile project(path: 'api', configuration: 'shadow') +} +---- \ No newline at end of file diff --git a/gradle-plugin-shadow/src/docs/asciidoc/60-shadowing-plugins.adoc b/gradle-plugin-shadow/src/docs/asciidoc/60-shadowing-plugins.adoc new file mode 100644 index 0000000..9264112 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/60-shadowing-plugins.adoc @@ -0,0 +1,49 @@ +== Using Shadow to Package Gradle Plugins + +In some scenarios, writing a Gradle plugin can be problematic because your plugin may depend on a version that +conflicts with the same dependency provided by the Gradle runtime. If this is the case, then you can utilize Shadow +to relocate your dependencies to a different package name to avoid the collision. + +Configuring the relocation has always been possible, but the build author is required to know all the package names +before hand. Shadow introduces a new task type `ConfigureShadowRelocation`. +Tasks of this type are configured to target an instance of a `ShadowJar` task and run immediately before it. + +The `ConfigureShadowRelocation` task, scans the dependencies from the configurations specified on the associated +`ShadowJar` task and collects the package names contained within them. It then configures relocation for these +packages using the specified `prefix` on the associated `ShadowJar` task. + +While this is useful for developing Gradle plugins, nothing about the `ConfigureShadowRelocation` task is tied to +Gradle projects. It can be used for standard Java or Groovy projects. + +A simple Gradle plugin can use this feature by applying the `shadow` plugin and configuring the dependencies +like so: + +[source,groovy,subs="+attributes"] +---- +import org.xbib.gradle.plugin.shadow.tasks.ConfigureShadowRelocation + +plugins { + id 'java' + id 'org.xbib.gradle.plugin.shadow' version '{project-version}' +} + +dependencies { + shadow localGroovy() + shadow gradleApi() + + compile 'org.ow2.asm:asm:7.0-beta' + compile 'org.ow2.asm:asm-commons:7.0-beta' + compile 'org.ow2.asm:asm-util:7.0-beta' +} + +task relocateShadowJar(type: ConfigureShadowRelocation) { + target = tasks.shadowJar +} +tasks.shadowJar.dependsOn tasks.relocateShadowJar + +---- + +Note that the `localGroovy()` and `gradleApi()` dependencies are added to the `shadow` configuration instead of the +normal `compile` configuration. These dependencies are provided by Gradle to compile your project but are ultimately +provided by the Gradle runtime when executing the plugin. Thus, it is __not__ advisable to bundle these dependencies +with your plugin. \ No newline at end of file diff --git a/gradle-plugin-shadow/src/docs/asciidoc/80-examples.adoc b/gradle-plugin-shadow/src/docs/asciidoc/80-examples.adoc new file mode 100644 index 0000000..e69de29 diff --git a/gradle-plugin-shadow/src/docs/asciidoc/99-about.adoc b/gradle-plugin-shadow/src/docs/asciidoc/99-about.adoc new file mode 100644 index 0000000..92fdbb5 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/99-about.adoc @@ -0,0 +1,23 @@ +== About This Project + +John Engelman about the original Shadow plugin: + +____ +I started this project in December of 2012. We were working on converting from a monolithic application into a the +new hot jazz of "microservices" using Dropwizard. +I had also just started learning about Gradle and I knew that the incremental build system it provided would benefit +our development team greatly. +Unfortunately, the closest thing that Gradle had to Maven's Shade plugin was its ability to create application TARs and +ZIPs. + +So, Charlie Knudsen and myself set out to port the existing Shade code into a Gradle plugin. +This port is what existing up until the `0.9` milestone releases for Shadow. +It functioned, but it wasn't idiomatic Gradle by any means. + +Starting with 0.9, Shadow was rewritten from the ground up as standard Gradle plugin and leveraged as much of Gradle's +classes and concepts as possible. +At the same time as the 0.9 release, Gradle was announcing the https://plugins.gradle.org[Gradle Plugin Portal] and +so Shadow was published there. + +Shadow has had nearly ~900,000 downloads from Bintray and countless more from the Gradle Plugin Portal. +____ diff --git a/gradle-plugin-shadow/src/docs/asciidoc/index.adoc b/gradle-plugin-shadow/src/docs/asciidoc/index.adoc new file mode 100644 index 0000000..2ae2a49 --- /dev/null +++ b/gradle-plugin-shadow/src/docs/asciidoc/index.adoc @@ -0,0 +1,32 @@ +:src: ../ +:tests: {src}/test/groovy/org/xbib/gradle/plugin/shadow +:api: api/org/xbib/gradle/plugin/shadow +:docinfo1: +ifdef::env-github[] +:note-caption: :information-source: +endif::[] + += Gradle Shadow Plugin User Guide & Examples + +:revnumber: {project-version} + +NOTE: This documentation was taken from the original Shadow Plugin and is copyrighted by John R. Engelman. +References to older original Shadow Plugin versions have been removed for consistency. +The references to the application mode (runShadow) are no longer supported and have been removed. +To create application images, use Java Platform Module System, especially the `jlink` tool. + +link:api/index.html[API Docs] + +include::00-intro.adoc[] + +include::10-configuring.adoc[] + +include::20-custom-tasks.adoc[] + +include::40-publishing.adoc[] + +include::50-multi-project-builds.adoc[] + +include::60-shadowing-plugins.adoc[] + +include::99-about.adoc[] \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowBasePlugin.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowBasePlugin.groovy new file mode 100644 index 0000000..295adb1 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowBasePlugin.groovy @@ -0,0 +1,21 @@ +package org.xbib.gradle.plugin.shadow + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.util.GradleVersion + +class ShadowBasePlugin implements Plugin { + + static final String EXTENSION_NAME = 'shadow' + + static final String CONFIGURATION_NAME = 'shadow' + + @Override + void apply(Project project) { + if (GradleVersion.current() < GradleVersion.version('4.10')) { + throw new IllegalArgumentException('shadow requires at least Gradle 4.10') + } + project.extensions.create(EXTENSION_NAME, ShadowExtension, project) + project.configurations.create(CONFIGURATION_NAME) + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowExtension.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowExtension.groovy new file mode 100644 index 0000000..c7788f3 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowExtension.groovy @@ -0,0 +1,38 @@ +package org.xbib.gradle.plugin.shadow + +import org.gradle.api.Project +import org.gradle.api.artifacts.SelfResolvingDependency +import org.gradle.api.file.CopySpec +import org.gradle.api.publish.maven.MavenPom +import org.gradle.api.publish.maven.MavenPublication + +class ShadowExtension { + + CopySpec applicationDistribution + Project project + + ShadowExtension(Project project) { + this.project = project + applicationDistribution = project.copySpec {} + } + + void component(MavenPublication publication) { + + publication.artifact(project.tasks.shadowJar) + publication.pom { MavenPom pom -> + pom.withXml { xml -> + def dependenciesNode = xml.asNode().appendNode('dependencies') + + project.configurations.shadow.allDependencies.each { + if (! (it instanceof SelfResolvingDependency)) { + def dependencyNode = dependenciesNode.appendNode('dependency') + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + dependencyNode.appendNode('scope', 'runtime') + } + } + } + } + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowJavaPlugin.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowJavaPlugin.groovy new file mode 100644 index 0000000..793cc2a --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowJavaPlugin.groovy @@ -0,0 +1,58 @@ +package org.xbib.gradle.plugin.shadow + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginConvention +import org.gradle.configuration.project.ProjectConfigurationActionContainer +import org.xbib.gradle.plugin.shadow.tasks.ShadowJar + +import javax.inject.Inject + +class ShadowJavaPlugin implements Plugin { + + static final String SHADOW_JAR_TASK_NAME = 'shadowJar' + + static final String SHADOW_GROUP = 'Shadow' + + private final ProjectConfigurationActionContainer configurationActionContainer; + + @Inject + ShadowJavaPlugin(ProjectConfigurationActionContainer configurationActionContainer) { + this.configurationActionContainer = configurationActionContainer + } + + @Override + void apply(Project project) { + configureShadowTask(project) + project.configurations.compileClasspath.extendsFrom project.configurations.shadow + } + + protected void configureShadowTask(Project project) { + JavaPluginConvention convention = project.convention.getPlugin(JavaPluginConvention) + project.tasks.register(SHADOW_JAR_TASK_NAME, ShadowJar) { shadow -> + shadow.group = SHADOW_GROUP + shadow.description = 'Create a combined JAR of project and runtime dependencies' + shadow.archiveClassifier.set("all") + shadow.manifest.inheritFrom project.tasks.jar.manifest + def libsProvider = project.provider { -> [project.tasks.jar.manifest.attributes.get('Class-Path')] } + def files = project.objects.fileCollection().from { -> + project.configurations.findByName(ShadowBasePlugin.CONFIGURATION_NAME) + } + shadow.doFirst { + if (!files.empty) { + def libs = libsProvider.get() + libs.addAll files.collect { "${it.name}" } + manifest.attributes 'Class-Path': libs.findAll { it }.join(' ') + } + } + shadow.from(convention.sourceSets.main.output) + shadow.configurations = [project.configurations.findByName('runtimeClasspath') ? + project.configurations.runtimeClasspath : project.configurations.runtime] + shadow.exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class') + shadow.dependencies { + exclude(dependency(project.dependencies.gradleApi())) + } + project.artifacts.add(ShadowBasePlugin.CONFIGURATION_NAME, shadow) + } + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowPlugin.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowPlugin.groovy new file mode 100644 index 0000000..bbb1406 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowPlugin.groovy @@ -0,0 +1,31 @@ +package org.xbib.gradle.plugin.shadow + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPlugin +import org.xbib.gradle.plugin.shadow.tasks.ShadowJar + +class ShadowPlugin implements Plugin { + + @Override + void apply(Project project) { + project.plugins.apply(ShadowBasePlugin) + project.plugins.withType(JavaPlugin) { + project.plugins.apply(ShadowJavaPlugin) + } + def rootProject = project.rootProject + rootProject.plugins.withId('com.gradle.build-scan') { + rootProject.buildScan.buildFinished { + def shadowTasks = project.tasks.withType(ShadowJar) + shadowTasks.each { task -> + if (task.didWork) { + task.stats.buildScanData.each { k, v -> + rootProject.buildScan.value "shadow.${task.path}.${k}", v.toString() + } + rootProject.buildScan.value "shadow.${task.path}.configurations", task.configurations*.name.join(", ") + } + } + } + } + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowStats.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowStats.groovy new file mode 100644 index 0000000..22b592d --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/ShadowStats.groovy @@ -0,0 +1,81 @@ +package org.xbib.gradle.plugin.shadow + +import groovy.util.logging.Log +import org.gradle.api.GradleException + +@Log +class ShadowStats { + + long totalTime + long jarStartTime + long jarEndTime + int jarCount = 1 + boolean processingJar + Map relocations = [:] + + void relocate(String src, String dst) { + relocations[src] = dst + } + + String getRelocationString() { + def maxLength = relocations.keySet().collect { it.length() }.max() + relocations.collect { k, v -> "${k} ${separator(k, maxLength)} ${v}"}.sort().join("\n") + } + + String separator(String key, int max) { + return "→" + } + + void startJar() { + if (processingJar) throw new GradleException("Can only time one entry at a time") + processingJar = true + jarStartTime = System.currentTimeMillis() + } + + void finishJar() { + if (processingJar) { + jarEndTime = System.currentTimeMillis() + jarCount++ + totalTime += jarTiming + processingJar = false + } + } + + void printStats() { + println this + } + + long getJarTiming() { + jarEndTime - jarStartTime + } + + double getTotalTimeSecs() { + totalTime / 1000 + } + + double getAverageTimePerJar() { + totalTime / jarCount + } + + double getAverageTimeSecsPerJar() { + averageTimePerJar / 1000 + } + + String toString() { + StringBuilder sb = new StringBuilder() + sb.append "*******************\n" + sb.append "GRADLE SHADOW STATS\n" + sb.append "\n" + sb.append "Total Jars: $jarCount (includes project)\n" + sb.append "Total Time: ${totalTimeSecs}s [${totalTime}ms]\n" + sb.append "Average Time/Jar: ${averageTimeSecsPerJar}s [${averageTimePerJar}ms]\n" + sb.append "*******************" + } + + Map getBuildScanData() { + [ + dependencies: jarCount, + relocations: relocationString + ] + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/impl/RelocatorRemapper.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/impl/RelocatorRemapper.groovy new file mode 100644 index 0000000..28c9b7b --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/impl/RelocatorRemapper.groovy @@ -0,0 +1,94 @@ +package org.xbib.gradle.plugin.shadow.impl + +import org.objectweb.asm.commons.Remapper +import org.xbib.gradle.plugin.shadow.relocation.RelocateClassContext +import org.xbib.gradle.plugin.shadow.relocation.RelocatePathContext +import org.xbib.gradle.plugin.shadow.relocation.Relocator +import org.xbib.gradle.plugin.shadow.tasks.ShadowCopyAction +import org.xbib.gradle.plugin.shadow.ShadowStats + +import java.util.regex.Matcher +import java.util.regex.Pattern + +class RelocatorRemapper extends Remapper { + + private final Pattern classPattern = Pattern.compile("(\\[*)?L(.+)") + + List relocators + ShadowStats stats + + RelocatorRemapper(List relocators, ShadowStats stats) { + this.relocators = relocators + this.stats = stats + } + + boolean hasRelocators() { + return !relocators.empty + } + + Object mapValue(Object object) { + if (object instanceof String) { + String name = (String) object + String value = name + + String prefix = "" + String suffix = "" + + Matcher m = classPattern.matcher(name) + if (m.matches()) { + prefix = m.group(1) + "L" + suffix = "" + name = m.group(2) + } + + RelocateClassContext classContext = RelocateClassContext.builder().className(name).stats(stats).build() + RelocatePathContext pathContext = RelocatePathContext.builder().path(name).stats(stats).build() + for (Relocator r : relocators) { + if (r.canRelocateClass(classContext)) { + value = prefix + r.relocateClass(classContext) + suffix + break + } else if (r.canRelocatePath(pathContext)) { + value = prefix + r.relocatePath(pathContext) + suffix + break + } + } + + return value + } + + return super.mapValue(object) + } + + String map(String name) { + String value = name + + String prefix = "" + String suffix = "" + + Matcher m = classPattern.matcher(name) + if (m.matches()) { + prefix = m.group(1) + "L" + suffix = "" + name = m.group(2) + } + + RelocatePathContext pathContext = RelocatePathContext.builder().path(name).stats(stats).build() + for (Relocator r : relocators) { + if (r.canRelocatePath(pathContext)) { + value = prefix + r.relocatePath(pathContext) + suffix + break + } + } + + return value + } + + String mapPath(String path) { + map(path.substring(0, path.indexOf('.'))) + } + + String mapPath(ShadowCopyAction.RelativeArchivePath path) { + mapPath(path.pathString) + } + +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Clazz.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Clazz.groovy new file mode 100644 index 0000000..490c5b3 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Clazz.groovy @@ -0,0 +1,85 @@ +package org.xbib.gradle.plugin.shadow.internal + +class Clazz implements Comparable { + + private final Set dependencies = new HashSet() + + private final Set references = new HashSet() + + private final Set units = new HashSet() + + private final String name + + Clazz( final String pName ) { + name = pName; + } + + String getName() { + name + } + + void addClazzpathUnit(ClazzpathUnit pUnit ) { + units.add(pUnit) + } + + void removeClazzpathUnit( ClazzpathUnit pUnit ) { + units.remove(pUnit) + } + + Set getClazzpathUnits() { + units + } + + void addDependency( final Clazz pClazz ) { + pClazz.references.add(this) + dependencies.add(pClazz) + } + + public void removeDependency( final Clazz pClazz ) { + pClazz.references.remove(this) + dependencies.remove(pClazz) + } + + Set getDependencies() { + return dependencies + } + + Set getReferences() { + return references + } + + Set getTransitiveDependencies() { + final Set all = new HashSet(); + findTransitiveDependencies(all); + return all; + } + + void findTransitiveDependencies( final Set pAll ) { + for (Clazz clazz : dependencies) { + if (!pAll.contains(clazz)) { + pAll.add(clazz) + clazz.findTransitiveDependencies(pAll) + } + } + } + + boolean equals( final Object pO ) { + if (pO.getClass() != Clazz.class) { + return false + } + Clazz c = (Clazz) pO + name.equals(c.name) + } + + int hashCode() { + name.hashCode() + } + + int compareTo( final Clazz pO ) { + name.compareTo(((Clazz) pO).name) + } + + String toString() { + name + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Clazzpath.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Clazzpath.groovy new file mode 100644 index 0000000..7b74555 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Clazzpath.groovy @@ -0,0 +1,228 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.objectweb.asm.ClassReader + +import java.util.jar.JarEntry +import java.util.jar.JarInputStream +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Stream +import java.util.stream.StreamSupport + +class Clazzpath { + + private final Set units + + private final Map missing + + private final Map clazzes + + Clazzpath() { + units = new HashSet() + missing = new HashMap() + clazzes = new HashMap() + } + + boolean removeClazzpathUnit(ClazzpathUnit unit) { + Set unitClazzes = unit.getClazzes() + for (Clazz clazz : unitClazzes) { + clazz.removeClazzpathUnit(unit) + if (clazz.getClazzpathUnits().size() == 0) { + clazzes.remove(clazz.toString()) + } + } + units.remove(unit) + } + + ClazzpathUnit addClazzpathUnit(File file) throws IOException { + addClazzpathUnit(file.toPath()) + } + + ClazzpathUnit addClazzpathUnit(File file, String s) throws IOException { + addClazzpathUnit(file.toPath(), s) + } + + ClazzpathUnit addClazzpathUnit(Path path) throws IOException { + addClazzpathUnit(path, path.toString()) + } + + ClazzpathUnit addClazzpathUnit(Path path1, String s) throws IOException { + Path path = path1.toAbsolutePath() + if (Files.isRegularFile(path)) { + return addClazzpathUnit(Files.newInputStream(path), s) + } else if (Files.isDirectory(path)) { + String prefix = Utils.separatorsToUnix(Utils.normalize(path.toString() + File.separatorChar)) + List list = [] + path.traverse { p -> + if (Files.isRegularFile(p) && isValidResourceName(p.getFileName().toString())) { + list << new Resource(p.toString().substring(prefix.length())) { + @Override + InputStream getInputStream() throws IOException { + Files.newInputStream(p) + } + } + } + } + return addClazzpathUnit(list, s, true) + } + throw new IllegalArgumentException("neither file nor directory") + } + + ClazzpathUnit addClazzpathUnit(InputStream inputStream, String s) throws IOException { + final JarInputStream jarInputStream = new JarInputStream(inputStream) + try { + Stream stream = toEntryStream(jarInputStream).map { e -> e.getName() } + .filter { name -> isValidResourceName(name) } + .map { name -> new Resource(name) { + @Override + InputStream getInputStream() throws IOException { + jarInputStream + } + } + } + addClazzpathUnit(stream.&iterator, s, false) + } finally { + jarInputStream.close() + } + } + + ClazzpathUnit addClazzpathUnit(Iterable resources, String s, boolean shouldCloseResourceStream) + throws IOException { + Map unitClazzes = new HashMap() + Map unitDependencies = new HashMap() + ClazzpathUnit unit = new ClazzpathUnit(s, unitClazzes, unitDependencies) + for (Resource resource : resources) { + String clazzName = resource.name + Clazz clazz = getClazz(clazzName) + if (clazz == null) { + clazz = missing.get(clazzName) + if (clazz != null) { + // already marked missing + clazz = missing.remove(clazzName) + } else { + clazz = new Clazz(clazzName) + } + } + clazz.addClazzpathUnit(unit) + clazzes.put(clazzName, clazz) + unitClazzes.put(clazzName, clazz) + DependenciesClassRemapper dependenciesClassAdapter = new DependenciesClassRemapper() + InputStream inputStream = resource.getInputStream() + try { + new ClassReader(inputStream.readAllBytes()).accept(dependenciesClassAdapter, ClassReader.EXPAND_FRAMES | ClassReader.SKIP_DEBUG) + } finally { + if (shouldCloseResourceStream) { + inputStream.close() + } + } + Set depNames = dependenciesClassAdapter.getDependencies() + for (String depName : depNames) { + Clazz dep = getClazz(depName) + if (dep == null) { + // there is no such clazz yet + dep = missing.get(depName) + } + if (dep == null) { + // it is also not recorded to be missing + dep = new Clazz(depName) + dep.addClazzpathUnit(unit) + missing.put(depName, dep) + } + if (dep != clazz) { + unitDependencies.put(depName, dep) + clazz.addDependency(dep) + } + } + } + units.add(unit) + unit + } + + Set getClazzes() { + new HashSet(clazzes.values()) + } + + Set getClashedClazzes() { + Set all = new HashSet() + for (Clazz clazz : clazzes.values()) { + if (clazz.getClazzpathUnits().size() > 1) { + all.add(clazz) + } + } + all + } + + Set getMissingClazzes() { + new HashSet(missing.values()) + } + + Clazz getClazz(String clazzName) { + (Clazz) clazzes.get(clazzName) + } + + ClazzpathUnit[] getUnits() { + units.toArray(new ClazzpathUnit[units.size()]) + } + + private Stream toEntryStream(JarInputStream jarInputStream) { + StreamSupport.stream(Spliterators.spliteratorUnknownSize(new JarEntryIterator(jarInputStream), + Spliterator.IMMUTABLE), false) + } + + private class JarEntryIterator implements Iterator { + + JarInputStream jarInputStream + JarEntry entry + + JarEntryIterator(JarInputStream jarInputStream) { + this.jarInputStream = jarInputStream + this.entry = null + } + + @Override + boolean hasNext() { + try { + if (entry == null) { + entry = jarInputStream.getNextJarEntry() + } + return entry != null + } catch (IOException e) { + throw new RuntimeException(e) + } + } + + @Override + JarEntry next() { + try { + JarEntry result = entry != null ? entry : jarInputStream.getNextJarEntry() + entry = null + return result + } catch (IOException e) { + throw new RuntimeException(e) + } + } + } + + private static abstract class Resource { + + private static int ext = '.class'.length() + + String name + + Resource(String name) { + // foo/bar/Foo.class -> // foo.bar.Foo + this.name = name.substring(0, name.length() - ext).replace('/', '.') + } + + abstract InputStream getInputStream() throws IOException + + @Override + String toString() { + name + } + } + + private static boolean isValidResourceName(String name) { + (name != null) && name.endsWith('.class') && !name.contains('-') + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ClazzpathUnit.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ClazzpathUnit.groovy new file mode 100644 index 0000000..1430aab --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ClazzpathUnit.groovy @@ -0,0 +1,40 @@ +package org.xbib.gradle.plugin.shadow.internal + +class ClazzpathUnit { + + private final String s + + private final Map clazzes + + private final Map dependencies + + ClazzpathUnit(String s, Map clazzes, Map dependencies) { + this.s = s + this.clazzes = clazzes + this.dependencies = dependencies + } + + Set getClazzes() { + new HashSet(clazzes.values()) + } + + Clazz getClazz( final String pClazzName ) { + clazzes.get(pClazzName) + } + + Set getDependencies() { + new HashSet(dependencies.values()) + } + + Set getTransitiveDependencies() { + Set all = new HashSet() + for (Clazz clazz : clazzes.values()) { + clazz.findTransitiveDependencies(all) + } + all + } + + String toString() { + s + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DefaultDependencyFilter.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DefaultDependencyFilter.groovy new file mode 100644 index 0000000..adc5e4b --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DefaultDependencyFilter.groovy @@ -0,0 +1,126 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ResolvedDependency +import org.gradle.api.file.FileCollection +import org.gradle.api.specs.Spec +import org.gradle.api.specs.Specs + +class DefaultDependencyFilter implements DependencyFilter { + + private final Project project + + private final List> includeSpecs = [] + + private final List> excludeSpecs = [] + + DefaultDependencyFilter(Project project) { + this.project = project + } + + FileCollection resolve(Configuration configuration) { + Set includedDeps = [] + Set excludedDeps = [] + resolve(configuration.resolvedConfiguration.firstLevelModuleDependencies, includedDeps, excludedDeps) + project.files(configuration.files) - project.files(excludedDeps.collect { + it.moduleArtifacts*.file + }.flatten()) + } + + FileCollection resolve(Collection configurations) { + configurations.collect { + resolve(it) + }.sum() as FileCollection ?: project.files() + } + + /** + * Exclude dependencies that match the provided spec. + * + * @param spec + * @return + */ + DependencyFilter exclude(Spec spec) { + excludeSpecs << spec + this + } + + /** + * Include dependencies that match the provided spec. + * + * @param spec + * @return + */ + DependencyFilter include(Spec spec) { + includeSpecs << spec + return this + } + + /** + * Create a spec that matches the provided project notation on group, name, and version + * @param notation + * @return + */ + Spec project(Map notation) { + dependency(project.dependencies.project(notation)) + } + + /** + * Create a spec that matches the default configuration for the provided project path on group, name, and version + * + * @param notation + * @return + */ + Spec project(String notation) { + dependency(project.dependencies.project(path: notation, configuration: 'default')) + } + + /** + * Create a spec that matches dependencies using the provided notation on group, name, and version + * @param notation + * @return + */ + Spec dependency(Object notation) { + dependency(project.dependencies.create(notation)) + } + + /** + * Create a spec that matches the provided dependency on group, name, and version + * @param dependency + * @return + */ + Spec dependency(Dependency dependency) { + this.dependency({ ResolvedDependency it -> + (!dependency.group || it.moduleGroup.matches(dependency.group)) && + (!dependency.name || it.moduleName.matches(dependency.name)) && + (!dependency.version || it.moduleVersion.matches(dependency.version)) + }) + } + + /** + * Create a spec that matches the provided closure + * @param spec + * @return + */ + Spec dependency(Closure spec) { + return Specs.convertClosureToSpec(spec) + } + + + protected void resolve(Set dependencies, + Set includedDependencies, + Set excludedDependencies) { + dependencies.each { + if (isIncluded(it) ? includedDependencies.add(it) : excludedDependencies.add(it)) { + resolve(it.children, includedDependencies, excludedDependencies) + } + } + } + + protected boolean isIncluded(ResolvedDependency dependency) { + boolean include = includeSpecs.empty || includeSpecs.any { it.isSatisfiedBy(dependency) } + boolean exclude = !excludeSpecs.empty && excludeSpecs.any { it.isSatisfiedBy(dependency) } + return include && !exclude + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DefaultZipCompressor.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DefaultZipCompressor.groovy new file mode 100644 index 0000000..89ff0eb --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DefaultZipCompressor.groovy @@ -0,0 +1,28 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.gradle.api.UncheckedIOException +import org.xbib.gradle.plugin.shadow.zip.Zip64Mode +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +class DefaultZipCompressor implements ZipCompressor { + + private final int entryCompressionMethod + private final Zip64Mode zip64Mode + + DefaultZipCompressor(boolean allowZip64Mode, int entryCompressionMethod) { + this.entryCompressionMethod = entryCompressionMethod + zip64Mode = allowZip64Mode ? Zip64Mode.AsNeeded : Zip64Mode.Never + } + + @Override + ZipOutputStream createArchiveOutputStream(File destination) { + try { + ZipOutputStream zipOutputStream = new ZipOutputStream(destination) + zipOutputStream.setUseZip64(zip64Mode) + zipOutputStream.setMethod(entryCompressionMethod) + return zipOutputStream + } catch (Exception e) { + throw new UncheckedIOException("unable to create ZIP output stream for file " + destination, e) + } + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependenciesClassRemapper.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependenciesClassRemapper.groovy new file mode 100644 index 0000000..324de10 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependenciesClassRemapper.groovy @@ -0,0 +1,144 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.objectweb.asm.AnnotationVisitor +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.FieldVisitor +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.TypePath +import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.commons.Remapper + +class DependenciesClassRemapper extends ClassRemapper { + + private static final AnnotationVisitor annotationVisitor = new MyAnnotationVisitor() + + private static final MethodVisitor methodVisitor = new MyMethodVisitor() + + private static final FieldVisitor fieldVisitor = new MyFieldVisitor() + + private static final ClassVisitor classVisitor = new MyClassVisitor() + + DependenciesClassRemapper() { + super(classVisitor, new CollectingRemapper()) + } + + Set getDependencies() { + return ((CollectingRemapper) super.remapper).classes + } + + private static class CollectingRemapper extends Remapper { + Set classes = new HashSet() + + @Override + String map(String className) { + classes.add(className.replace('/', '.')) + className + } + } + + private static class MyAnnotationVisitor extends AnnotationVisitor { + + MyAnnotationVisitor() { + super(Opcodes.ASM7) + } + + @Override + AnnotationVisitor visitAnnotation(String name, String desc) { + this + } + + @Override + AnnotationVisitor visitArray(String name) { + this + } + } + + private static class MyMethodVisitor extends MethodVisitor { + + MyMethodVisitor() { + super(Opcodes.ASM7) + } + + @Override + AnnotationVisitor visitAnnotationDefault() { + annotationVisitor + } + + @Override + AnnotationVisitor visitAnnotation(String desc, boolean visible) { + annotationVisitor + } + + @Override + AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { + annotationVisitor + } + + @Override + AnnotationVisitor visitInsnAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + annotationVisitor + } + + @Override + AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, + Label[] end, int[] index, String descriptor, + boolean visible) { + annotationVisitor + } + + @Override + AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible ) { + annotationVisitor + } + + @Override + AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible ) { + annotationVisitor + } + } + + private static class MyFieldVisitor extends FieldVisitor { + + MyFieldVisitor() { + super(Opcodes.ASM7) + } + + @Override + AnnotationVisitor visitAnnotation(String desc, boolean visible) { + annotationVisitor + } + @Override + AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) { + annotationVisitor + } + } + + private static class MyClassVisitor extends ClassVisitor { + + MyClassVisitor() { + super(Opcodes.ASM7); + } + + @Override + AnnotationVisitor visitAnnotation(String desc, boolean visible) { + annotationVisitor + } + + @Override + FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + fieldVisitor + } + + @Override + MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + methodVisitor + } + + @Override + AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible ) { + annotationVisitor + } + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyFilter.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyFilter.groovy new file mode 100644 index 0000000..63425cd --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyFilter.groovy @@ -0,0 +1,76 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ResolvedDependency +import org.gradle.api.file.FileCollection +import org.gradle.api.specs.Spec + +interface DependencyFilter { + + /** + * Resolve a Configuration against the include/exclude rules in the filter + * @param configuration + * @return + */ + FileCollection resolve(Configuration configuration) + + /** + * Resolve all Configurations against the include/exclude ruels in the filter and combine the results + * @param configurations + * @return + */ + FileCollection resolve(Collection configurations) + + /** + * Exclude dependencies that match the provided spec. + * + * @param spec + * @return + */ + DependencyFilter exclude(Spec spec) + + /** + * Include dependencies that match the provided spec. + * + * @param spec + * @return + */ + DependencyFilter include(Spec spec) + + /** + * Create a spec that matches the provided project notation on group, name, and version + * @param notation + * @return + */ + Spec project(Map notation) + + /** + * Create a spec that matches the default configuration for the provided project path on group, name, and version + * + * @param notation + * @return + */ + Spec project(String notation) + + /** + * Create a spec that matches dependencies using the provided notation on group, name, and version + * @param notation + * @return + */ + Spec dependency(Object notation) + + /** + * Create a spec that matches the provided dependency on group, name, and version + * @param dependency + * @return + */ + Spec dependency(Dependency dependency) + + /** + * Create a spec that matches the provided closure + * @param spec + * @return + */ + Spec dependency(Closure spec) +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyUtils.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyUtils.groovy new file mode 100644 index 0000000..9c1b956 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyUtils.groovy @@ -0,0 +1,44 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.objectweb.asm.ClassReader + +import java.util.jar.JarEntry +import java.util.jar.JarInputStream + +class DependencyUtils { + + static Collection getDependenciesOfJar(InputStream is ) throws IOException { + Set dependencies = [] + JarInputStream inputStream = new JarInputStream(is) + inputStream.withCloseable { + while (true) { + JarEntry entry = inputStream.getNextJarEntry() + if (entry == null) { + break + } + if (entry.isDirectory()) { + inputStream.readAllBytes() + continue + } + if (entry.getName().endsWith('.class')) { + DependenciesClassRemapper dependenciesClassAdapter = new DependenciesClassRemapper() + new ClassReader(inputStream.readAllBytes()).accept(dependenciesClassAdapter, 0) + dependencies.addAll(dependenciesClassAdapter.getDependencies()) + } else { + inputStream.readAllBytes() + } + } + } + dependencies + } + + static Collection getDependenciesOfClass(InputStream is) throws IOException { + final DependenciesClassRemapper dependenciesClassAdapter = new DependenciesClassRemapper() + new ClassReader(is.readAllBytes()).accept(dependenciesClassAdapter, ClassReader.EXPAND_FRAMES) + dependenciesClassAdapter.getDependencies() + } + + static Collection getDependenciesOfClass(Class clazz) throws IOException { + getDependenciesOfClass(clazz.getResourceAsStream('/' + clazz.getName().replace('.', '/') + '.class')) + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ServiceStream.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ServiceStream.groovy new file mode 100644 index 0000000..e69de29 diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/StringBuilderWriter.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/StringBuilderWriter.groovy new file mode 100644 index 0000000..052aab1 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/StringBuilderWriter.groovy @@ -0,0 +1,142 @@ +package org.xbib.gradle.plugin.shadow.internal + +/** + * {@link Writer} implementation that outputs to a {@link StringBuilder}. + *

+ * NOTE: This implementation, as an alternative to + * java.io.StringWriter, provides an un-synchronized + * (i.e. for use in a single thread) implementation for better performance. + * For safe usage with multiple {@link Thread}s then + * java.io.StringWriter should be used. + */ +class StringBuilderWriter extends Writer { + + private final StringBuilder builder + + /** + * Constructs a new {@link StringBuilder} instance with default capacity. + */ + StringBuilderWriter() { + this.builder = new StringBuilder() + } + + /** + * Constructs a new {@link StringBuilder} instance with the specified capacity. + * + * @param capacity The initial capacity of the underlying {@link StringBuilder} + */ + StringBuilderWriter(int capacity) { + this.builder = new StringBuilder(capacity) + } + + /** + * Constructs a new instance with the specified {@link StringBuilder}. + * + *

If {@code builder} is null a new instance with default capacity will be created.

+ * + * @param builder The String builder. May be null. + */ + StringBuilderWriter(StringBuilder builder) { + this.builder = builder != null ? builder : new StringBuilder() + } + + /** + * Appends a single character to this Writer. + * + * @param value The character to append + * @return This writer instance + */ + @Override + Writer append(char value) { + builder.append(value) + this + } + + /** + * Appends a character sequence to this Writer. + * + * @param value The character to append + * @return This writer instance + */ + @Override + Writer append(CharSequence value) { + builder.append(value) + this + } + + /** + * Appends a portion of a character sequence to the {@link StringBuilder}. + * + * @param value The character to append + * @param start The index of the first character + * @param end The index of the last character + 1 + * @return This writer instance + */ + @Override + Writer append(CharSequence value, int start, int end) { + builder.append(value, start, end) + this + } + + /** + * Closing this writer has no effect. + */ + @Override + void close() { + // no-op + } + + /** + * Flushing this writer has no effect. + */ + @Override + void flush() { + // no-op + } + + + /** + * Writes a String to the {@link StringBuilder}. + * + * @param value The value to write + */ + @Override + void write(String value) { + if (value != null) { + builder.append(value) + } + } + + /** + * Writes a portion of a character array to the {@link StringBuilder}. + * + * @param value The value to write + * @param offset The index of the first character + * @param length The number of characters to write + */ + @Override + void write(char[] value, int offset, int length) { + if (value != null) { + builder.append(value, offset, length) + } + } + + /** + * Returns the underlying builder. + * + * @return The underlying builder + */ + StringBuilder getBuilder() { + builder + } + + /** + * Returns {@link StringBuilder#toString()}. + * + * @return The contents of the String builder. + */ + @Override + String toString() { + builder.toString() + } +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/UnusedTracker.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/UnusedTracker.groovy new file mode 100644 index 0000000..bb1144e --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/UnusedTracker.groovy @@ -0,0 +1,44 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.gradle.api.Project +import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.SourceSet + +class UnusedTracker { + + private final FileCollection toMinimize + + private final List projectUnits + + private final Clazzpath clazzPath + + private UnusedTracker(List classDirs, FileCollection toMinimize) { + this.toMinimize = toMinimize + this.clazzPath = new Clazzpath() + this.projectUnits = classDirs.collect { clazzPath.addClazzpathUnit(it) } + } + + Set findUnused() { + Set unused = clazzPath.clazzes + for (cpu in projectUnits) { + unused.removeAll(cpu.clazzes) + unused.removeAll(cpu.transitiveDependencies) + } + unused.collect { it.name }.toSet() + } + + void addDependency(File jarOrDir) { + if (toMinimize.contains(jarOrDir)) { + clazzPath.addClazzpathUnit(jarOrDir) + } + } + + static UnusedTracker forProject(Project project, FileCollection toMinimize) { + final List classDirs = new ArrayList<>() + for (SourceSet sourceSet in project.sourceSets) { + Iterable classesDirs = sourceSet.output.classesDirs + classDirs.addAll(classesDirs.findAll { it.isDirectory() }) + } + return new UnusedTracker(classDirs, toMinimize) + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Utils.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Utils.groovy new file mode 100644 index 0000000..19a2ac4 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/Utils.groovy @@ -0,0 +1,629 @@ +package org.xbib.gradle.plugin.shadow.internal + +import java.nio.charset.Charset +import java.nio.file.Files +import java.nio.file.StandardOpenOption + +class Utils { + + private static final String EMPTY_STRING = "" + + private static final Character EXTENSION_SEPARATOR = '.' as Character + + private static final Character UNIX_SEPARATOR = '/' as Character + + private static final Character WINDOWS_SEPARATOR = '\\' as Character + + private static final Character SYSTEM_SEPARATOR = File.separatorChar as Character + + private static final int NOT_FOUND = -1 + + private static final char OTHER_SEPARATOR + + static { + if (SYSTEM_SEPARATOR == WINDOWS_SEPARATOR) { + OTHER_SEPARATOR = UNIX_SEPARATOR + } else { + OTHER_SEPARATOR = WINDOWS_SEPARATOR + } + } + + static String separatorsToUnix(String path) { + if (path == null || path.indexOf(WINDOWS_SEPARATOR as String) == NOT_FOUND) { + return path + } + path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR) + } + + static String getExtension(String fileName) throws IllegalArgumentException { + if (fileName == null) { + return null + } + int index = indexOfExtension(fileName) + if (index == NOT_FOUND) { + return EMPTY_STRING + } + return fileName.substring(index + 1) + } + + static int indexOfExtension(String fileName) throws IllegalArgumentException { + if (fileName == null) { + return NOT_FOUND + } + if (SYSTEM_SEPARATOR == WINDOWS_SEPARATOR) { + int offset = fileName.indexOf(':' as String, getAdsCriticalOffset(fileName)) + if (offset != -1) { + throw new IllegalArgumentException("NTFS ADS separator (':') in file name is forbidden.") + } + } + int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR as String) + int lastSeparator = indexOfLastSeparator(fileName) + lastSeparator > extensionPos ? NOT_FOUND : extensionPos + } + + static int getAdsCriticalOffset(String fileName) { + int offset1 = fileName.lastIndexOf(SYSTEM_SEPARATOR as String) + int offset2 = fileName.lastIndexOf(OTHER_SEPARATOR as String) + if (offset1 == -1) { + if (offset2 == -1) { + return 0 + } + return offset2 + 1 + } + if (offset2 == -1) { + return offset1 + 1 + } + return Math.max(offset1, offset2) + 1 + } + + static int indexOfLastSeparator(String fileName) { + if (fileName == null) { + return NOT_FOUND + } + int lastUnixPos = fileName.lastIndexOf(UNIX_SEPARATOR as String) + int lastWindowsPos = fileName.lastIndexOf(WINDOWS_SEPARATOR as String) + return Math.max(lastUnixPos, lastWindowsPos) + } + + static String normalize(String fileName) { + doNormalize(fileName, SYSTEM_SEPARATOR, true) + } + + private static String doNormalize(String fileName, char separator, boolean keepSeparator) { + if (fileName == null) { + return null + } + failIfNullBytePresent(fileName) + int size = fileName.length() + if (size == 0) { + return fileName + } + int prefix = getPrefixLength(fileName) + if (prefix < 0) { + return null + } + char[] array = new char[size + 2] + fileName.getChars(0, fileName.length(), array, 0) + char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR + for (int i = 0; i < array.length; i++) { + if (array[i] == otherSeparator) { + array[i] = separator + } + } + boolean lastIsDirectory = true + if (array[size - 1] != separator) { + array[size++] = separator + lastIsDirectory = false + } + for (int i = prefix + 1; i < size; i++) { + if (array[i] == separator && array[i - 1] == separator) { + System.arraycopy(array, i, array, i - 1, size - i) + size-- + i-- + } + } + char dot = '.' as char + for (int i = prefix + 1; i < size; i++) { + if (array[i] == separator && array[i - 1] == dot && + (i == prefix + 1 || array[i - 2] == separator)) { + if (i == size - 1) { + lastIsDirectory = true + } + System.arraycopy(array, i + 1, array, i - 1, size - i) + size -=2 + i-- + } + } + outer: + for (int i = prefix + 2; i < size; i++) { + if (array[i] == separator && array[i - 1] == dot && array[i - 2] == dot && + (i == prefix + 2 || array[i - 3] == separator)) { + if (i == prefix + 2) { + return null + } + if (i == size - 1) { + lastIsDirectory = true + } + int j + for (j = i - 4 ; j >= prefix; j--) { + if (array[j] == separator) { + System.arraycopy(array, i + 1, array, j + 1, size - i) + size -= i - j + i = j + 1 + continue outer + } + } + System.arraycopy(array, i + 1, array, prefix, size - i) + size -= i + 1 - prefix + i = prefix + 1 + } + } + if (size <= 0) { + return EMPTY_STRING + } + if (size <= prefix) { + return new String(array, 0, size) + } + if (lastIsDirectory && keepSeparator) { + return new String(array, 0, size) + } + new String(array, 0, size - 1) + } + + static int getPrefixLength(String fileName) { + if (fileName == null) { + return NOT_FOUND + } + int len = fileName.length() + if (len == 0) { + return 0 + } + char ch0 = fileName.charAt(0) as char + if (ch0 == ':' as char) { + return NOT_FOUND + } + if (len == 1) { + if (ch0 == '~' as char) { + return 2 + } + return isSeparator(ch0) ? 1 : 0 + } + if (ch0 == '~' as char) { + int posUnix = fileName.indexOf(UNIX_SEPARATOR as String, 1) + int posWin = fileName.indexOf(WINDOWS_SEPARATOR as String, 1) + if (posUnix == NOT_FOUND && posWin == NOT_FOUND) { + return len + 1 + } + posUnix = posUnix == NOT_FOUND ? posWin : posUnix + posWin = posWin == NOT_FOUND ? posUnix : posWin + return Math.min(posUnix, posWin) + 1 + } + char ch1 = fileName.charAt(1) as char + if (ch1 == ':' as char) { + ch0 = Character.toUpperCase(ch0) + if (ch0 >= ('A' as char) && ch0 <= ('Z' as char)) { + if (len == 2 || !isSeparator(fileName.charAt(2))) { + return 2 + } + return 3 + } else if (ch0 == UNIX_SEPARATOR) { + return 1 + } + return NOT_FOUND + + } else if (isSeparator(ch0) && isSeparator(ch1)) { + int posUnix = fileName.indexOf(UNIX_SEPARATOR as String, 2) + int posWin = fileName.indexOf(WINDOWS_SEPARATOR as String, 2) + if (posUnix == NOT_FOUND && posWin == NOT_FOUND || posUnix == 2 || posWin == 2) { + return NOT_FOUND + } + posUnix = posUnix == NOT_FOUND ? posWin : posUnix + posWin = posWin == NOT_FOUND ? posUnix : posWin + int pos = Math.min(posUnix, posWin) + 1 + String hostnamePart = fileName.substring(2, pos - 1) + return isValidHostName(hostnamePart) ? pos : NOT_FOUND + } else { + return isSeparator(ch0) ? 1 : 0 + } + } + + static boolean isSeparator(char ch) { + return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR + } + + private static void failIfNullBytePresent(String path) { + int len = path.length() + for (int i = 0; i < len; i++) { + if (path.charAt(i) == 0 as char) { + throw new IllegalArgumentException("Null byte present in file/path name. There are no " + + "known legitimate use cases for such data, but several injection attacks may use it") + } + } + } + + static String removeExtension(String fileName) { + if (fileName == null) { + return null + } + failIfNullBytePresent(fileName) + int index = indexOfExtension(fileName) + if (index == NOT_FOUND) { + return fileName + } + fileName.substring(0, index) + } + + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4 + + static long copyLarge(Reader input, Writer output) throws IOException { + return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]) + } + + static long copyLarge(Reader input, Writer output, final char[] buffer) throws IOException { + long count = 0 + int n + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n) + count += n + } + count + } + + static long copyLarge(InputStream input, OutputStream output) throws IOException { + return copy(input, output, DEFAULT_BUFFER_SIZE); + } + + static long copy(InputStream input, OutputStream output, int bufferSize) throws IOException { + return copyLarge(input, output, new byte[bufferSize]) + } + + static long copyLarge(InputStream input, OutputStream output, byte[] buffer) throws IOException { + long count = 0 + int n + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n) + count += n + } + return count + } + + static void writeStringToFile(File file, String data, Charset encoding) throws IOException { + writeStringToFile(file, data, encoding, false) + } + + static void writeStringToFile(File file, String data, Charset encoding, boolean append) throws IOException { + OutputStream out = append ? + Files.newOutputStream(file.toPath(), StandardOpenOption.APPEND) : Files.newOutputStream(file.toPath()) + out.withCloseable { outputStream -> + write(data, outputStream, encoding) + } + } + + static void write(String data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + output.write(data.getBytes(encoding)) + } + } + + static String readFileToString(File file, Charset encoding) throws IOException { + InputStream inputStream = Files.newInputStream(file.toPath()) + inputStream.withCloseable { is -> + return toString(is, encoding) + } + } + + static String toString(InputStream input, Charset encoding) throws IOException { + StringBuilderWriter sw = new StringBuilderWriter() + sw.withCloseable { writer -> + copyLarge(new InputStreamReader(input, encoding), writer) + return sw.toString() + } + } + + static byte[] readFileToByteArray(File file) throws IOException { + InputStream inputStream = Files.newInputStream(file.toPath()) + inputStream.withCloseable { is -> + long fileLength = file.length() + return fileLength > 0 ? toByteArray(is, fileLength) : toByteArray(is) + } + } + + static byte[] toByteArray(InputStream input, long size) throws IOException { + if (size > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size) + } + return toByteArray(input, (int) size) + } + + static byte[] toByteArray(InputStream input, int size) throws IOException { + if (size < 0) { + throw new IllegalArgumentException("Size must be equal or greater than zero: " + size) + } + if (size == 0) { + return new byte[0] + } + byte[] data = new byte[size] + int offset = 0 + int read = 0 + while (offset < size && (read = input.read(data, offset, size - offset)) != -1) { + offset += read + } + if (offset != size) { + throw new IOException("Unexpected read size. current: " + offset + ", expected: " + size) + } + data + } + + static byte[] toByteArray(InputStream inputStream) throws IOException { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream() + outputStream.withCloseable { + copyLarge(inputStream, outputStream) + return outputStream.toByteArray() + } + } + + private static final String PATTERN_HANDLER_PREFIX = "[" + + private static final String PATTERN_HANDLER_SUFFIX = "]" + + private static final String REGEX_HANDLER_PREFIX = "%regex" + PATTERN_HANDLER_PREFIX + + private static final String ANT_HANDLER_PREFIX = "%ant" + PATTERN_HANDLER_PREFIX + + static boolean matchPath(String pattern, String str, boolean isCaseSensitive) { + matchPath(pattern, str, File.separator, isCaseSensitive) + } + + static boolean matchPath(String pattern, String str, String separator, boolean isCaseSensitive) { + if (isRegexPrefixedPattern(pattern)) { + pattern = pattern.substring(REGEX_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length()) + return str.matches(pattern) + } else { + if (isAntPrefixedPattern(pattern)) { + pattern = pattern.substring(ANT_HANDLER_PREFIX.length(), + pattern.length() - PATTERN_HANDLER_SUFFIX.length()) + } + + return matchAntPathPattern(pattern, str, separator, isCaseSensitive) + } + } + + static boolean isRegexPrefixedPattern(String pattern) { + pattern.length() > (REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1) && + pattern.startsWith(REGEX_HANDLER_PREFIX) && pattern.endsWith( PATTERN_HANDLER_SUFFIX) + } + + static boolean isAntPrefixedPattern(String pattern) { + pattern.length() > (ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1) && + pattern.startsWith(ANT_HANDLER_PREFIX) && pattern.endsWith(PATTERN_HANDLER_SUFFIX) + } + + static boolean matchAntPathPattern(String pattern, String str, String separator, boolean isCaseSensitive) { + if (separatorPatternStartSlashMismatch(pattern, str, separator)) { + return false + } + List patDirs = tokenizePathToString(pattern, separator) + List strDirs = tokenizePathToString(str, separator) + matchAntPathPattern(patDirs, strDirs, isCaseSensitive) + } + + static boolean separatorPatternStartSlashMismatch(String pattern, String str, String separator) { + str.startsWith(separator) != pattern.startsWith(separator) + } + + static List tokenizePathToString(String path, String separator) { + List ret = [] + StringTokenizer st = new StringTokenizer(path, separator) + while (st.hasMoreTokens()) { + ret.add(st.nextToken()) + } + ret + } + + static boolean matchAntPathPattern(List patDirs, List strDirs, boolean isCaseSensitive) { + int patIdxStart = 0 + int patIdxEnd = patDirs.size() - 1 + int strIdxStart = 0 + int strIdxEnd = strDirs.size() - 1 + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = patDirs.get(patIdxStart) + if (patDir.equals("**")) { + break + } + if (!match(patDir, strDirs.get(strIdxStart), isCaseSensitive)) { + return false + } + patIdxStart++ + strIdxStart++ + } + if (strIdxStart > strIdxEnd) { + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!patDirs.get(i).equals( "**")) { + return false + } + } + return true + } else { + if (patIdxStart > patIdxEnd) { + return false + } + } + while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { + String patDir = patDirs.get(patIdxEnd) + if (patDir.equals("**")) { + break + } + if (!match(patDir, strDirs.get(strIdxEnd), isCaseSensitive)) { + return false + } + patIdxEnd-- + strIdxEnd-- + } + if (strIdxStart > strIdxEnd) { + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!patDirs.get(i).equals("**")) { + return false + } + } + return true + } + while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { + int patIdxTmp = -1 + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if (patDirs.get(i).equals("**")) { + patIdxTmp = i + break + } + } + if (patIdxTmp == patIdxStart + 1) { + patIdxStart++ + continue + } + int patLength = (patIdxTmp - patIdxStart - 1) + int strLength = (strIdxEnd - strIdxStart + 1) + int foundIdx = -1; + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + String subPat = patDirs.get(patIdxStart + j + 1) + String subStr = strDirs.get(strIdxStart + i + j) + if (!match(subPat, subStr, isCaseSensitive)) { + continue strLoop + } + } + foundIdx = strIdxStart + i + break + } + if (foundIdx == -1) { + return false + } + patIdxStart = patIdxTmp + strIdxStart = foundIdx + patLength + } + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (!patDirs.get(i).equals("**")) { + return false + } + } + return true + } + + static boolean match(String pattern, String str, boolean isCaseSensitive) { + char[] patArr = pattern.toCharArray() + char[] strArr = str.toCharArray() + match(patArr, strArr, isCaseSensitive) + } + + static boolean match(char[] patArr, char[] strArr, boolean isCaseSensitive) { + int patIdxStart = 0 + int patIdxEnd = patArr.length - 1 + int strIdxStart = 0 + int strIdxEnd = strArr.length - 1 + char ch + + boolean containsStar = false + for (char aPatArr : patArr) { + if (aPatArr == ('*' as char)) { + containsStar = true + break + } + } + if (!containsStar) { + if (patIdxEnd != strIdxEnd) { + return false + } + for (int i = 0; i <= patIdxEnd; i++) { + ch = patArr[i] + if ( ch != ('?' as char) && !equals(ch, strArr[i], isCaseSensitive)) { + return false + } + } + return true + } + if (patIdxEnd == 0) { + return true + } + while ((ch = patArr[patIdxStart]) != ('*' as char) && strIdxStart <= strIdxEnd) { + if ( ch != ('?' as char) && !equals( ch, strArr[strIdxStart], isCaseSensitive)) { + return false + } + patIdxStart++ + strIdxStart++ + } + if (strIdxStart > strIdxEnd) { + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if ( patArr[i] != ('*' as char)) { + return false + } + } + return true + } + while ((ch = patArr[patIdxEnd] ) != ('*' as char) && strIdxStart <= strIdxEnd) { + if (ch != ('?' as char) && !equals( ch, strArr[strIdxEnd], isCaseSensitive)) { + return false + } + patIdxEnd-- + strIdxEnd-- + } + if (strIdxStart > strIdxEnd) { + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if ( patArr[i] != ('*' as char)) { + return false + } + } + return true + } + while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { + int patIdxTmp = -1 + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if ( patArr[i] == ('*' as char)) { + patIdxTmp = i + break + } + } + if (patIdxTmp == patIdxStart + 1) { + patIdxStart++ + continue + } + int patLength = ( patIdxTmp - patIdxStart - 1 ) + int strLength = ( strIdxEnd - strIdxStart + 1 ) + int foundIdx = -1 + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + ch = patArr[patIdxStart + j + 1] + if (ch != ('?' as char) && !equals( ch, strArr[strIdxStart + i + j], isCaseSensitive)) { + continue strLoop + } + } + foundIdx = strIdxStart + i + break + } + if (foundIdx == -1) { + return false + } + patIdxStart = patIdxTmp + strIdxStart = foundIdx + patLength + } + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (patArr[i] != ('*' as char)) { + return false + } + } + return true + } + + static boolean equals(char c1, char c2, boolean isCaseSensitive) { + if (c1 == c2) { + return true + } + if (!isCaseSensitive) { + if (Character.toUpperCase(c1) == Character.toUpperCase(c2) + || Character.toLowerCase(c1) == Character.toLowerCase(c2)) { + return true + } + } + return false + } + +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ZipCompressor.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ZipCompressor.groovy new file mode 100644 index 0000000..2c37b9f --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/internal/ZipCompressor.groovy @@ -0,0 +1,11 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.gradle.api.internal.file.archive.compression.ArchiveOutputStreamFactory +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +interface ZipCompressor extends ArchiveOutputStreamFactory { + + @Override + ZipOutputStream createArchiveOutputStream(File destination) + +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/RelocateClassContext.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/RelocateClassContext.groovy new file mode 100644 index 0000000..a1339a0 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/RelocateClassContext.groovy @@ -0,0 +1,14 @@ +package org.xbib.gradle.plugin.shadow.relocation + +import org.xbib.gradle.plugin.shadow.ShadowStats +import groovy.transform.Canonical +import groovy.transform.builder.Builder + +@Canonical +@Builder +class RelocateClassContext { + + String className + ShadowStats stats + +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/RelocatePathContext.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/RelocatePathContext.groovy new file mode 100644 index 0000000..d94d841 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/RelocatePathContext.groovy @@ -0,0 +1,13 @@ +package org.xbib.gradle.plugin.shadow.relocation + +import org.xbib.gradle.plugin.shadow.ShadowStats +import groovy.transform.Canonical +import groovy.transform.builder.Builder + +@Canonical +@Builder +class RelocatePathContext { + + String path + ShadowStats stats +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/Relocator.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/Relocator.groovy new file mode 100644 index 0000000..d914a62 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/Relocator.groovy @@ -0,0 +1,17 @@ +package org.xbib.gradle.plugin.shadow.relocation + +/** + * Modified from org.apache.maven.plugins.shade.relocation.Relocator + */ +interface Relocator { + + boolean canRelocatePath(RelocatePathContext context) + + String relocatePath(RelocatePathContext context) + + boolean canRelocateClass(RelocateClassContext context) + + String relocateClass(RelocateClassContext context) + + String applyToSourceContent(String sourceContent) +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocator.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocator.groovy new file mode 100644 index 0000000..eb6b71e --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocator.groovy @@ -0,0 +1,156 @@ +package org.xbib.gradle.plugin.shadow.relocation + +import org.xbib.gradle.plugin.shadow.internal.Utils + +import java.util.regex.Pattern + +/** + * Modified from org.apache.maven.plugins.shade.relocation.SimpleRelocator + */ +class SimpleRelocator implements Relocator { + + private final String pattern + + private final String pathPattern + + private final String shadedPattern + + private final String shadedPathPattern + + private final Set includes + + private final Set excludes + + private final boolean rawString + + SimpleRelocator(String patt, String shadedPattern, List includes, List excludes) { + this(patt, shadedPattern, includes, excludes, false) + } + + SimpleRelocator(String patt, String shadedPattern, List includes, List excludes, + boolean rawString) { + this.rawString = rawString + + if (rawString) { + this.pathPattern = patt + this.shadedPathPattern = shadedPattern + + this.pattern = null // not used for raw string relocator + this.shadedPattern = null // not used for raw string relocator + } else { + if (patt == null) { + this.pattern = "" + this.pathPattern = "" + } else { + this.pattern = patt.replace('/', '.') + this.pathPattern = patt.replace('.', '/') + } + + if (shadedPattern != null) { + this.shadedPattern = shadedPattern.replace('/', '.') + this.shadedPathPattern = shadedPattern.replace('.', '/') + } else { + this.shadedPattern = "hidden." + this.pattern + this.shadedPathPattern = "hidden/" + this.pathPattern + } + } + + this.includes = normalizePatterns(includes) + this.excludes = normalizePatterns(excludes) + } + + SimpleRelocator include(String pattern) { + this.includes.addAll normalizePatterns([pattern]) + return this + } + + SimpleRelocator exclude(String pattern) { + this.excludes.addAll normalizePatterns([pattern]) + return this + } + + private static Set normalizePatterns(Collection patterns) { + Set normalized = null + if (patterns != null && !patterns.isEmpty()) { + normalized = new LinkedHashSet() + for (String pattern : patterns) { + String classPattern = pattern.replace('.', '/') + normalized.add(classPattern) + if (classPattern.endsWith("/*")) { + String packagePattern = classPattern.substring(0, classPattern.lastIndexOf('/')) + normalized.add(packagePattern) + } + } + } + return normalized ?: [] + } + + private boolean isIncluded(String path) { + if (includes != null && !includes.isEmpty()) { + for (String include : includes) { + if (Utils.matchPath(include, path, true)) { + return true + } + } + return false + } + return true + } + + private boolean isExcluded(String path) { + if (excludes != null && !excludes.isEmpty()) { + for (String exclude : excludes) { + if (Utils.matchPath(exclude, path, true)) { + return true + } + } + } + return false + } + + boolean canRelocatePath(RelocatePathContext context) { + String path = context.path + if (rawString) { + return Pattern.compile(pathPattern).matcher(path).find() + } + if (path.endsWith(".class")) { + path = path.substring(0, path.length() - 6) + } + if (!isIncluded(path) || isExcluded(path)) { + return false + } + // Allow for annoying option of an extra / on the front of a path. + // See MSHADE-119 comes from getClass().getResource("/a/b/c.properties"). + return path.startsWith(pathPattern) || path.startsWith("/" + pathPattern) + } + + boolean canRelocateClass(RelocateClassContext context) { + String clazz = context.className + RelocatePathContext pathContext = RelocatePathContext.builder().path(clazz.replace('.', '/')).stats(context.stats).build() + return !rawString && clazz.indexOf('/') < 0 && canRelocatePath(pathContext) + } + + String relocatePath(RelocatePathContext context) { + String path = context.path + context.stats.relocate(pathPattern, shadedPathPattern) + if (rawString) { + return path.replaceAll(pathPattern, shadedPathPattern) + } else { + return path.replaceFirst(pathPattern, shadedPathPattern) + } + } + + String relocateClass(RelocateClassContext context) { + String clazz = context.className + context.stats.relocate(pathPattern, shadedPathPattern) + return clazz.replaceFirst(pattern, shadedPattern) + } + + String applyToSourceContent(String sourceContent) { + if (rawString) { + return sourceContent + } else { + return sourceContent.replaceAll("\\b" + pattern, shadedPattern) + } + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ConfigureShadowRelocation.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ConfigureShadowRelocation.groovy new file mode 100644 index 0000000..9993a93 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ConfigureShadowRelocation.groovy @@ -0,0 +1,50 @@ +package org.xbib.gradle.plugin.shadow.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction + +import java.util.jar.JarFile + +class ConfigureShadowRelocation extends DefaultTask { + + @Input + ShadowJar target + + @Input + String prefix = "shadow" + + @InputFiles @Optional + List getConfigurations() { + return target.configurations + } + + @TaskAction + void configureRelocation() { + def packages = [] as Set + configurations.each { configuration -> + configuration.files.each { jar -> + JarFile jf = new JarFile(jar) + jf.entries().each { entry -> + if (entry.name.endsWith(".class")) { + packages << entry.name[0..entry.name.lastIndexOf('/')-1].replaceAll('/', '.') + } + } + jf.close() + } + } + packages.each { + target.relocate(it, "${prefix}.${it}") + } + + } + + static String taskName(Task task) { + return "configureRelocation${task.name.capitalize()}" + } + +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/DefaultInheritManifest.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/DefaultInheritManifest.groovy new file mode 100644 index 0000000..9e3239f --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/DefaultInheritManifest.groovy @@ -0,0 +1,99 @@ +package org.xbib.gradle.plugin.shadow.tasks + +import org.gradle.api.Action +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.java.archives.Attributes +import org.gradle.api.java.archives.Manifest +import org.gradle.api.java.archives.ManifestException +import org.gradle.api.java.archives.ManifestMergeSpec +import org.gradle.api.java.archives.internal.DefaultManifest +import org.gradle.api.java.archives.internal.DefaultManifestMergeSpec +import org.gradle.util.ConfigureUtil + +class DefaultInheritManifest implements InheritManifest { + + private List inheritMergeSpecs = [] + + private final FileResolver fileResolver + + private final Manifest internalManifest + + DefaultInheritManifest(FileResolver fileResolver) { + this.internalManifest = new DefaultManifest(fileResolver) + this.fileResolver = fileResolver + } + + InheritManifest inheritFrom(Object... inheritPaths) { + inheritFrom(inheritPaths, null) + this + } + + InheritManifest inheritFrom(Object inheritPaths, Closure closure) { + DefaultManifestMergeSpec mergeSpec = new DefaultManifestMergeSpec() + mergeSpec.from(inheritPaths) + inheritMergeSpecs.add(mergeSpec) + ConfigureUtil.configure(closure, mergeSpec) + this + } + + @Override + Attributes getAttributes() { + internalManifest.getAttributes() + } + + @Override + Map getSections() { + internalManifest.getSections() + } + + @Override + Manifest attributes(Map map) throws ManifestException { + internalManifest.attributes(map) + this + } + + @Override + Manifest attributes(Map map, String s) throws ManifestException { + internalManifest.attributes(map, s) + this + } + + @Override + DefaultManifest getEffectiveManifest() { + DefaultManifest base = new DefaultManifest(fileResolver) + inheritMergeSpecs.each { + base = it.merge(base, fileResolver) + } + base.from internalManifest + base.getEffectiveManifest() + } + + Manifest writeTo(Writer writer) { + this.getEffectiveManifest().writeTo((Object) writer) + this + } + + @Override + Manifest writeTo(Object o) { + this.getEffectiveManifest().writeTo(o) + this + } + + @Override + Manifest from(Object... objects) { + internalManifest.from(objects) + this + } + + @Override + Manifest from(Object o, Closure closure) { + internalManifest.from(o, closure) + this + } + + @Override + Manifest from(Object o, Action action) { + internalManifest.from(o, action) + this + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/InheritManifest.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/InheritManifest.groovy new file mode 100644 index 0000000..938b88e --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/InheritManifest.groovy @@ -0,0 +1,10 @@ +package org.xbib.gradle.plugin.shadow.tasks + +import org.gradle.api.java.archives.Manifest + +interface InheritManifest extends Manifest { + + InheritManifest inheritFrom(Object... inheritPaths) + + InheritManifest inheritFrom(Object inheritPaths, Closure closure) +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowCopyAction.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowCopyAction.groovy new file mode 100644 index 0000000..bcecfc8 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowCopyAction.groovy @@ -0,0 +1,503 @@ +package org.xbib.gradle.plugin.shadow.tasks + +import org.gradle.api.Action +import org.gradle.api.GradleException +import org.gradle.api.UncheckedIOException +import org.gradle.api.file.FileCopyDetails +import org.gradle.api.file.FileTreeElement +import org.gradle.api.file.RelativePath +import org.gradle.api.internal.DocumentationRegistry +import org.gradle.api.internal.file.CopyActionProcessingStreamAction +import org.gradle.api.internal.file.copy.CopyAction +import org.gradle.api.internal.file.copy.CopyActionProcessingStream +import org.gradle.api.internal.file.copy.FileCopyDetailsInternal +import org.gradle.api.logging.Logger +import org.gradle.api.specs.Spec +import org.gradle.api.tasks.WorkResult +import org.gradle.api.tasks.WorkResults +import org.gradle.api.tasks.bundling.Zip +import org.gradle.api.tasks.util.PatternSet +import org.gradle.internal.UncheckedException +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.commons.ClassRemapper +import org.xbib.gradle.plugin.shadow.ShadowStats +import org.xbib.gradle.plugin.shadow.impl.RelocatorRemapper +import org.xbib.gradle.plugin.shadow.internal.UnusedTracker +import org.xbib.gradle.plugin.shadow.internal.Utils +import org.xbib.gradle.plugin.shadow.internal.ZipCompressor +import org.xbib.gradle.plugin.shadow.relocation.Relocator +import org.xbib.gradle.plugin.shadow.transformers.Transformer +import org.xbib.gradle.plugin.shadow.transformers.TransformerContext +import org.xbib.gradle.plugin.shadow.zip.UnixStat +import org.xbib.gradle.plugin.shadow.zip.Zip64RequiredException +import org.xbib.gradle.plugin.shadow.zip.ZipEntry +import org.xbib.gradle.plugin.shadow.zip.ZipFile +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +import java.time.LocalDate +import java.time.OffsetDateTime +import java.util.zip.ZipException + +class ShadowCopyAction implements CopyAction { + + static final long CONSTANT_TIME_FOR_ZIP_ENTRIES = LocalDate.of(1980, 1, 1).atStartOfDay() + .toInstant(OffsetDateTime.now().getOffset()).toEpochMilli() + + private final Logger log + private final File zipFile + private final ZipCompressor compressor + private final DocumentationRegistry documentationRegistry + private final List transformers + private final List relocators + private final PatternSet patternSet + private final ShadowStats stats + private final String encoding + private final boolean preserveFileTimestamps + private final boolean minimizeJar + private final UnusedTracker unusedTracker + + ShadowCopyAction(Logger log, File zipFile, ZipCompressor compressor, DocumentationRegistry documentationRegistry, + String encoding, List transformers, List relocators, + PatternSet patternSet, ShadowStats stats, + boolean preserveFileTimestamps, boolean minimizeJar, UnusedTracker unusedTracker) { + this.log = log + this.zipFile = zipFile + this.compressor = compressor + this.documentationRegistry = documentationRegistry + this.transformers = transformers + this.relocators = relocators + this.patternSet = patternSet + this.stats = stats + this.encoding = encoding + this.preserveFileTimestamps = preserveFileTimestamps + this.minimizeJar = minimizeJar + this.unusedTracker = unusedTracker + } + + @Override + WorkResult execute(CopyActionProcessingStream stream) { + Set unusedClasses + if (minimizeJar) { + stream.process(new BaseStreamAction() { + @Override + void visitFile(FileCopyDetails fileDetails) { + if (isArchive(fileDetails)) { + unusedTracker.addDependency(fileDetails.file) + } + } + }) + unusedClasses = unusedTracker.findUnused() + } else { + unusedClasses = Collections.emptySet() + } + + try { + ZipOutputStream zipOutStr = compressor.createArchiveOutputStream(zipFile) + withResource(zipOutStr, new Action() { + void execute(ZipOutputStream outputStream) { + try { + stream.process(new StreamAction(outputStream, encoding, transformers, relocators, patternSet, + unusedClasses, stats)) + processTransformers(outputStream) + } catch (Exception e) { + log.error(e.getMessage() as String, e) + throw e + } + } + }) + } catch (UncheckedIOException e) { + if (e.cause instanceof Zip64RequiredException) { + throw new Zip64RequiredException( + String.format("%s\n\nTo build this archive, please enable the zip64 extension.\nSee: %s", + e.cause.message, documentationRegistry.getDslRefForProperty(Zip, "zip64")) + ) + } + } catch (Exception e) { + throw new GradleException("could not create zip '${zipFile.toString()}'", e) + } + return WorkResults.didWork(true) + } + + private void processTransformers(ZipOutputStream stream) { + transformers.each { Transformer transformer -> + if (transformer.hasTransformedResource()) { + transformer.modifyOutputStream(stream, preserveFileTimestamps) + } + } + } + + private long getArchiveTimeFor(long timestamp) { + return preserveFileTimestamps ? timestamp : CONSTANT_TIME_FOR_ZIP_ENTRIES + } + + private ZipEntry setArchiveTimes(ZipEntry zipEntry) { + if (!preserveFileTimestamps) { + zipEntry.setTime(CONSTANT_TIME_FOR_ZIP_ENTRIES) + } + return zipEntry + } + + private static void withResource(T resource, Action action) { + try { + action.execute(resource) + } catch (Throwable t) { + try { + resource.close() + } catch (IOException e) { + // Ignored + } + throw UncheckedException.throwAsUncheckedException(t) + } + + try { + resource.close() + } catch (IOException e) { + throw new UncheckedIOException(e) + } + } + + abstract class BaseStreamAction implements CopyActionProcessingStreamAction { + protected boolean isArchive(FileCopyDetails fileDetails) { + return fileDetails.relativePath.pathString.endsWith('.jar') + } + + protected boolean isClass(FileCopyDetails fileDetails) { + return Utils.getExtension(fileDetails.path) == 'class' + } + + @Override + void processFile(FileCopyDetailsInternal details) { + if (details.directory) { + visitDir(details) + } else { + visitFile(details) + } + } + + protected void visitDir(FileCopyDetails dirDetails) {} + + protected abstract void visitFile(FileCopyDetails fileDetails) + } + + private class StreamAction extends BaseStreamAction { + + private final ZipOutputStream zipOutStr + private final List transformers + private final List relocators + private final RelocatorRemapper remapper + private final PatternSet patternSet + private final Set unused + private final ShadowStats stats + + private Set visitedFiles = new HashSet() + + StreamAction(ZipOutputStream zipOutStr, String encoding, List transformers, + List relocators, PatternSet patternSet, Set unused, + ShadowStats stats) { + this.zipOutStr = zipOutStr + this.transformers = transformers + this.relocators = relocators + this.remapper = new RelocatorRemapper(relocators, stats) + this.patternSet = patternSet + this.unused = unused + this.stats = stats + if(encoding != null) { + this.zipOutStr.setEncoding(encoding) + } + } + + private boolean recordVisit(RelativePath path) { + return visitedFiles.add(path.pathString) + } + + @Override + void visitFile(FileCopyDetails fileDetails) { + if (!isArchive(fileDetails)) { + try { + boolean isClass = isClass(fileDetails) + if (!remapper.hasRelocators() || !isClass) { + if (!isTransformable(fileDetails)) { + String mappedPath = remapper.map(fileDetails.relativePath.pathString) + ZipEntry archiveEntry = new ZipEntry(mappedPath) + archiveEntry.setTime(getArchiveTimeFor(fileDetails.lastModified)) + archiveEntry.unixMode = (UnixStat.FILE_FLAG | fileDetails.mode) + zipOutStr.putNextEntry(archiveEntry) + fileDetails.copyTo(zipOutStr) + zipOutStr.closeEntry() + } else { + transform(fileDetails) + } + } else if (isClass && !isUnused(fileDetails.path)) { + remapClass(fileDetails) + } + recordVisit(fileDetails.relativePath) + } catch (Exception e) { + throw new GradleException(String.format("Could not add %s to ZIP '%s'.", fileDetails, zipFile), e) + } + } else { + processArchive(fileDetails) + } + } + + private void processArchive(FileCopyDetails fileDetails) { + stats.startJar() + ZipFile archive = new ZipFile(fileDetails.file) + List archiveElements = archive.entries.collect { + new ArchiveFileTreeElement(new RelativeArchivePath(it, fileDetails)) + } + Spec patternSpec = patternSet.getAsSpec() + List filteredArchiveElements = archiveElements.findAll { ArchiveFileTreeElement archiveElement -> + patternSpec.isSatisfiedBy(archiveElement) + } + filteredArchiveElements.each { ArchiveFileTreeElement archiveElement -> + if (archiveElement.relativePath.file) { + visitArchiveFile(archiveElement, archive) + } + } + archive.close() + stats.finishJar() + } + + private void visitArchiveDirectory(RelativeArchivePath archiveDir) { + if (recordVisit(archiveDir)) { + zipOutStr.putNextEntry(archiveDir.entry) + zipOutStr.closeEntry() + } + } + + private void visitArchiveFile(ArchiveFileTreeElement archiveFile, ZipFile archive) { + def archiveFilePath = archiveFile.relativePath + if (archiveFile.classFile || !isTransformable(archiveFile)) { + if (recordVisit(archiveFilePath) && !isUnused(archiveFilePath.entry.name)) { + if (!remapper.hasRelocators() || !archiveFile.classFile) { + copyArchiveEntry(archiveFilePath, archive) + } else { + remapClass(archiveFilePath, archive) + } + } + } else { + transform(archiveFile, archive) + } + } + + private void addParentDirectories(RelativeArchivePath file) { + if (file) { + addParentDirectories(file.parent) + if (!file.file) { + visitArchiveDirectory(file) + } + } + } + + private boolean isUnused(String classPath) { + final String className = Utils.removeExtension(classPath) + .replace('/' as char, '.' as char) + final boolean result = unused.contains(className) + if (result) { + log.debug("dropping unused class: $className") + } + return result + } + + private void remapClass(RelativeArchivePath file, ZipFile archive) { + if (file.classFile) { + ZipEntry zipEntry = setArchiveTimes(new ZipEntry(remapper.mapPath(file) + '.class')) + addParentDirectories(new RelativeArchivePath(zipEntry, null)) + InputStream is = archive.getInputStream(file.entry) + try { + remapClass(is, file.pathString, file.entry.time) + } finally { + is.close() + } + } + } + + private void remapClass(FileCopyDetails fileCopyDetails) { + if (Utils.getExtension(fileCopyDetails.name) == 'class') { + remapClass(fileCopyDetails.file.newInputStream(), fileCopyDetails.path, fileCopyDetails.lastModified) + } + } + + private void remapClass(InputStream classInputStream, String path, long lastModified) { + InputStream is = classInputStream + ClassReader cr = new ClassReader(is) + ClassWriter cw = new ClassWriter(0) + ClassVisitor cv = new ClassRemapper(cw, remapper) + try { + cr.accept(cv, ClassReader.EXPAND_FRAMES) + } catch (Throwable ise) { + throw new GradleException("error in ASM processing class " + path, ise) + } + byte[] renamedClass = cw.toByteArray() + String mappedName = remapper.mapPath(path) + InputStream bis = new ByteArrayInputStream(renamedClass) + try { + ZipEntry archiveEntry = new ZipEntry(mappedName + ".class") + archiveEntry.setTime(getArchiveTimeFor(lastModified)) + zipOutStr.putNextEntry(archiveEntry) + Utils.copyLarge(bis, zipOutStr) + zipOutStr.closeEntry() + } catch (ZipException e) { + log.warn("there is a duplicate " + mappedName + " in source project") + } finally { + bis.close() + } + } + + private void copyArchiveEntry(RelativeArchivePath archiveFile, ZipFile archive) { + String mappedPath = remapper.map(archiveFile.entry.name) + ZipEntry entry = new ZipEntry(mappedPath) + entry.setTime(getArchiveTimeFor(archiveFile.entry.time)) + RelativeArchivePath mappedFile = new RelativeArchivePath(entry, archiveFile.details) + addParentDirectories(mappedFile) + zipOutStr.putNextEntry(mappedFile.entry) + InputStream is = archive.getInputStream(archiveFile.entry) + try { + Utils.copyLarge(is, zipOutStr) + } finally { + is.close() + } + zipOutStr.closeEntry() + } + + @Override + protected void visitDir(FileCopyDetails dirDetails) { + try { + String path = dirDetails.relativePath.pathString + '/' + ZipEntry archiveEntry = new ZipEntry(path) + archiveEntry.setTime(getArchiveTimeFor(dirDetails.lastModified)) + archiveEntry.unixMode = (UnixStat.DIR_FLAG | dirDetails.mode) + zipOutStr.putNextEntry(archiveEntry) + zipOutStr.closeEntry() + recordVisit(dirDetails.relativePath) + } catch (Exception e) { + throw new GradleException(String.format("Could not add %s to ZIP '%s'.", dirDetails, zipFile), e) + } + } + + private void transform(ArchiveFileTreeElement element, ZipFile archive) { + InputStream is = archive.getInputStream(element.relativePath.entry) + try { + transform(element, is) + } finally { + is.close() + } + } + + private void transform(FileCopyDetails details) { + transform(details, details.file.newInputStream()) + } + + private void transform(FileTreeElement element, InputStream inputStream) { + String mappedPath = remapper.map(element.relativePath.pathString) + transformers.find { it.canTransformResource(element) }.transform( + TransformerContext.builder() + .path(mappedPath) + .inputStream(inputStream) + .relocators(relocators) + .stats(stats) + .build() + ) + } + + private boolean isTransformable(FileTreeElement element) { + return transformers.any { it.canTransformResource(element) } + } + + } + + class RelativeArchivePath extends RelativePath { + + ZipEntry entry + FileCopyDetails details + + RelativeArchivePath(ZipEntry entry, FileCopyDetails fileDetails) { + super(!entry.directory, entry.name.split('/')) + this.entry = entry + this.details = fileDetails + } + + boolean isClassFile() { + return lastName.endsWith('.class') + } + + RelativeArchivePath getParent() { + if (!segments || segments.length == 1) { + return null + } else { + String path = segments[0..-2].join('/') + '/' + return new RelativeArchivePath(setArchiveTimes(new ZipEntry(path)), null) + } + } + } + + class ArchiveFileTreeElement implements FileTreeElement { + + private final RelativeArchivePath archivePath + + ArchiveFileTreeElement(RelativeArchivePath archivePath) { + this.archivePath = archivePath + } + + boolean isClassFile() { + return archivePath.classFile + } + + @Override + File getFile() { + return null + } + + @Override + boolean isDirectory() { + return archivePath.entry.directory + } + + @Override + long getLastModified() { + return archivePath.entry.lastModifiedDate.time + } + + @Override + long getSize() { + return archivePath.entry.size + } + + @Override + InputStream open() { + return null + } + + @Override + void copyTo(OutputStream outputStream) { + + } + + @Override + boolean copyTo(File file) { + return false + } + + @Override + String getName() { + return archivePath.pathString + } + + @Override + String getPath() { + return archivePath.lastName + } + + @Override + RelativeArchivePath getRelativePath() { + return archivePath + } + + @Override + int getMode() { + return archivePath.entry.unixMode + } + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowJar.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowJar.groovy new file mode 100644 index 0000000..d4ec349 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowJar.groovy @@ -0,0 +1,398 @@ +package org.xbib.gradle.plugin.shadow.tasks + +import org.gradle.api.file.DuplicatesStrategy +import org.gradle.api.internal.file.copy.CopySpecResolver +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.bundling.ZipEntryCompression +import org.gradle.api.Action +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.FileCollection +import org.gradle.api.internal.DocumentationRegistry +import org.gradle.api.internal.file.FileResolver +import org.gradle.api.internal.file.copy.CopyAction +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.util.PatternSet +import org.gradle.internal.Factory +import org.gradle.api.tasks.util.internal.PatternSets +import org.xbib.gradle.plugin.shadow.ShadowStats +import org.xbib.gradle.plugin.shadow.internal.DefaultDependencyFilter +import org.xbib.gradle.plugin.shadow.internal.DefaultZipCompressor +import org.xbib.gradle.plugin.shadow.internal.DependencyFilter +import org.xbib.gradle.plugin.shadow.internal.UnusedTracker +import org.xbib.gradle.plugin.shadow.internal.ZipCompressor +import org.xbib.gradle.plugin.shadow.relocation.Relocator +import org.xbib.gradle.plugin.shadow.relocation.SimpleRelocator +import org.xbib.gradle.plugin.shadow.transformers.AppendingTransformer +import org.xbib.gradle.plugin.shadow.transformers.GroovyExtensionModuleTransformer +import org.xbib.gradle.plugin.shadow.transformers.ServiceFileTransformer +import org.xbib.gradle.plugin.shadow.transformers.Transformer +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +import java.util.concurrent.Callable + +class ShadowJar extends Jar implements ShadowSpec { + + private List transformers + + private List relocators + + private List configurations + + private DependencyFilter dependencyFilter + + private boolean minimizeJar + + private DependencyFilter dependencyFilterForMinimize + + private ShadowStats shadowStats + + ShadowJar() { + super() + dependencyFilter = new DefaultDependencyFilter(getProject()) + dependencyFilterForMinimize = new DefaultDependencyFilter(getProject()) + setManifest(new DefaultInheritManifest(getServices().get(FileResolver))) + transformers = [] + relocators = [] + configurations = [] + shadowStats = new ShadowStats() + setDuplicatesStrategy(DuplicatesStrategy.INCLUDE) + } + + @Override + ShadowJar minimize() { + minimizeJar = true + this + } + + @Override + ShadowJar minimize(Action c) { + minimize() + if (c != null) { + c.execute(dependencyFilterForMinimize) + } + return this + } + + @Override + @Internal + ShadowStats getStats() { + shadowStats + } + + @Override + InheritManifest getManifest() { + (InheritManifest) super.getManifest() + } + + @Override + protected CopyAction createCopyAction() { + DocumentationRegistry documentationRegistry = getServices().get(DocumentationRegistry) + FileCollection toMinimize = dependencyFilterForMinimize.resolve(configurations) + UnusedTracker unusedTracker = UnusedTracker.forProject(getProject(), toMinimize) + CopySpecResolver copySpecResolver = mainSpec.buildRootResolver() + Factory patternSetFactory = PatternSets.getNonCachingPatternSetFactory() + PatternSet patternSet = patternSetFactory.create() + patternSet.setCaseSensitive(copySpecResolver.caseSensitive) + patternSet.include(copySpecResolver.allIncludes) + patternSet.includeSpecs(copySpecResolver.allIncludeSpecs) + patternSet.exclude(copySpecResolver.allExcludes) + patternSet.excludeSpecs(copySpecResolver.allExcludeSpecs) + new ShadowCopyAction(getLogger(), getArchiveFile().get().getAsFile(), getInternalCompressor(), documentationRegistry, + this.getMetadataCharset(), transformers, relocators, patternSet, shadowStats, + isPreserveFileTimestamps(), minimizeJar, unusedTracker) + } + + @Internal + protected ZipCompressor getInternalCompressor() { + switch (getEntryCompression()) { + case ZipEntryCompression.DEFLATED: + return new DefaultZipCompressor(isZip64(), ZipOutputStream.DEFLATED) + case ZipEntryCompression.STORED: + return new DefaultZipCompressor(isZip64(), ZipOutputStream.STORED) + default: + throw new IllegalArgumentException(String.format("unknown compression type %s", entryCompression)) + } + } + + @TaskAction + protected void copy() { + from(getIncludedDependencies()) + super.copy() + getLogger().info(shadowStats.toString()) + } + + @InputFiles + FileCollection getIncludedDependencies() { + getProject().files(new Callable() { + + @Override + FileCollection call() throws Exception { + return dependencyFilter.resolve(configurations) + } + }) + } + + /** + * Configure inclusion/exclusion of module & project dependencies into uber jar. + * + * @param c the configuration of the filter + * @return this + */ + @Override + ShadowJar dependencies(Action c) { + if (c != null) { + c.execute(dependencyFilter) + } + this + } + + /** + * Add a Transformer instance for modifying JAR resources and configure. + * + * @param clazz the transformer to add. Must have a no-arg constructor + * @return this + */ + @Override + ShadowJar transform(Class clazz) + throws InstantiationException, IllegalAccessException { + transform(clazz, null) + } + + /** + * Add a Transformer instance for modifying JAR resources and configure. + * + * @param clazz the transformer class to add. Must have no-arg constructor + * @param c the configuration for the transformer + * @return this + */ + @Override + ShadowJar transform(Class clazz, Action c) + throws InstantiationException, IllegalAccessException { + Transformer transformer = clazz.getDeclaredConstructor().newInstance() + if (c != null) { + c.execute(transformer) + } + transformers.add(transformer) + this + } + + /** + * Add a preconfigured transformer instance. + * + * @param transformer the transformer instance to add + * @return this + */ + @Override + ShadowJar transform(Transformer transformer) { + transformers.add(transformer) + this + } + + /** + * Syntactic sugar for merging service files in JARs. + * + * @return this + */ + @Override + ShadowJar mergeServiceFiles() { + try { + transform(ServiceFileTransformer.class) + } catch (IllegalAccessException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } catch (InstantiationException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } + this + } + + /** + * Syntactic sugar for merging service files in JARs. + * + * @return this + */ + @Override + ShadowJar mergeServiceFiles(String rootPath) { + try { + transform(ServiceFileTransformer.class, new Action() { + + @Override + void execute(ServiceFileTransformer serviceFileTransformer) { + serviceFileTransformer.setPath(rootPath) + } + }) + } catch (IllegalAccessException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } catch (InstantiationException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } + this + } + + /** + * Syntactic sugar for merging service files in JARs. + * + * @return this + */ + @Override + ShadowJar mergeServiceFiles(Action configureClosure) { + try { + transform(ServiceFileTransformer.class, configureClosure) + } catch (IllegalAccessException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } catch (InstantiationException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } + this + } + + /** + * Syntactic sugar for merging Groovy extension module descriptor files in JARs + * + * @return this + */ + @Override + ShadowJar mergeGroovyExtensionModules() { + try { + transform(GroovyExtensionModuleTransformer.class) + } catch (IllegalAccessException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } catch (InstantiationException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } + this + } + + /** + * Syntactic sugar for merging service files in JARs + * + * @return this + */ + @Override + ShadowJar append(String resourcePath) { + try { + transform(AppendingTransformer.class, new Action() { + @Override + void execute(AppendingTransformer transformer) { + transformer.setResource(resourcePath) + } + }) + } catch (IllegalAccessException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } catch (InstantiationException e) { + getLogger().log(LogLevel.ERROR, e.getMessage() as String, e) + } + return this + } + + /** + * Add a class relocator that maps each class in the pattern to the provided destination. + * + * @param pattern the source pattern to relocate + * @param destination the destination package + * @return this + */ + @Override + ShadowJar relocate(String pattern, String destination) { + relocate(pattern, destination, null) + } + + /** + * Add a class relocator that maps each class in the pattern to the provided destination. + * + * @param pattern the source pattern to relocate + * @param destination the destination package + * @param configure the configuration of the relocator + * @return this + */ + @Override + ShadowJar relocate(String pattern, String destination, Action configure) { + SimpleRelocator relocator = new SimpleRelocator(pattern, destination, new ArrayList(), new ArrayList()); + if (configure != null) { + configure.execute(relocator) + } + relocators.add(relocator) + this + } + + /** + * Add a relocator instance. + * + * @param relocator the relocator instance to add + * @return this + */ + @Override + ShadowJar relocate(Relocator relocator) { + relocators.add(relocator) + this + } + + /** + * Add a relocator of the provided class. + * + * @param relocatorClass the relocator class to add. Must have a no-arg constructor. + * @return this + */ + @Override + ShadowJar relocate(Class relocatorClass) + throws InstantiationException, IllegalAccessException { + relocate(relocatorClass, null) + } + + /** + * Add a relocator of the provided class and configure. + * + * @param relocatorClass the relocator class to add. Must have a no-arg constructor + * @param configure the configuration for the relocator + * @return this + */ + @Override + ShadowJar relocate(Class relocatorClass, Action configure) + throws InstantiationException, IllegalAccessException { + Relocator relocator = relocatorClass.getDeclaredConstructor().newInstance() + if (configure != null) { + configure.execute(relocator) + } + relocators.add(relocator) + this + } + + @Internal + List getTransformers() { + return this.transformers + } + + void setTransformers(List transformers) { + this.transformers = transformers + } + + @Internal + List getRelocators() { + return this.relocators + } + + void setRelocators(List relocators) { + this.relocators = relocators + } + + @InputFiles @Optional + List getConfigurations() { + this.configurations + } + + void setConfigurations(List configurations) { + this.configurations = configurations + } + + @Internal + DependencyFilter getDependencyFilter() { + return this.dependencyFilter + } + + void setDependencyFilter(DependencyFilter filter) { + this.dependencyFilter = filter + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowSpec.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowSpec.groovy new file mode 100644 index 0000000..30ad8ed --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/tasks/ShadowSpec.groovy @@ -0,0 +1,51 @@ +package org.xbib.gradle.plugin.shadow.tasks + +import org.xbib.gradle.plugin.shadow.ShadowStats +import org.xbib.gradle.plugin.shadow.internal.DependencyFilter +import org.xbib.gradle.plugin.shadow.relocation.Relocator +import org.xbib.gradle.plugin.shadow.relocation.SimpleRelocator +import org.xbib.gradle.plugin.shadow.transformers.ServiceFileTransformer +import org.xbib.gradle.plugin.shadow.transformers.Transformer +import org.gradle.api.Action +import org.gradle.api.file.CopySpec + +interface ShadowSpec extends CopySpec { + + ShadowSpec minimize() + + ShadowSpec minimize(Action configureClosure) + + ShadowSpec dependencies(Action configure) + + ShadowSpec transform(Class clazz) + throws InstantiationException, IllegalAccessException + + ShadowSpec transform(Class clazz, Action configure) + throws InstantiationException, IllegalAccessException + + ShadowSpec transform(Transformer transformer) + + ShadowSpec mergeServiceFiles() + + ShadowSpec mergeServiceFiles(String rootPath) + + ShadowSpec mergeServiceFiles(Action configureClosure) + + ShadowSpec mergeGroovyExtensionModules() + + ShadowSpec append(String resourcePath) + + ShadowSpec relocate(String pattern, String destination) + + ShadowSpec relocate(String pattern, String destination, Action configure) + + ShadowSpec relocate(Relocator relocator) + + ShadowSpec relocate(Class clazz) + throws InstantiationException, IllegalAccessException + + ShadowSpec relocate(Class clazz, Action configure) + throws InstantiationException, IllegalAccessException + + ShadowStats getStats() +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheLicenseResourceTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheLicenseResourceTransformer.groovy new file mode 100644 index 0000000..879f471 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheLicenseResourceTransformer.groovy @@ -0,0 +1,35 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +/** + * Prevents duplicate copies of the license + * Modified from org.apache.maven.plugins.shade.resouce.ApacheLicenseResourceTransformer. + */ +class ApacheLicenseResourceTransformer implements Transformer { + + private static final String LICENSE_PATH = "META-INF/LICENSE" + + private static final String LICENSE_TXT_PATH = "META-INF/LICENSE.txt" + + @Override + boolean canTransformResource(FileTreeElement element) { + def path = element.relativePath.pathString + return LICENSE_PATH.equalsIgnoreCase(path) || + LICENSE_TXT_PATH.regionMatches(true, 0, path, 0, LICENSE_TXT_PATH.length()) + } + + @Override + void transform(TransformerContext context) { + } + + @Override + boolean hasTransformedResource() { + return false + } + + @Override + void modifyOutputStream(ZipOutputStream jos, boolean preserveFileTimestamps) { + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformer.groovy new file mode 100644 index 0000000..b106f0b --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformer.groovy @@ -0,0 +1,184 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.zip.ZipEntry +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +import java.text.SimpleDateFormat + +/** + * Merges META-INF/NOTICE.TXT files. + * Modified from org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer + */ +class ApacheNoticeResourceTransformer implements Transformer { + Set entries = new LinkedHashSet() + + Map> organizationEntries = new LinkedHashMap>() + + String projectName = "" + + boolean addHeader = true + + String preamble1 = "// ------------------------------------------------------------------\n" + + "// NOTICE file corresponding to the section 4d of The Apache License,\n" + + "// Version 2.0, in this case for " + + String preamble2 = "\n// ------------------------------------------------------------------\n" + + String preamble3 = "This product includes software developed at\n" + + String organizationName = "The Apache Software Foundation" + + String organizationURL = "http://www.apache.org/" + + String inceptionYear = "2006" + + String copyright + + /** + * The file encoding of the NOTICE file. + */ + String encoding + + private static final String NOTICE_PATH = "META-INF/NOTICE" + + private static final String NOTICE_TXT_PATH = "META-INF/NOTICE.txt" + + @Override + boolean canTransformResource(FileTreeElement element) { + def path = element.relativePath.pathString + if (NOTICE_PATH.equalsIgnoreCase(path) || NOTICE_TXT_PATH.equalsIgnoreCase(path)) { + return true + } + + return false + } + + @Override + void transform(TransformerContext context) { + if (entries.isEmpty()) { + String year = new SimpleDateFormat("yyyy").format(new Date()) + if (!inceptionYear.equals(year)) { + year = inceptionYear + "-" + year + } + + //add headers + if (addHeader) { + entries.add(preamble1 + projectName + preamble2) + } else { + entries.add("") + } + //fake second entry, we'll look for a real one later + entries.add(projectName + "\nCopyright " + year + " " + organizationName + "\n") + entries.add(preamble3 + organizationName + " (" + organizationURL + ").\n") + } + + BufferedReader reader + if (encoding != null && !encoding.isEmpty()) { + reader = new BufferedReader(new InputStreamReader(context.inputStream, encoding)) + } else { + reader = new BufferedReader(new InputStreamReader(context.inputStream)) + } + + String line = reader.readLine() + StringBuffer sb = new StringBuffer() + Set currentOrg = null + int lineCount = 0 + while (line != null) { + String trimedLine = line.trim() + + if (!trimedLine.startsWith("//")) { + if (trimedLine.length() > 0) { + if (trimedLine.startsWith("- ")) { + //resource-bundle 1.3 mode + if (lineCount == 1 + && sb.toString().indexOf("This product includes/uses software(s) developed by") != -1) { + currentOrg = organizationEntries.get(sb.toString().trim()) + if (currentOrg == null) { + currentOrg = new TreeSet() + organizationEntries.put(sb.toString().trim(), currentOrg) + } + sb = new StringBuffer() + } else if (sb.length() > 0 && currentOrg != null) { + currentOrg.add(sb.toString()) + sb = new StringBuffer() + } + + } + sb.append(line).append("\n") + lineCount++ + } else { + String ent = sb.toString() + if (ent.startsWith(projectName) && ent.indexOf("Copyright ") != -1) { + copyright = ent + } + if (currentOrg == null) { + entries.add(ent) + } else { + currentOrg.add(ent) + } + sb = new StringBuffer() + lineCount = 0 + currentOrg = null + } + } + + line = reader.readLine() + } + if (sb.length() > 0) { + if (currentOrg == null) { + entries.add(sb.toString()) + } else { + currentOrg.add(sb.toString()) + } + } + } + + @Override + boolean hasTransformedResource() { + return true + } + + @Override + void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { + ZipEntry zipEntry = new ZipEntry(NOTICE_PATH) + zipEntry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, zipEntry.time) + os.putNextEntry(zipEntry) + + Writer pow + if (encoding != null && !encoding.isEmpty()) { + pow = new OutputStreamWriter(os, encoding) + } else { + pow = new OutputStreamWriter(os) + } + PrintWriter writer = new PrintWriter(pow) + + int count = 0 + for (String line : entries) { + ++count + if (line.equals(copyright) && count != 2) { + continue + } + + if (count == 2 && copyright != null) { + writer.print(copyright) + writer.print('\n') + } else { + writer.print(line) + writer.print('\n') + } + if (count == 3) { + for (Map.Entry> entry : organizationEntries.entrySet()) { + writer.print(entry.getKey()) + writer.print('\n') + for (String l : entry.getValue()) { + writer.print(l) + } + writer.print('\n') + } + } + } + writer.flush() + entries.clear() + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/AppendingTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/AppendingTransformer.groovy new file mode 100644 index 0000000..355419e --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/AppendingTransformer.groovy @@ -0,0 +1,43 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.internal.Utils +import org.xbib.gradle.plugin.shadow.zip.ZipEntry +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +/** + * A resource processor that appends content for a resource, separated by a newline. + * Modified from org.apache.maven.plugins.shade.resource.AppendingTransformer. + */ +class AppendingTransformer implements Transformer { + String resource + + ByteArrayOutputStream data = new ByteArrayOutputStream() + + @Override + boolean canTransformResource(FileTreeElement element) { + def path = element.relativePath.pathString + resource != null && resource.equalsIgnoreCase(path) + } + + @Override + void transform(TransformerContext context) { + Utils.copyLarge(context.inputStream, data) + data.write('\n'.bytes) + context.inputStream.close() + } + + @Override + boolean hasTransformedResource() { + return data.size() > 0 + } + + @Override + void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { + ZipEntry entry = new ZipEntry(resource) + entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) + os.putNextEntry(entry) + Utils.copyLarge(new ByteArrayInputStream(data.toByteArray()), os) + data.reset() + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/DontIncludeResourceTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/DontIncludeResourceTransformer.groovy new file mode 100644 index 0000000..86883cd --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/DontIncludeResourceTransformer.groovy @@ -0,0 +1,37 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +/** + * A resource processor that prevents the inclusion of an arbitrary resource into the shaded JAR. + * Modified from org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer + */ +class DontIncludeResourceTransformer implements Transformer { + String resource + + @Override + boolean canTransformResource(FileTreeElement element) { + def path = element.relativePath.pathString + if (path.endsWith(resource)) { + return true + } + + return false + } + + @Override + void transform(TransformerContext context) { + // no op + } + + @Override + boolean hasTransformedResource() { + return false + } + + @Override + void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { + // no op + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/GroovyExtensionModuleTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/GroovyExtensionModuleTransformer.groovy new file mode 100644 index 0000000..a9f8fac --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/GroovyExtensionModuleTransformer.groovy @@ -0,0 +1,89 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.internal.Utils +import org.xbib.gradle.plugin.shadow.zip.ZipEntry +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +/** + * Modified from eu.appsatori.gradle.fatjar.tasks.PrepareFiles.groovy + * Resource transformer that merges Groovy extension module descriptor files into a single file. If there are several + * META-INF/services/org.codehaus.groovy.runtime.ExtensionModule resources spread across many JARs the individual + * entries will all be merged into a single META-INF/services/org.codehaus.groovy.runtime.ExtensionModule resource + * packaged into the resultant JAR produced by the shadowing process. + */ +class GroovyExtensionModuleTransformer implements Transformer { + + private static final GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH = + "META-INF/services/org.codehaus.groovy.runtime.ExtensionModule" + + private static final MODULE_NAME_KEY = 'moduleName' + private static final MODULE_VERSION_KEY = 'moduleVersion' + private static final EXTENSION_CLASSES_KEY = 'extensionClasses' + private static final STATIC_EXTENSION_CLASSES_KEY = 'staticExtensionClasses' + + private static final MERGED_MODULE_NAME = 'MergedByShadowJar' + private static final MERGED_MODULE_VERSION = '1.0.0' + + private final Properties module = new Properties() + + @Override + boolean canTransformResource(FileTreeElement element) { + return element.relativePath.pathString == GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH + } + + @Override + void transform(TransformerContext context) { + def props = new Properties() + props.load(context.inputStream) + props.each { String key, String value -> + switch (key) { + case MODULE_NAME_KEY: + handle(key, value) { + module.setProperty(key, MERGED_MODULE_NAME) + } + break + case MODULE_VERSION_KEY: + handle(key, value) { + module.setProperty(key, MERGED_MODULE_VERSION) + } + break + case [EXTENSION_CLASSES_KEY, STATIC_EXTENSION_CLASSES_KEY]: + handle(key, value) { String existingValue -> + def newValue = "${existingValue},${value}" + module.setProperty(key, newValue) + } + break + } + } + } + + @Override + boolean hasTransformedResource() { + return module.size() > 0 + } + + @Override + void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { + ZipEntry entry = new ZipEntry(GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATH) + entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) + os.putNextEntry(entry) + Utils.copyLarge(toInputStream(module), os) + os.closeEntry() + } + + private static InputStream toInputStream(Properties props) { + def baos = new ByteArrayOutputStream() + props.store(baos, null) + return new ByteArrayInputStream(baos.toByteArray()) + } + + private handle(String key, String value, Closure mergeValue) { + def existingValue = module.getProperty(key) + if (existingValue) { + mergeValue(existingValue) + } else { + module.setProperty(key, value) + } + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/IncludeResourceTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/IncludeResourceTransformer.groovy new file mode 100644 index 0000000..7febedc --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/IncludeResourceTransformer.groovy @@ -0,0 +1,42 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.internal.Utils +import org.xbib.gradle.plugin.shadow.zip.ZipEntry +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +/** + * A resource processor that allows the addition of an arbitrary file content into the shaded JAR. + * Modified from org.apache.maven.plugins.shade.resource.IncludeResourceTransformer + */ +class IncludeResourceTransformer implements Transformer { + + File file + + String resource + + @Override + boolean canTransformResource(FileTreeElement element) { + return false + } + + @Override + void transform(TransformerContext context) { + // no op + } + + @Override + boolean hasTransformedResource() { + return file != null ? file.exists() : false + } + + @Override + void modifyOutputStream(ZipOutputStream outputStream, boolean preserveFileTimestamps) { + ZipEntry entry = new ZipEntry(resource) + entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) + outputStream.putNextEntry(entry) + InputStream is = new FileInputStream(file) + Utils.copyLarge(is, outputStream) + is.close() + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ManifestResourceTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ManifestResourceTransformer.groovy new file mode 100644 index 0000000..44b9029 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ManifestResourceTransformer.groovy @@ -0,0 +1,78 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.zip.ZipEntry +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +import java.util.jar.* +import java.util.jar.Attributes.Name + +/** + * A resource processor that allows the arbitrary addition of attributes to + * the first MANIFEST.MF that is found in the set of JARs being processed, or + * to a newly created manifest for the shaded JAR. + * Modified from org.apache.maven.plugins.shade.resource.ManifestResourceTransformer + */ +class ManifestResourceTransformer implements Transformer { + + private String mainClass + + private Map manifestEntries + + private boolean manifestDiscovered + + private Manifest manifest + + @Override + boolean canTransformResource(FileTreeElement element) { + def path = element.relativePath.pathString + if (JarFile.MANIFEST_NAME.equalsIgnoreCase(path)) { + return true + } + return false + } + + @Override + void transform(TransformerContext context) { + if (!manifestDiscovered) { + manifest = new Manifest(context.inputStream) + manifestDiscovered = true + if (context.inputStream) { + context.inputStream.close() + } + } + } + + @Override + boolean hasTransformedResource() { + true + } + + @Override + void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { + if (manifest == null) { + manifest = new Manifest() + } + Attributes attributes = manifest.getMainAttributes() + if (mainClass != null) { + attributes.put(Name.MAIN_CLASS, mainClass) + } + if (manifestEntries != null) { + for (Map.Entry entry : manifestEntries.entrySet()) { + attributes.put(new Name(entry.getKey()), entry.getValue()) + } + } + ZipEntry entry = new ZipEntry(JarFile.MANIFEST_NAME) + entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) + os.putNextEntry(entry) + manifest.write(os) + } + + ManifestResourceTransformer attributes(Map attributes) { + if (manifestEntries == null) { + manifestEntries = [:] + } + manifestEntries.putAll(attributes) + this + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/PropertiesFileTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/PropertiesFileTransformer.groovy new file mode 100644 index 0000000..9afab69 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/PropertiesFileTransformer.groovy @@ -0,0 +1,220 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.internal.Utils +import org.xbib.gradle.plugin.shadow.zip.ZipEntry +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +import static groovy.lang.Closure.IDENTITY + +/** + * Resources transformer that merges Properties files. + * + *

The default merge strategy discards duplicate values coming from additional + * resources. This behavior can be changed by setting a value for the mergeStrategy + * property, such as 'first' (default), 'latest' or 'append'. If the merge strategy is + * 'latest' then the last value of a matching property entry will be used. If the + * merge strategy is 'append' then the property values will be combined, using a + * merge separator (default value is ','). The merge separator can be changed by + * setting a value for the mergeSeparator property.

+ * + * Say there are two properties files A and B with the + * following entries: + * + * A + *
    + *
  • key1 = value1
  • + *
  • key2 = value2
  • + *
+ * + * B + *
    + *
  • key2 = balue2
  • + *
  • key3 = value3
  • + *
+ * + * With mergeStrategy = first you get + * + * C + *
    + *
  • key1 = value1
  • + *
  • key2 = value2
  • + *
  • key3 = value3
  • + *
+ * + * With mergeStrategy = latest you get + * + * C + *
    + *
  • key1 = value1
  • + *
  • key2 = balue2
  • + *
  • key3 = value3
  • + *
+ * + * With mergeStrategy = append and mergeSparator = ; you get + * + * C + *
    + *
  • key1 = value1
  • + *
  • key2 = value2;balue2
  • + *
  • key3 = value3
  • + *
+ * + *

There are three additional properties that can be set: paths, mappings, + * and keyTransformer. + * The first contains a list of strings or regexes that will be used to determine if + * a path should be transformed or not. The merge strategy and merge separator are + * taken from the global settings.

+ * + *

The mappings property allows you to define merge strategy and separator per + * path

. If either paths or mappings is defined then no other path + * entries will be merged. mappings has precedence over paths if both + * are defined.

+ * + *

If you need to transform keys in properties files, e.g. because they contain class + * names about to be relocated, you can set the keyTransformer property to a + * closure that receives the original key and returns the key name to be used.

+ * + *

Example:

+ *
+ * import org.codehaus.griffon.gradle.shadow.transformers.*
+ * shadowJar {
+ *     transform(PropertiesFileTransformer) {
+ *         paths = [
+ *             'META-INF/editors/java.beans.PropertyEditor'
+ *         ]
+ *         keyTransformer = { key ->
+ *             key.replaceAll('^(orig\.package\..*)$', 'new.prefix.$1')
+ *         }
+ *     }
+ * }
+ * 
+ */ +class PropertiesFileTransformer implements Transformer { + + private static final String PROPERTIES_SUFFIX = '.properties' + + Map propertiesEntries = [:] + + List paths = [] + + Map> mappings = [:] + + String mergeStrategy = 'first' + + String mergeSeparator = ',' + + Closure keyTransformer = IDENTITY + + @Override + boolean canTransformResource(FileTreeElement element) { + def path = element.relativePath.pathString + if (mappings.containsKey(path)) { + return true + } + for (key in mappings.keySet()) { + if (path =~ /$key/) { + return true + } + } + if (path in paths) { + return true + } + for (p in paths) { + if (path =~ /$p/) { + return true + } + } + !mappings && !paths && path.endsWith(PROPERTIES_SUFFIX) + } + + @Override + void transform(TransformerContext context) { + Properties props = propertiesEntries[context.path] + Properties incoming = loadAndTransformKeys(context.inputStream) + if (props == null) { + propertiesEntries[context.path] = incoming + } else { + incoming.each { key, value -> + if (props.containsKey(key)) { + switch (mergeStrategyFor(context.path).toLowerCase()) { + case 'latest': + props.put(key, value) + break + case 'append': + props.put(key, props.getProperty(key as String) + mergeSeparatorFor(context.path) + value) + break + case 'first': + break + default: + break + } + } else { + props.put(key, value) + } + } + } + } + + @Override + boolean hasTransformedResource() { + propertiesEntries.size() > 0 + } + + @Override + void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { + propertiesEntries.each { String path, Properties props -> + ZipEntry entry = new ZipEntry(path) + entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) + os.putNextEntry(entry) + Utils.copyLarge(toInputStream(props), os) + os.closeEntry() + } + } + + private Properties loadAndTransformKeys(InputStream is) { + Properties props = new Properties() + props.load(is) + transformKeys(props) + } + + private Properties transformKeys(Properties properties) { + if (keyTransformer == IDENTITY) + return properties + def result = new Properties() + properties.each { key, value -> + result.put(keyTransformer.call(key), value) + } + result + } + + private String mergeStrategyFor(String path) { + if (mappings.containsKey(path)) { + return mappings.get(path).mergeStrategy ?: mergeStrategy + } + for (key in mappings.keySet()) { + if (path =~ /$key/) { + return mappings.get(key).mergeStrategy ?: mergeStrategy + } + } + mergeStrategy + } + + private String mergeSeparatorFor(String path) { + if (mappings.containsKey(path)) { + return mappings.get(path).mergeSeparator ?: mergeSeparator + } + for (key in mappings.keySet()) { + if (path =~ /$key/) { + return mappings.get(key).mergeSeparator ?: mergeSeparator + } + } + mergeSeparator + } + + private static InputStream toInputStream(Properties props) { + ByteArrayOutputStream baos = new ByteArrayOutputStream() + props.store(baos, '') + new ByteArrayInputStream(baos.toByteArray()) + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ServiceFileTransformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ServiceFileTransformer.groovy new file mode 100644 index 0000000..ec8cb20 --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/ServiceFileTransformer.groovy @@ -0,0 +1,145 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.xbib.gradle.plugin.shadow.internal.ServiceStream +import org.xbib.gradle.plugin.shadow.internal.Utils +import org.xbib.gradle.plugin.shadow.relocation.RelocateClassContext +import org.gradle.api.file.FileTreeElement +import org.gradle.api.specs.Spec +import org.gradle.api.tasks.util.PatternFilterable +import org.gradle.api.tasks.util.PatternSet +import org.xbib.gradle.plugin.shadow.zip.ZipEntry +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +/** + * Modified from org.apache.maven.plugins.shade.resource.ServiceResourceTransformer.java + * Resources transformer that appends entries in META-INF/services resources into + * a single resource. For example, if there are several META-INF/services/org.apache.maven.project.ProjectBuilder + * resources spread across many JARs the individual entries will all be concatenated into a single + * META-INF/services/org.apache.maven.project.ProjectBuilder resource packaged into the resultant JAR produced + * by the shading process. + */ +class ServiceFileTransformer implements Transformer, PatternFilterable { + + private static final String SERVICES_PATTERN = "META-INF/services/**" + + private static final String GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATTERN = + "META-INF/services/org.codehaus.groovy.runtime.ExtensionModule" + + Map serviceEntries = [:].withDefault { new ServiceStream() } + + private final PatternSet patternSet = + new PatternSet().include(SERVICES_PATTERN).exclude(GROOVY_EXTENSION_MODULE_DESCRIPTOR_PATTERN) + + void setPath(String path) { + patternSet.setIncludes(["${path}/**"]) + } + + @Override + boolean canTransformResource(FileTreeElement element) { + return patternSet.asSpec.isSatisfiedBy(element) + } + + @Override + void transform(TransformerContext context) { + def lines = context.inputStream.readLines() + def targetPath = context.path + context.relocators.each {rel -> + if(rel.canRelocateClass(RelocateClassContext.builder().className(new File(targetPath).name).stats(context.stats).build())) { + targetPath = rel.relocateClass(RelocateClassContext.builder().className(targetPath).stats(context.stats).build()) + } + lines.eachWithIndex { String line, int i -> + def lineContext = RelocateClassContext.builder().className(line).stats(context.stats).build() + if(rel.canRelocateClass(lineContext)) { + lines[i] = rel.relocateClass(lineContext) + } + } + } + lines.each {line -> serviceEntries[targetPath].append(new ByteArrayInputStream(line.getBytes()))} + } + + @Override + boolean hasTransformedResource() { + return serviceEntries.size() > 0 + } + + @Override + void modifyOutputStream(ZipOutputStream os, boolean preserveFileTimestamps) { + serviceEntries.each { String path, ServiceStream stream -> + ZipEntry entry = new ZipEntry(path) + entry.time = TransformerContext.getEntryTimestamp(preserveFileTimestamps, entry.time) + os.putNextEntry(entry) + Utils.copyLarge(stream.toInputStream(), os) + os.closeEntry() + } + } + + @Override + ServiceFileTransformer include(String... includes) { + patternSet.include(includes) + this + } + + @Override + ServiceFileTransformer include(Iterable includes) { + patternSet.include(includes) + this + } + + @Override + ServiceFileTransformer include(Spec includeSpec) { + patternSet.include(includeSpec) + this + } + + @Override + ServiceFileTransformer include(Closure includeSpec) { + patternSet.include(includeSpec) + this + } + + @Override + ServiceFileTransformer exclude(String... excludes) { + patternSet.exclude(excludes) + this + } + + @Override + ServiceFileTransformer exclude(Iterable excludes) { + patternSet.exclude(excludes) + this + } + + @Override + ServiceFileTransformer exclude(Spec excludeSpec) { + patternSet.exclude(excludeSpec) + this + } + + @Override + ServiceFileTransformer exclude(Closure excludeSpec) { + patternSet.exclude(excludeSpec) + this + } + + @Override + Set getIncludes() { + patternSet.includes + } + + @Override + ServiceFileTransformer setIncludes(Iterable includes) { + patternSet.includes = includes + this + } + + @Override + Set getExcludes() { + patternSet.excludes + } + + @Override + ServiceFileTransformer setExcludes(Iterable excludes) { + patternSet.excludes = excludes + this + } +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/Transformer.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/Transformer.groovy new file mode 100644 index 0000000..f282e2c --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/Transformer.groovy @@ -0,0 +1,18 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.xbib.gradle.plugin.shadow.zip.ZipOutputStream + +/** + * Modified from org.apache.maven.plugins.shade.resource.ResourceTransformer. + */ +interface Transformer { + + boolean canTransformResource(FileTreeElement element) + + void transform(TransformerContext context) + + boolean hasTransformedResource() + + void modifyOutputStream(ZipOutputStream jos, boolean preserveFileTimestamps) +} diff --git a/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerContext.groovy b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerContext.groovy new file mode 100644 index 0000000..dd77a2c --- /dev/null +++ b/gradle-plugin-shadow/src/main/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerContext.groovy @@ -0,0 +1,24 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import groovy.transform.Canonical +import groovy.transform.builder.Builder +import org.xbib.gradle.plugin.shadow.ShadowStats +import org.xbib.gradle.plugin.shadow.relocation.Relocator +import org.xbib.gradle.plugin.shadow.tasks.ShadowCopyAction + +@Canonical +@Builder +class TransformerContext { + + String path + + InputStream inputStream + + List relocators + + ShadowStats stats + + static long getEntryTimestamp(boolean preserveFileTimestamps, long entryTime) { + preserveFileTimestamps ? entryTime : ShadowCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/internal/ServiceStream.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/internal/ServiceStream.java new file mode 100644 index 0000000..10c8189 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/internal/ServiceStream.java @@ -0,0 +1,28 @@ +package org.xbib.gradle.plugin.shadow.internal; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * This class was moved to java because groovy compiler reports "no such property: count" + */ +public class ServiceStream extends ByteArrayOutputStream { + + public ServiceStream() { + super(1024); + } + + public void append(InputStream is) throws IOException { + if (count > 0 && buf[count - 1] != '\n' && buf[count - 1] != '\r') { + byte[] newline = new byte[] {'\n'}; + write(newline, 0, newline.length); + } + is.transferTo(this); + } + + public InputStream toInputStream() { + return new ByteArrayInputStream(buf, 0, count); + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/AbstractUnicodeExtraField.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/AbstractUnicodeExtraField.java new file mode 100644 index 0000000..2b94d76 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/AbstractUnicodeExtraField.java @@ -0,0 +1,159 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.nio.charset.StandardCharsets; +import java.util.zip.CRC32; +import java.util.zip.ZipException; + +/** + * A common base class for Unicode extra information extra fields. + */ +public abstract class AbstractUnicodeExtraField implements ZipExtraField { + private long nameCRC32; + private byte[] unicodeName; + private byte[] data; + + protected AbstractUnicodeExtraField() { + } + + /** + * Assemble as unicode extension from the name/comment and + * encoding of the original zip entry. + * + * @param text The file name or comment. + * @param bytes The encoded of the filename or comment in the zip + * file. + * @param off The offset of the encoded filename or comment in + * bytes. + * @param len The length of the encoded filename or comment in + * bytes. + */ + protected AbstractUnicodeExtraField(final String text, final byte[] bytes, final int off, + final int len) { + final CRC32 crc32 = new CRC32(); + crc32.update(bytes, off, len); + nameCRC32 = crc32.getValue(); + + unicodeName = text.getBytes(StandardCharsets.UTF_8); + } + + /** + * Assemble as unicode extension from the name/comment and + * encoding of the original zip entry. + * + * @param text The file name or comment. + * @param bytes The encoded of the filename or comment in the zip + * file. + */ + protected AbstractUnicodeExtraField(final String text, final byte[] bytes) { + + this(text, bytes, 0, bytes.length); + } + + private void assembleData() { + if (unicodeName == null) { + return; + } + + data = new byte[5 + unicodeName.length]; + // version 1 + data[0] = 0x01; + System.arraycopy(ZipLong.getBytes(nameCRC32), 0, data, 1, 4); + System.arraycopy(unicodeName, 0, data, 5, unicodeName.length); + } + + /** + * @return The CRC32 checksum of the filename or comment as + * encoded in the central directory of the zip file. + */ + public long getNameCRC32() { + return nameCRC32; + } + + /** + * @param nameCRC32 The CRC32 checksum of the filename as encoded + * in the central directory of the zip file to set. + */ + public void setNameCRC32(final long nameCRC32) { + this.nameCRC32 = nameCRC32; + data = null; + } + + /** + * @return The utf-8 encoded name. + */ + public byte[] getUnicodeName() { + byte[] b = null; + if (unicodeName != null) { + b = new byte[unicodeName.length]; + System.arraycopy(unicodeName, 0, b, 0, b.length); + } + return b; + } + + /** + * @param unicodeName The utf-8 encoded name to set. + */ + public void setUnicodeName(final byte[] unicodeName) { + if (unicodeName != null) { + this.unicodeName = new byte[unicodeName.length]; + System.arraycopy(unicodeName, 0, this.unicodeName, 0, + unicodeName.length); + } else { + this.unicodeName = null; + } + data = null; + } + + /** {@inheritDoc} */ + public byte[] getCentralDirectoryData() { + if (data == null) { + this.assembleData(); + } + byte[] b = null; + if (data != null) { + b = new byte[data.length]; + System.arraycopy(data, 0, b, 0, b.length); + } + return b; + } + + /** {@inheritDoc} */ + public ZipShort getCentralDirectoryLength() { + if (data == null) { + assembleData(); + } + return new ZipShort(data.length); + } + + /** {@inheritDoc} */ + public byte[] getLocalFileDataData() { + return getCentralDirectoryData(); + } + + /** {@inheritDoc} */ + public ZipShort getLocalFileDataLength() { + return getCentralDirectoryLength(); + } + + /** {@inheritDoc} */ + public void parseFromLocalFileData(final byte[] buffer, final int offset, final int length) + throws ZipException { + + if (length < 5) { + throw new ZipException("UniCode path extra data must have at least" + + " 5 bytes."); + } + + final int version = buffer[offset]; + + if (version != 0x01) { + throw new ZipException("Unsupported version [" + version + + "] for UniCode path extra data."); + } + + nameCRC32 = ZipLong.getValue(buffer, offset + 1); + unicodeName = new byte[length - 5]; + System.arraycopy(buffer, offset + 5, unicodeName, 0, length - 5); + data = null; + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/AsiExtraField.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/AsiExtraField.java new file mode 100644 index 0000000..c4e061a --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/AsiExtraField.java @@ -0,0 +1,299 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.util.zip.CRC32; +import java.util.zip.ZipException; + +/** + * Adds Unix file permission and UID/GID fields as well as symbolic + * link handling. + * + *

This class uses the ASi extra field in the format:

+ *
+ *         Value         Size            Description
+ *         -----         ----            -----------
+ * (Unix3) 0x756e        Short           tag for this extra block type
+ *         TSize         Short           total data size for this block
+ *         CRC           Long            CRC-32 of the remaining data
+ *         Mode          Short           file permissions
+ *         SizDev        Long            symlink'd size OR major/minor dev num
+ *         UID           Short           user ID
+ *         GID           Short           group ID
+ *         (var.)        variable        symbolic link filename
+ * 
+ * taken from appnote.iz (Info-ZIP note, 981119) found at ftp://ftp.uu.net/pub/archiving/zip/doc/ + + * + *

Short is two bytes and Long is four bytes in big endian byte and + * word order, device numbers are currently not supported.

+ * + *

Since the documentation this class is based upon doesn't mention + * the character encoding of the file name at all, it is assumed that + * it uses the current platform's default encoding.

+ */ +public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable { + + private static final ZipShort HEADER_ID = new ZipShort(0x756E); + private static final int WORD = 4; + /** + * Standard Unix stat(2) file mode. + */ + private int mode = 0; + /** + * User ID. + */ + private int uid = 0; + /** + * Group ID. + */ + private int gid = 0; + /** + * File this entry points to, if it is a symbolic link. + * + *

empty string - if entry is not a symbolic link.

+ */ + private String link = ""; + /** + * Is this an entry for a directory? + */ + private boolean dirFlag = false; + + /** + * Instance used to calculate checksums. + */ + private CRC32 crc = new CRC32(); + + /** Constructor for AsiExtraField. */ + public AsiExtraField() { + } + + /** + * The Header-ID. + * @return the value for the header id for this extrafield + */ + public ZipShort getHeaderId() { + return HEADER_ID; + } + + /** + * Length of the extra field in the local file data - without + * Header-ID or length specifier. + * @return a ZipShort for the length of the data of this extra field + */ + public ZipShort getLocalFileDataLength() { + return new ZipShort(WORD // CRC + + 2 // Mode + + WORD // SizDev + + 2 // UID + + 2 // GID + + getLinkedFile().getBytes().length); + // Uses default charset - see class Javadoc + } + + /** + * Delegate to local file data. + * @return the centralDirectory length + */ + public ZipShort getCentralDirectoryLength() { + return getLocalFileDataLength(); + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * @return get the data + */ + public byte[] getLocalFileDataData() { + // CRC will be added later + byte[] data = new byte[getLocalFileDataLength().getValue() - WORD]; + System.arraycopy(ZipShort.getBytes(getMode()), 0, data, 0, 2); + + byte[] linkArray = getLinkedFile().getBytes(); // Uses default charset - see class Javadoc + System.arraycopy(ZipLong.getBytes(linkArray.length), + 0, data, 2, WORD); + + System.arraycopy(ZipShort.getBytes(getUserId()), + 0, data, 6, 2); + System.arraycopy(ZipShort.getBytes(getGroupId()), + 0, data, 8, 2); + + System.arraycopy(linkArray, 0, data, 10, linkArray.length); + + crc.reset(); + crc.update(data); + long checksum = crc.getValue(); + + byte[] result = new byte[data.length + WORD]; + System.arraycopy(ZipLong.getBytes(checksum), 0, result, 0, WORD); + System.arraycopy(data, 0, result, WORD, data.length); + return result; + } + + /** + * Delegate to local file data. + * @return the local file data + */ + public byte[] getCentralDirectoryData() { + return getLocalFileDataData(); + } + + /** + * Set the user id. + * @param uid the user id + */ + public void setUserId(int uid) { + this.uid = uid; + } + + /** + * Get the user id. + * @return the user id + */ + public int getUserId() { + return uid; + } + + /** + * Set the group id. + * @param gid the group id + */ + public void setGroupId(int gid) { + this.gid = gid; + } + + /** + * Get the group id. + * @return the group id + */ + public int getGroupId() { + return gid; + } + + /** + * Indicate that this entry is a symbolic link to the given filename. + * + * @param name Name of the file this entry links to, empty String + * if it is not a symbolic link. + * + */ + public void setLinkedFile(String name) { + link = name; + mode = getMode(mode); + } + + /** + * Name of linked file + * + * @return name of the file this entry links to if it is a + * symbolic link, the empty string otherwise. + */ + public String getLinkedFile() { + return link; + } + + /** + * Is this entry a symbolic link? + * @return true if this is a symbolic link + */ + public boolean isLink() { + return !getLinkedFile().isEmpty(); + } + + /** + * File mode of this file. + * @param mode the file mode + */ + public void setMode(int mode) { + this.mode = getMode(mode); + } + + /** + * File mode of this file. + * @return the file mode + */ + public int getMode() { + return mode; + } + + /** + * Indicate whether this entry is a directory. + * @param dirFlag if true, this entry is a directory + */ + public void setDirectory(boolean dirFlag) { + this.dirFlag = dirFlag; + mode = getMode(mode); + } + + /** + * Is this entry a directory? + * @return true if this entry is a directory + */ + public boolean isDirectory() { + return dirFlag && !isLink(); + } + + /** + * Populate data from this array as if it was in local file data. + * @param data an array of bytes + * @param offset the start offset + * @param length the number of bytes in the array from offset + * @throws ZipException on error + */ + public void parseFromLocalFileData(byte[] data, int offset, int length) + throws ZipException { + + long givenChecksum = ZipLong.getValue(data, offset); + byte[] tmp = new byte[length - WORD]; + System.arraycopy(data, offset + WORD, tmp, 0, length - WORD); + crc.reset(); + crc.update(tmp); + long realChecksum = crc.getValue(); + if (givenChecksum != realChecksum) { + throw new ZipException("bad CRC checksum " + + Long.toHexString(givenChecksum) + + " instead of " + + Long.toHexString(realChecksum)); + } + + int newMode = ZipShort.getValue(tmp, 0); + byte[] linkArray = new byte[(int) ZipLong.getValue(tmp, 2)]; + uid = ZipShort.getValue(tmp, 6); + gid = ZipShort.getValue(tmp, 8); + + if (linkArray.length == 0) { + link = ""; + } else { + System.arraycopy(tmp, 10, linkArray, 0, linkArray.length); + link = new String(linkArray); // Uses default charset - see class Javadoc + } + setDirectory((newMode & DIR_FLAG) != 0); + setMode(newMode); + } + + /** + * Get the file mode for given permissions with the correct file type. + * @param mode the mode + * @return the type with the mode + */ + protected int getMode(int mode) { + int type = FILE_FLAG; + if (isLink()) { + type = LINK_FLAG; + } else if (isDirectory()) { + type = DIR_FLAG; + } + return type | (mode & PERM_MASK); + } + + @Override + public Object clone() { + try { + AsiExtraField cloned = (AsiExtraField) super.clone(); + cloned.crc = new CRC32(); + return cloned; + } catch (CloneNotSupportedException cnfe) { + // impossible + throw new RuntimeException(cnfe); + } + } +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/CentralDirectoryParsingZipExtraField.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/CentralDirectoryParsingZipExtraField.java new file mode 100644 index 0000000..37d3299 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/CentralDirectoryParsingZipExtraField.java @@ -0,0 +1,20 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.util.zip.ZipException; + +/** + * {@link ZipExtraField ZipExtraField} that knows how to parse central + * directory data. + */ +public interface CentralDirectoryParsingZipExtraField extends ZipExtraField { + /** + * Populate data from this array as if it was in central directory data. + * @param data an array of bytes + * @param offset the start offset + * @param length the number of bytes in the array from offset + * + * @throws ZipException on error + */ + void parseFromCentralDirectoryData(byte[] data, int offset, int length) + throws ZipException; +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ExtraFieldUtils.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ExtraFieldUtils.java new file mode 100644 index 0000000..58aa602 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ExtraFieldUtils.java @@ -0,0 +1,272 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.ZipException; + +/** + * ZipExtraField related methods. + */ +public class ExtraFieldUtils { + + private static final int WORD = 4; + + /** + * Static registry of known extra fields. + */ + private static final Map> implementations; + + static { + implementations = new ConcurrentHashMap<>(); + register(AsiExtraField.class); + register(JarMarker.class); + register(UnicodePathExtraField.class); + register(UnicodeCommentExtraField.class); + register(Zip64ExtendedInformationExtraField.class); + } + + /** + * Register a ZipExtraField implementation. + * + *

The given class must have a no-arg constructor and implement + * the {@link ZipExtraField ZipExtraField interface}.

+ * @param c the class to register + */ + public static void register(Class c) { + try { + ZipExtraField ze = (ZipExtraField) c.getDeclaredConstructor().newInstance(); + implementations.put(ze.getHeaderId(), c); + } catch (ClassCastException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new IllegalStateException("unable to initialize zip extra field implementation"); + } + } + + /** + * Create an instance of the appropriate ExtraField, falls back to + * {@link UnrecognizedExtraField UnrecognizedExtraField}. + * @param headerId the header identifier + * @return an instance of the appropriate ExtraField + * @exception InstantiationException if unable to instantiate the class + * @exception IllegalAccessException if not allowed to instantiate the class + */ + public static ZipExtraField createExtraField(ZipShort headerId) + throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + Class c = implementations.get(headerId); + if (c != null) { + return (ZipExtraField) c.getDeclaredConstructor().newInstance(); + } + UnrecognizedExtraField u = new UnrecognizedExtraField(); + u.setHeaderId(headerId); + return u; + } + + /** + * Split the array into ExtraFields and populate them with the + * given data as local file data, throwing an exception if the + * data cannot be parsed. + * @param data an array of bytes as it appears in local file data + * @return an array of ExtraFields + * @throws ZipException on error + */ + public static ZipExtraField[] parse(byte[] data) throws ZipException { + return parse(data, true, UnparseableExtraField.THROW); + } + + /** + * Split the array into ExtraFields and populate them with the + * given data, throwing an exception if the data cannot be parsed. + * @param data an array of bytes + * @param local whether data originates from the local file data + * or the central directory + * @return an array of ExtraFields + * @throws ZipException on error + */ + public static ZipExtraField[] parse(byte[] data, boolean local) + throws ZipException { + return parse(data, local, UnparseableExtraField.THROW); + } + + /** + * Split the array into ExtraFields and populate them with the + * given data. + * @param data an array of bytes + * @param local whether data originates from the local file data + * or the central directory + * @param onUnparseableData what to do if the extra field data + * cannot be parsed. + * @return an array of ExtraFields + * @throws ZipException on error + */ + public static ZipExtraField[] parse(byte[] data, boolean local, + UnparseableExtraField onUnparseableData) + throws ZipException { + List v = new ArrayList<>(); + int start = 0; + LOOP: + while (start <= data.length - WORD) { + ZipShort headerId = new ZipShort(data, start); + int length = (new ZipShort(data, start + 2)).getValue(); + if (start + WORD + length > data.length) { + switch (onUnparseableData.getKey()) { + case UnparseableExtraField.THROW_KEY: + throw new ZipException("bad extra field starting at " + + start + ". Block length of " + length + + " bytes exceeds remaining data of " + + (data.length - start - WORD) + " bytes."); + case UnparseableExtraField.READ_KEY: + UnparseableExtraFieldData field = new UnparseableExtraFieldData(); + if (local) { + field.parseFromLocalFileData(data, start, data.length - start); + } else { + field.parseFromCentralDirectoryData(data, start, data.length - start); + } + v.add(field); + //$FALL-THROUGH$ + case UnparseableExtraField.SKIP_KEY: + // since we cannot parse the data we must assume + // the extra field consumes the whole rest of the + // available data + break LOOP; + default: + throw new ZipException("unknown UnparseableExtraField key: " + + onUnparseableData.getKey()); + } + } + try { + ZipExtraField ze = createExtraField(headerId); + if (local || !(ze instanceof CentralDirectoryParsingZipExtraField)) { + ze.parseFromLocalFileData(data, start + WORD, length); + } else { + ((CentralDirectoryParsingZipExtraField) ze) + .parseFromCentralDirectoryData(data, start + WORD, length); + } + v.add(ze); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ie) { + throw new ZipException(ie.getMessage()); + } + start += (length + WORD); + } + + ZipExtraField[] result = new ZipExtraField[v.size()]; + return v.toArray(result); + } + + /** + * Merges the local file data fields of the given ZipExtraFields. + * @param data an array of ExtraFiles + * @return an array of bytes + */ + public static byte[] mergeLocalFileDataData(ZipExtraField[] data) { + final boolean lastIsUnparseableHolder = data.length > 0 + && data[data.length - 1] instanceof UnparseableExtraFieldData; + int regularExtraFieldCount = lastIsUnparseableHolder ? data.length - 1 : data.length; + + int sum = WORD * regularExtraFieldCount; + for (ZipExtraField element : data) { + sum += element.getLocalFileDataLength().getValue(); + } + + byte[] result = new byte[sum]; + int start = 0; + for (int i = 0; i < regularExtraFieldCount; i++) { + System.arraycopy(data[i].getHeaderId().getBytes(), + 0, result, start, 2); + System.arraycopy(data[i].getLocalFileDataLength().getBytes(), + 0, result, start + 2, 2); + byte[] local = data[i].getLocalFileDataData(); + System.arraycopy(local, 0, result, start + WORD, local.length); + start += (local.length + WORD); + } + if (lastIsUnparseableHolder) { + byte[] local = data[data.length - 1].getLocalFileDataData(); + System.arraycopy(local, 0, result, start, local.length); + } + return result; + } + + /** + * Merges the central directory fields of the given ZipExtraFields. + * @param data an array of ExtraFields + * @return an array of bytes + */ + public static byte[] mergeCentralDirectoryData(ZipExtraField[] data) { + final boolean lastIsUnparseableHolder = data.length > 0 + && data[data.length - 1] instanceof UnparseableExtraFieldData; + int regularExtraFieldCount = lastIsUnparseableHolder ? data.length - 1 : data.length; + + int sum = WORD * regularExtraFieldCount; + for (ZipExtraField element : data) { + sum += element.getCentralDirectoryLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for (int i = 0; i < regularExtraFieldCount; i++) { + System.arraycopy(data[i].getHeaderId().getBytes(), + 0, result, start, 2); + System.arraycopy(data[i].getCentralDirectoryLength().getBytes(), + 0, result, start + 2, 2); + byte[] local = data[i].getCentralDirectoryData(); + System.arraycopy(local, 0, result, start + WORD, local.length); + start += (local.length + WORD); + } + if (lastIsUnparseableHolder) { + byte[] local = data[data.length - 1].getCentralDirectoryData(); + System.arraycopy(local, 0, result, start, local.length); + } + return result; + } + + /** + * "enum" for the possible actions to take if the extra field + * cannot be parsed. + */ + public static final class UnparseableExtraField { + /** + * Key for "throw an exception" action. + */ + public static final int THROW_KEY = 0; + /** + * Key for "skip" action. + */ + public static final int SKIP_KEY = 1; + /** + * Key for "read" action. + */ + public static final int READ_KEY = 2; + + /** + * Throw an exception if field cannot be parsed. + */ + public static final UnparseableExtraField THROW = new UnparseableExtraField(THROW_KEY); + + /** + * Skip the extra field entirely and don't make its data + * available - effectively removing the extra field data. + */ + public static final UnparseableExtraField SKIP = new UnparseableExtraField(SKIP_KEY); + + /** + * Read the extra field data into an instance of {@link + * UnparseableExtraFieldData UnparseableExtraFieldData}. + */ + public static final UnparseableExtraField READ = new UnparseableExtraField(READ_KEY); + + private final int key; + + private UnparseableExtraField(int k) { + key = k; + } + + /** + * Key of the action to take. + * + * @return int + */ + public int getKey() { + return key; + } + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/FallbackZipEncoding.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/FallbackZipEncoding.java new file mode 100644 index 0000000..fb63453 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/FallbackZipEncoding.java @@ -0,0 +1,72 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * A fallback ZipEncoding, which uses a java.io means to encode names. + * + *

This implementation is not favorable for encodings other than + * utf-8, because java.io encodes unmappable character as question + * marks leading to unreadable ZIP entries on some operating + * systems.

+ * + *

Furthermore this implementation is unable to tell whether a + * given name can be safely encoded or not.

+ * + *

This implementation acts as a last resort implementation, when + * neither {@link Simple8BitZipEncoding} nor {@link NioZipEncoding} is + * available.

+ * + *

The methods of this class are reentrant.

+ */ +class FallbackZipEncoding implements ZipEncoding { + private final String charset; + + /** + * Construct a fallback zip encoding, which uses the platform's + * default charset. + */ + public FallbackZipEncoding() { + this.charset = null; + } + + /** + * Construct a fallback zip encoding, which uses the given charset. + * + * @param charset The name of the charset or {@code null} for + * the platform's default character set. + */ + public FallbackZipEncoding(final String charset) { + this.charset = charset; + } + + /** + * @see ZipEncoding#canEncode(java.lang.String) + */ + public boolean canEncode(final String name) { + return true; + } + + /** + * @see ZipEncoding#encode(java.lang.String) + */ + public ByteBuffer encode(final String name) throws IOException { + if (this.charset == null) { // i.e. use default charset, see no-args constructor + return ByteBuffer.wrap(name.getBytes()); + } else { + return ByteBuffer.wrap(name.getBytes(this.charset)); + } + } + + /** + * @see ZipEncoding#decode(byte[]) + */ + public String decode(final byte[] data) throws IOException { + if (this.charset == null) { // i.e. use default charset, see no-args constructor + return new String(data); + } else { + return new String(data, this.charset); + } + } +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/GeneralPurposeBit.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/GeneralPurposeBit.java new file mode 100644 index 0000000..899b824 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/GeneralPurposeBit.java @@ -0,0 +1,193 @@ +package org.xbib.gradle.plugin.shadow.zip; + +/** + * Parser/encoder for the "general purpose bit" field in ZIP's local + * file and central directory headers. + */ +public final class GeneralPurposeBit implements Cloneable { + /** + * Indicates that the file is encrypted. + */ + private static final int ENCRYPTION_FLAG = 1; + + /** + * Indicates that a data descriptor stored after the file contents + * will hold CRC and size information. + */ + private static final int DATA_DESCRIPTOR_FLAG = 1 << 3; + + /** + * Indicates strong encryption. + */ + private static final int STRONG_ENCRYPTION_FLAG = 1 << 6; + + /** + * Indicates that filenames are written in utf-8. + * + *

The only reason this is public is that {@link + * ZipOutputStream#EFS_FLAG} was public in several versions of + * Apache Ant and we needed a substitute for it.

+ */ + public static final int UFT8_NAMES_FLAG = 1 << 11; + + private boolean languageEncodingFlag = false; + private boolean dataDescriptorFlag = false; + private boolean encryptionFlag = false; + private boolean strongEncryptionFlag = false; + + public GeneralPurposeBit() { + } + + /** + * whether the current entry uses UTF8 for file name and comment. + * + * @return boolean + */ + public boolean usesUTF8ForNames() { + return languageEncodingFlag; + } + + /** + * whether the current entry will use UTF8 for file name and comment. + * + * @param b boolean + */ + public void useUTF8ForNames(boolean b) { + languageEncodingFlag = b; + } + + /** + * whether the current entry uses the data descriptor to store CRC + * and size information + * + * @return boolean + */ + public boolean usesDataDescriptor() { + return dataDescriptorFlag; + } + + /** + * whether the current entry will use the data descriptor to store + * CRC and size information + * + * @param b boolean + */ + public void useDataDescriptor(boolean b) { + dataDescriptorFlag = b; + } + + /** + * whether the current entry is encrypted + * + * @return boolean + */ + public boolean usesEncryption() { + return encryptionFlag; + } + + /** + * whether the current entry will be encrypted + * + * @param b boolean + */ + public void useEncryption(boolean b) { + encryptionFlag = b; + } + + /** + * whether the current entry is encrypted using strong encryption + * + * @return boolean + */ + public boolean usesStrongEncryption() { + return encryptionFlag && strongEncryptionFlag; + } + + /** + * whether the current entry will be encrypted using strong encryption + * + * @param b boolean + */ + public void useStrongEncryption(boolean b) { + strongEncryptionFlag = b; + if (b) { + useEncryption(true); + } + } + + /** + * Encodes the set bits in a form suitable for ZIP archives. + * + * @return byte[] + */ + public byte[] encode() { + byte[] result = new byte[2]; + encode(result, 0); + return result; + } + + /** + * Encodes the set bits in a form suitable for ZIP archives. + * + * @param buf the output buffer + * @param offset + * The offset within the output buffer of the first byte to be written. + * must be non-negative and no larger than buf.length-2 + */ + public void encode(byte[] buf, int offset) { + ZipShort.putShort((dataDescriptorFlag ? DATA_DESCRIPTOR_FLAG : 0) + | (languageEncodingFlag ? UFT8_NAMES_FLAG : 0) + | (encryptionFlag ? ENCRYPTION_FLAG : 0) + | (strongEncryptionFlag ? STRONG_ENCRYPTION_FLAG : 0), + buf, offset); + } + + /** + * Parses the supported flags from the given archive data. + * + * @param data local file header or a central directory entry. + * @param offset offset at which the general purpose bit starts + * @return GeneralPurposeBit + */ + public static GeneralPurposeBit parse(final byte[] data, final int offset) { + final int generalPurposeFlag = ZipShort.getValue(data, offset); + GeneralPurposeBit b = new GeneralPurposeBit(); + b.useDataDescriptor((generalPurposeFlag & DATA_DESCRIPTOR_FLAG) != 0); + b.useUTF8ForNames((generalPurposeFlag & UFT8_NAMES_FLAG) != 0); + b.useStrongEncryption((generalPurposeFlag & STRONG_ENCRYPTION_FLAG) + != 0); + b.useEncryption((generalPurposeFlag & ENCRYPTION_FLAG) != 0); + return b; + } + + @Override + public int hashCode() { + return 3 * (7 * (13 * (17 * (encryptionFlag ? 1 : 0) + + (strongEncryptionFlag ? 1 : 0)) + + (languageEncodingFlag ? 1 : 0)) + + (dataDescriptorFlag ? 1 : 0)); + } + + @Override + public boolean equals(Object o) { + if (o instanceof GeneralPurposeBit) { + GeneralPurposeBit g = (GeneralPurposeBit) o; + return g.encryptionFlag == encryptionFlag + && g.strongEncryptionFlag == strongEncryptionFlag + && g.languageEncodingFlag == languageEncodingFlag + && g.dataDescriptorFlag == dataDescriptorFlag; + } + + return false; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + // impossible + throw new RuntimeException("GeneralPurposeBit is not Cloneable?", ex); //NOSONAR + } + } +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/JarMarker.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/JarMarker.java new file mode 100644 index 0000000..bfea6c9 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/JarMarker.java @@ -0,0 +1,87 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.util.zip.ZipException; + +/** + * If this extra field is added as the very first extra field of the + * archive, Solaris will consider it an executable jar file. + */ +public final class JarMarker implements ZipExtraField { + + private static final ZipShort ID = new ZipShort(0xCAFE); + private static final ZipShort NULL = new ZipShort(0); + private static final byte[] NO_BYTES = new byte[0]; + private static final JarMarker DEFAULT = new JarMarker(); + + /** No-arg constructor */ + public JarMarker() { + // empty + } + + /** + * Since JarMarker is stateless we can always use the same instance. + * @return the DEFAULT jarmaker. + */ + public static JarMarker getInstance() { + return DEFAULT; + } + + /** + * The Header-ID. + * @return the header id + */ + public ZipShort getHeaderId() { + return ID; + } + + /** + * Length of the extra field in the local file data - without + * Header-ID or length specifier. + * @return 0 + */ + public ZipShort getLocalFileDataLength() { + return NULL; + } + + /** + * Length of the extra field in the central directory - without + * Header-ID or length specifier. + * @return 0 + */ + public ZipShort getCentralDirectoryLength() { + return NULL; + } + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * @return the data + */ + public byte[] getLocalFileDataData() { + return NO_BYTES; + } + + /** + * The actual data to put central directory - without Header-ID or + * length specifier. + * @return the data + */ + public byte[] getCentralDirectoryData() { + return NO_BYTES; + } + + /** + * Populate data from this array as if it was in local file data. + * @param data an array of bytes + * @param offset the start offset + * @param length the number of bytes in the array from offset + * + * @throws ZipException on error + */ + public void parseFromLocalFileData(byte[] data, int offset, int length) + throws ZipException { + if (length != 0) { + throw new ZipException("JarMarker doesn't expect any data"); + } + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/NioZipEncoding.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/NioZipEncoding.java new file mode 100644 index 0000000..20f615b --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/NioZipEncoding.java @@ -0,0 +1,100 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CoderResult; +import java.nio.charset.CodingErrorAction; + +/** + * A ZipEncoding, which uses a java.nio {@link + * java.nio.charset.Charset Charset} to encode names. + * + *

This implementation works for all cases under java-1.5 or + * later. However, in java-1.4, some charsets don't have a java.nio + * implementation, most notably the default ZIP encoding Cp437.

+ * + *

The methods of this class are reentrant.

+ */ +class NioZipEncoding implements ZipEncoding { + private final Charset charset; + + /** + * Construct an NIO based zip encoding, which wraps the given + * charset. + * + * @param charset The NIO charset to wrap. + */ + public NioZipEncoding(final Charset charset) { + this.charset = charset; + } + + /** + * @see ZipEncoding#canEncode(java.lang.String) + */ + public boolean canEncode(final String name) { + final CharsetEncoder enc = this.charset.newEncoder(); + enc.onMalformedInput(CodingErrorAction.REPORT); + enc.onUnmappableCharacter(CodingErrorAction.REPORT); + + return enc.canEncode(name); + } + + /** + * @see ZipEncoding#encode(java.lang.String) + */ + public ByteBuffer encode(final String name) { + final CharsetEncoder enc = this.charset.newEncoder(); + + enc.onMalformedInput(CodingErrorAction.REPORT); + enc.onUnmappableCharacter(CodingErrorAction.REPORT); + + final CharBuffer cb = CharBuffer.wrap(name); + ByteBuffer out = ByteBuffer.allocate(name.length() + + (name.length() + 1) / 2); + + while (cb.remaining() > 0) { + final CoderResult res = enc.encode(cb, out, true); + + if (res.isUnmappable() || res.isMalformed()) { + + // write the unmappable characters in utf-16 + // pseudo-URL encoding style to ByteBuffer. + if (res.length() * 6 > out.remaining()) { + out = ZipEncodingHelper.growBuffer(out, out.position() + + res.length() * 6); + } + + for (int i = 0; i < res.length(); ++i) { + ZipEncodingHelper.appendSurrogate(out, cb.get()); + } + + } else if (res.isOverflow()) { + + out = ZipEncodingHelper.growBuffer(out, 0); + + } else if (res.isUnderflow()) { + + enc.flush(out); + break; + + } + } + + out.limit(out.position()); + out.rewind(); + return out; + } + + /** + * @see ZipEncoding#decode(byte[]) + */ + public String decode(final byte[] data) throws IOException { + return this.charset.newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(data)).toString(); + } +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Simple8BitZipEncoding.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Simple8BitZipEncoding.java new file mode 100644 index 0000000..309f116 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Simple8BitZipEncoding.java @@ -0,0 +1,250 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This ZipEncoding implementation implements a simple 8 bit character + * set, which meets the following restrictions: + * + *
    + *
  • Characters 0x0000 to 0x007f are encoded as the corresponding + * byte values 0x00 to 0x7f.
  • + *
  • All byte codes from 0x80 to 0xff are mapped to a unique Unicode + * character in the range 0x0080 to 0x7fff. (No support for + * UTF-16 surrogates) + *
+ * + *

These restrictions most notably apply to the most prominent + * omissions of Java 1.4 {@link java.nio.charset.Charset Charset} + * implementation, Cp437 and Cp850.

+ * + *

The methods of this class are reentrant.

+ */ +class Simple8BitZipEncoding implements ZipEncoding { + + /** + * A character entity, which is put to the reverse mapping table + * of a simple encoding. + */ + private static final class Simple8BitChar implements Comparable { + public final char unicode; + public final byte code; + + Simple8BitChar(final byte code, final char unicode) { + this.code = code; + this.unicode = unicode; + } + + public int compareTo(final Simple8BitChar a) { + return this.unicode - a.unicode; + } + + @Override + public String toString() { + return "0x" + Integer.toHexString(0xffff & unicode) + + "->0x" + Integer.toHexString(0xff & code); + } + + @Override + public boolean equals(final Object o) { + if (o instanceof Simple8BitChar) { + final Simple8BitChar other = (Simple8BitChar) o; + return unicode == other.unicode && code == other.code; + } + return false; + } + + @Override + public int hashCode() { + return unicode; + } + } + + /** + * The characters for byte values of 128 to 255 stored as an array of + * 128 chars. + */ + private final char[] highChars; + + /** + * A list of {@link Simple8BitChar} objects sorted by the unicode + * field. This list is used to binary search reverse mapping of + * unicode characters with a character code greater than 127. + */ + private final List reverseMapping; + + /** + * @param highChars The characters for byte values of 128 to 255 + * stored as an array of 128 chars. + */ + public Simple8BitZipEncoding(final char[] highChars) { + this.highChars = highChars.clone(); + final List temp = + new ArrayList<>(this.highChars.length); + + byte code = 127; + + for (char highChar : this.highChars) { + temp.add(new Simple8BitChar(++code, highChar)); + } + + Collections.sort(temp); + this.reverseMapping = Collections.unmodifiableList(temp); + } + + /** + * Return the character code for a given encoded byte. + * + * @param b The byte to decode. + * @return The associated character value. + */ + public char decodeByte(final byte b) { + // code 0-127 + if (b >= 0) { + return (char) b; + } + + // byte is signed, so 128 == -128 and 255 == -1 + return this.highChars[128 + b]; + } + + /** + * @param c The character to encode. + * @return Whether the given unicode character is covered by this encoding. + */ + public boolean canEncodeChar(final char c) { + + if (c >= 0 && c < 128) { + return true; + } + + final Simple8BitChar r = this.encodeHighChar(c); + return r != null; + } + + /** + * Pushes the encoded form of the given character to the given byte buffer. + * + * @param bb The byte buffer to write to. + * @param c The character to encode. + * @return Whether the given unicode character is covered by this encoding. + * If {@code false} is returned, nothing is pushed to the + * byte buffer. + */ + public boolean pushEncodedChar(final ByteBuffer bb, final char c) { + + if (c >= 0 && c < 128) { + bb.put((byte) c); + return true; + } + + final Simple8BitChar r = this.encodeHighChar(c); + if (r == null) { + return false; + } + bb.put(r.code); + return true; + } + + /** + * @param c A unicode character in the range from 0x0080 to 0x7f00 + * @return A Simple8BitChar, if this character is covered by this encoding. + * A {@code null} value is returned, if this character is not + * covered by this encoding. + */ + private Simple8BitChar encodeHighChar(final char c) { + // for performance an simplicity, yet another reincarnation of + // binary search... + int i0 = 0; + int i1 = this.reverseMapping.size(); + + while (i1 > i0) { + + final int i = i0 + (i1 - i0) / 2; + + final Simple8BitChar m = this.reverseMapping.get(i); + + if (m.unicode == c) { + return m; + } + + if (m.unicode < c) { + i0 = i + 1; + } else { + i1 = i; + } + } + + if (i0 >= this.reverseMapping.size()) { + return null; + } + + final Simple8BitChar r = this.reverseMapping.get(i0); + + if (r.unicode != c) { + return null; + } + + return r; + } + + /** + * @see ZipEncoding#canEncode(java.lang.String) + */ + public boolean canEncode(final String name) { + + for (int i = 0; i < name.length(); ++i) { + + final char c = name.charAt(i); + + if (!this.canEncodeChar(c)) { + return false; + } + } + + return true; + } + + /** + * @see ZipEncoding#encode(java.lang.String) + */ + public ByteBuffer encode(final String name) { + ByteBuffer out = ByteBuffer.allocate(name.length() + + 6 + (name.length() + 1) / 2); + + for (int i = 0; i < name.length(); ++i) { + final char c = name.charAt(i); + + if (out.remaining() < 6) { + out = ZipEncodingHelper.growBuffer(out, out.position() + 6); + } + + if (!this.pushEncodedChar(out, c)) { + ZipEncodingHelper.appendSurrogate(out, c); + } + } + + out.limit(out.position()); + out.rewind(); + return out; + } + + /** + * @see ZipEncoding#decode(byte[]) + */ + public String decode(final byte[] data) throws IOException { + final char[] ret = new char[data.length]; + + for (int i = 0; i < data.length; ++i) { + ret[i] = this.decodeByte(data[i]); + } + + return new String(ret); + } + + +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnicodeCommentExtraField.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnicodeCommentExtraField.java new file mode 100644 index 0000000..229dfe0 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnicodeCommentExtraField.java @@ -0,0 +1,51 @@ +package org.xbib.gradle.plugin.shadow.zip; + +/** + * Info-ZIP Unicode Comment Extra Field (0x6375): + * + *

Stores the UTF-8 version of the file comment as stored in the + * central directory header.

+ * + *

See PKWARE's + * APPNOTE.TXT, section 4.6.8.

+ * + */ +public class UnicodeCommentExtraField extends AbstractUnicodeExtraField { + + public static final ZipShort UCOM_ID = new ZipShort(0x6375); + + public UnicodeCommentExtraField() { + } + + /** + * Assemble as unicode comment extension from the name given as + * text as well as the encoded bytes actually written to the archive. + * + * @param text The file name + * @param bytes the bytes actually written to the archive + * @param off The offset of the encoded comment in bytes. + * @param len The length of the encoded comment or comment in + * bytes. + */ + public UnicodeCommentExtraField(final String text, final byte[] bytes, final int off, + final int len) { + super(text, bytes, off, len); + } + + /** + * Assemble as unicode comment extension from the comment given as + * text as well as the bytes actually written to the archive. + * + * @param comment The file comment + * @param bytes the bytes actually written to the archive + */ + public UnicodeCommentExtraField(final String comment, final byte[] bytes) { + super(comment, bytes); + } + + /** {@inheritDoc} */ + public ZipShort getHeaderId() { + return UCOM_ID; + } + +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnicodePathExtraField.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnicodePathExtraField.java new file mode 100644 index 0000000..732f7ab --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnicodePathExtraField.java @@ -0,0 +1,48 @@ +package org.xbib.gradle.plugin.shadow.zip; + +/** + * Info-ZIP Unicode Path Extra Field (0x7075): + * + *

Stores the UTF-8 version of the file name field as stored in the + * local header and central directory header.

+ * + *

See PKWARE's + * APPNOTE.TXT, section 4.6.9.

+ */ +public class UnicodePathExtraField extends AbstractUnicodeExtraField { + + public static final ZipShort UPATH_ID = new ZipShort(0x7075); + + public UnicodePathExtraField() { + } + + /** + * Assemble as unicode path extension from the name given as + * text as well as the encoded bytes actually written to the archive. + * + * @param text The file name + * @param bytes the bytes actually written to the archive + * @param off The offset of the encoded filename in bytes. + * @param len The length of the encoded filename or comment in + * bytes. + */ + public UnicodePathExtraField(final String text, final byte[] bytes, final int off, final int len) { + super(text, bytes, off, len); + } + + /** + * Assemble as unicode path extension from the name given as + * text as well as the encoded bytes actually written to the archive. + * + * @param name The file name + * @param bytes the bytes actually written to the archive + */ + public UnicodePathExtraField(final String name, final byte[] bytes) { + super(name, bytes); + } + + /** {@inheritDoc} */ + public ZipShort getHeaderId() { + return UPATH_ID; + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnixStat.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnixStat.java new file mode 100644 index 0000000..42efd92 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnixStat.java @@ -0,0 +1,38 @@ +package org.xbib.gradle.plugin.shadow.zip; + +/** + * Constants from stat.h on Unix systems. + * + */ +public interface UnixStat { + + /** + * Bits used for permissions (and sticky bit) + */ + int PERM_MASK = 07777; + /** + * Indicates symbolic links. + */ + int LINK_FLAG = 0120000; + /** + * Indicates plain files. + */ + int FILE_FLAG = 0100000; + /** + * Indicates directories. + */ + int DIR_FLAG = 040000; + + /** + * Default permissions for symbolic links. + */ + int DEFAULT_LINK_PERM = 0777; + /** + * Default permissions for directories. + */ + int DEFAULT_DIR_PERM = 0755; + /** + * Default permissions for plain files. + */ + int DEFAULT_FILE_PERM = 0644; +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnparseableExtraFieldData.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnparseableExtraFieldData.java new file mode 100644 index 0000000..7681d57 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnparseableExtraFieldData.java @@ -0,0 +1,94 @@ +package org.xbib.gradle.plugin.shadow.zip; + +/** + * Wrapper for extra field data that doesn't conform to the recommended format of header-tag + size + data. + * + *

The header-id is artificial (and not listed as a known ID in + * APPNOTE.TXT). + * Since it isn't used anywhere except to satisfy the + * ZipExtraField contract it shouldn't matter anyway.

+ */ +public final class UnparseableExtraFieldData + implements CentralDirectoryParsingZipExtraField { + + private static final ZipShort HEADER_ID = new ZipShort(0xACC1); + + private byte[] localFileData; + private byte[] centralDirectoryData; + + /** + * The Header-ID. + * + * @return a completely arbitrary value that should be ignored. + */ + public ZipShort getHeaderId() { + return HEADER_ID; + } + + /** + * Length of the complete extra field in the local file data. + * + * @return The LocalFileDataLength value + */ + public ZipShort getLocalFileDataLength() { + return new ZipShort(localFileData == null ? 0 : localFileData.length); + } + + /** + * Length of the complete extra field in the central directory. + * + * @return The CentralDirectoryLength value + */ + public ZipShort getCentralDirectoryLength() { + return centralDirectoryData == null + ? getLocalFileDataLength() + : new ZipShort(centralDirectoryData.length); + } + + /** + * The actual data to put into local file data. + * + * @return The LocalFileDataData value + */ + public byte[] getLocalFileDataData() { + return ZipUtil.copy(localFileData); + } + + /** + * The actual data to put into central directory. + * + * @return The CentralDirectoryData value + */ + public byte[] getCentralDirectoryData() { + return centralDirectoryData == null + ? getLocalFileDataData() : ZipUtil.copy(centralDirectoryData); + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param buffer the buffer to read data from + * @param offset offset into buffer to read data + * @param length the length of data + */ + public void parseFromLocalFileData(byte[] buffer, int offset, int length) { + localFileData = new byte[length]; + System.arraycopy(buffer, offset, localFileData, 0, length); + } + + /** + * Populate data from this array as if it was in central directory data. + * + * @param buffer the buffer to read data from + * @param offset offset into buffer to read data + * @param length the length of data + */ + public void parseFromCentralDirectoryData(byte[] buffer, int offset, + int length) { + centralDirectoryData = new byte[length]; + System.arraycopy(buffer, offset, centralDirectoryData, 0, length); + if (localFileData == null) { + parseFromLocalFileData(buffer, offset, length); + } + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnrecognizedExtraField.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnrecognizedExtraField.java new file mode 100644 index 0000000..42039a1 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnrecognizedExtraField.java @@ -0,0 +1,130 @@ +package org.xbib.gradle.plugin.shadow.zip; + +/** + * Simple placeholder for all those extra fields we don't want to deal + * with. + * + *

Assumes local file data and central directory entries are + * identical - unless told the opposite.

+ * + */ +public class UnrecognizedExtraField + implements CentralDirectoryParsingZipExtraField { + + /** + * The Header-ID. + */ + private ZipShort headerId; + + /** + * Set the header id. + * @param headerId the header id to use + */ + public void setHeaderId(ZipShort headerId) { + this.headerId = headerId; + } + + /** + * Get the header id. + * @return the header id + */ + public ZipShort getHeaderId() { + return headerId; + } + + /** + * Extra field data in local file data - without + * Header-ID or length specifier. + */ + private byte[] localData; + + /** + * Set the extra field data in the local file data - + * without Header-ID or length specifier. + * @param data the field data to use + */ + public void setLocalFileDataData(byte[] data) { + localData = ZipUtil.copy(data); + } + + /** + * Get the length of the local data. + * @return the length of the local data + */ + public ZipShort getLocalFileDataLength() { + return new ZipShort(localData.length); + } + + /** + * Get the local data. + * @return the local data + */ + public byte[] getLocalFileDataData() { + return ZipUtil.copy(localData); + } + + /** + * Extra field data in central directory - without + * Header-ID or length specifier. + */ + private byte[] centralData; + + /** + * Set the extra field data in central directory. + * @param data the data to use + */ + public void setCentralDirectoryData(byte[] data) { + centralData = ZipUtil.copy(data); + } + + /** + * Get the central data length. + * If there is no central data, get the local file data length. + * @return the central data length + */ + public ZipShort getCentralDirectoryLength() { + if (centralData != null) { + return new ZipShort(centralData.length); + } + return getLocalFileDataLength(); + } + + /** + * Get the central data. + * @return the central data if present, else return the local file data + */ + public byte[] getCentralDirectoryData() { + if (centralData != null) { + return ZipUtil.copy(centralData); + } + return getLocalFileDataData(); + } + + /** + * @param data the array of bytes. + * @param offset the source location in the data array. + * @param length the number of bytes to use in the data array. + * @see ZipExtraField#parseFromLocalFileData(byte[], int, int) + */ + public void parseFromLocalFileData(byte[] data, int offset, int length) { + byte[] tmp = new byte[length]; + System.arraycopy(data, offset, tmp, 0, length); + setLocalFileDataData(tmp); + } + + /** + * @param data the array of bytes. + * @param offset the source location in the data array. + * @param length the number of bytes to use in the data array. + */ + public void parseFromCentralDirectoryData(byte[] data, int offset, + int length) { + byte[] tmp = new byte[length]; + System.arraycopy(data, offset, tmp, 0, length); + setCentralDirectoryData(tmp); + if (localData == null) { + setLocalFileDataData(tmp); + } + } + +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnsupportedZipFeatureException.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnsupportedZipFeatureException.java new file mode 100644 index 0000000..deae466 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/UnsupportedZipFeatureException.java @@ -0,0 +1,76 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.Serializable; +import java.util.zip.ZipException; + +/** + * Exception thrown when attempting to read or write data for a zip + * entry that uses ZIP features not supported by this library. + */ +public class UnsupportedZipFeatureException extends ZipException { + + private final Feature reason; + private final transient ZipEntry entry; + private static final long serialVersionUID = 20161221L; + + /** + * Creates an exception. + * @param reason the feature that is not supported + * @param entry the entry using the feature + */ + public UnsupportedZipFeatureException(Feature reason, + ZipEntry entry) { + super("unsupported feature " + reason + " used in entry " + + entry.getName()); + this.reason = reason; + this.entry = entry; + } + + /** + * The unsupported feature that has been used. + * + * @return Feature + */ + public Feature getFeature() { + return reason; + } + + /** + * The entry using the unsupported feature. + * + * @return ZipEntry + */ + public ZipEntry getEntry() { + return entry; + } + + /** + * ZIP Features that may or may not be supported. + */ + @SuppressWarnings("serial") + public static class Feature implements Serializable { + /** + * The entry is encrypted. + */ + public static final Feature ENCRYPTION = new Feature("encryption"); + /** + * The entry used an unsupported compression method. + */ + public static final Feature METHOD = new Feature("compression method"); + /** + * The entry uses a data descriptor. + */ + public static final Feature DATA_DESCRIPTOR = new Feature("data descriptor"); + + private final String name; + + private Feature(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64ExtendedInformationExtraField.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64ExtendedInformationExtraField.java new file mode 100644 index 0000000..d922283 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64ExtendedInformationExtraField.java @@ -0,0 +1,323 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.util.zip.ZipException; + +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.DWORD; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.WORD; + +/** + * Holds size and other extended information for entries that use Zip64 + * features. + * + *

See PKWARE's + * APPNOTE.TXT, section 4.5.3.

+ * + *

Currently Ant doesn't support encrypting the + * central directory so the note about masking doesn't apply.

+ * + *

The implementation relies on data being read from the local file + * header and assumes that both size values are always present.

+ */ +public class Zip64ExtendedInformationExtraField + implements CentralDirectoryParsingZipExtraField { + + static final ZipShort HEADER_ID = new ZipShort(0x0001); + + private static final String LFH_MUST_HAVE_BOTH_SIZES_MSG = + "Zip64 extended information must contain" + + " both size values in the local file header."; + private static final byte[] EMPTY = new byte[0]; + + private ZipEightByteInteger size, compressedSize, relativeHeaderOffset; + private ZipLong diskStart; + + /** + * Stored in {@link #parseFromCentralDirectoryData + * parseFromCentralDirectoryData} so it can be reused when ZipFile + * calls {@link #reparseCentralDirectoryData + * reparseCentralDirectoryData}. + * + *

Not used for anything else

+ */ + private byte[] rawCentralDirectoryData; + + /** + * This constructor should only be used by the code that reads + * archives inside of Ant. + */ + public Zip64ExtendedInformationExtraField() { } + + /** + * Creates an extra field based on the original and compressed size. + * + * @param size the entry's original size + * @param compressedSize the entry's compressed size + * @throws IllegalArgumentException if size or compressedSize is null + */ + public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, + ZipEightByteInteger compressedSize) { + this(size, compressedSize, null, null); + } + + /** + * Creates an extra field based on all four possible values. + * + * @param size the entry's original size + * @param compressedSize the entry's compressed size + * @param relativeHeaderOffset ZipEightByteInteger + * @param diskStart ZipLong + * @throws IllegalArgumentException if size or compressedSize is null + */ + public Zip64ExtendedInformationExtraField(ZipEightByteInteger size, + ZipEightByteInteger compressedSize, + ZipEightByteInteger relativeHeaderOffset, + ZipLong diskStart) { + this.size = size; + this.compressedSize = compressedSize; + this.relativeHeaderOffset = relativeHeaderOffset; + this.diskStart = diskStart; + } + + /** {@inheritDoc} */ + public ZipShort getHeaderId() { + return HEADER_ID; + } + + /** {@inheritDoc} */ + public ZipShort getLocalFileDataLength() { + return new ZipShort(size != null ? 2 * DWORD : 0); + } + + /** {@inheritDoc} */ + public ZipShort getCentralDirectoryLength() { + return new ZipShort((size != null ? DWORD : 0) + + (compressedSize != null ? DWORD : 0) + + (relativeHeaderOffset != null ? DWORD : 0) + + (diskStart != null ? WORD : 0)); + } + + /** {@inheritDoc} */ + public byte[] getLocalFileDataData() { + if (size != null || compressedSize != null) { + if (size == null || compressedSize == null) { + throw new IllegalArgumentException(LFH_MUST_HAVE_BOTH_SIZES_MSG); + } + byte[] data = new byte[2 * DWORD]; + addSizes(data); + return data; + } + return EMPTY; + } + + /** {@inheritDoc} */ + public byte[] getCentralDirectoryData() { + byte[] data = new byte[getCentralDirectoryLength().getValue()]; + int off = addSizes(data); + if (relativeHeaderOffset != null) { + System.arraycopy(relativeHeaderOffset.getBytes(), 0, data, off, DWORD); + off += DWORD; + } + if (diskStart != null) { + System.arraycopy(diskStart.getBytes(), 0, data, off, WORD); + off += WORD; + } + return data; + } + + /** {@inheritDoc} */ + public void parseFromLocalFileData(byte[] buffer, int offset, int length) + throws ZipException { + if (length == 0) { + // no local file data at all, may happen if an archive + // only holds a ZIP64 extended information extra field + // inside the central directory but not inside the local + // file header + return; + } + if (length < 2 * DWORD) { + throw new ZipException(LFH_MUST_HAVE_BOTH_SIZES_MSG); + } + size = new ZipEightByteInteger(buffer, offset); + offset += DWORD; + compressedSize = new ZipEightByteInteger(buffer, offset); + offset += DWORD; + int remaining = length - 2 * DWORD; + if (remaining >= DWORD) { + relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); + offset += DWORD; + remaining -= DWORD; + } + if (remaining >= WORD) { + diskStart = new ZipLong(buffer, offset); + offset += WORD; + remaining -= WORD; + } + } + + /** {@inheritDoc} */ + public void parseFromCentralDirectoryData(byte[] buffer, int offset, + int length) + throws ZipException { + // store for processing in reparseCentralDirectoryData + rawCentralDirectoryData = new byte[length]; + System.arraycopy(buffer, offset, rawCentralDirectoryData, 0, length); + + // if there is no size information in here, we are screwed and + // can only hope things will get resolved by LFH data later + // But there are some cases that can be detected + // * all data is there + // * length == 24 -> both sizes and offset + // * length % 8 == 4 -> at least we can identify the diskStart field + if (length >= 3 * DWORD + WORD) { + parseFromLocalFileData(buffer, offset, length); + } else if (length == 3 * DWORD) { + size = new ZipEightByteInteger(buffer, offset); + offset += DWORD; + compressedSize = new ZipEightByteInteger(buffer, offset); + offset += DWORD; + relativeHeaderOffset = new ZipEightByteInteger(buffer, offset); + } else if (length % DWORD == WORD) { + diskStart = new ZipLong(buffer, offset + length - WORD); + } + } + + /** + * Parses the raw bytes read from the central directory extra + * field with knowledge which fields are expected to be there. + * + *

All four fields inside the zip64 extended information extra + * field are optional and must only be present if their corresponding + * entry inside the central directory contains the correct magic + * value.

+ * + * @param hasUncompressedSize boolean + * @param hasCompressedSize boolean + * @param hasRelativeHeaderOffset boolean + * @param hasDiskStart boolean + * @throws ZipException if expected length of central directory data is incorrect + */ + public void reparseCentralDirectoryData(boolean hasUncompressedSize, + boolean hasCompressedSize, + boolean hasRelativeHeaderOffset, + boolean hasDiskStart) + throws ZipException { + if (rawCentralDirectoryData != null) { + int expectedLength = (hasUncompressedSize ? DWORD : 0) + + (hasCompressedSize ? DWORD : 0) + + (hasRelativeHeaderOffset ? DWORD : 0) + + (hasDiskStart ? WORD : 0); + if (rawCentralDirectoryData.length < expectedLength) { + throw new ZipException("central directory zip64 extended" + + " information extra field's length" + + " doesn't match central directory" + + " data. Expected length " + + expectedLength + " but is " + + rawCentralDirectoryData.length); + } + int offset = 0; + if (hasUncompressedSize) { + size = new ZipEightByteInteger(rawCentralDirectoryData, offset); + offset += DWORD; + } + if (hasCompressedSize) { + compressedSize = new ZipEightByteInteger(rawCentralDirectoryData, + offset); + offset += DWORD; + } + if (hasRelativeHeaderOffset) { + relativeHeaderOffset = + new ZipEightByteInteger(rawCentralDirectoryData, offset); + offset += DWORD; + } + if (hasDiskStart) { + diskStart = new ZipLong(rawCentralDirectoryData, offset); + offset += WORD; + } + } + } + + /** + * The uncompressed size stored in this extra field. + * + * @return ZipEightByteInteger + */ + public ZipEightByteInteger getSize() { + return size; + } + + /** + * The uncompressed size stored in this extra field. + * + * @param size ZipEightByteInteger + */ + public void setSize(ZipEightByteInteger size) { + this.size = size; + } + + /** + * The compressed size stored in this extra field. + * + * @return ZipEightByteInteger + */ + public ZipEightByteInteger getCompressedSize() { + return compressedSize; + } + + /** + * The uncompressed size stored in this extra field. + * + * @param compressedSize ZipEightByteInteger + */ + public void setCompressedSize(ZipEightByteInteger compressedSize) { + this.compressedSize = compressedSize; + } + + /** + * The relative header offset stored in this extra field. + * + * @return ZipEightByteInteger + */ + public ZipEightByteInteger getRelativeHeaderOffset() { + return relativeHeaderOffset; + } + + /** + * The relative header offset stored in this extra field. + * + * @param rho ZipEightByteInteger + */ + public void setRelativeHeaderOffset(ZipEightByteInteger rho) { + relativeHeaderOffset = rho; + } + + /** + * The disk start number stored in this extra field. + * + * @return ZipLong + */ + public ZipLong getDiskStartNumber() { + return diskStart; + } + + /** + * The disk start number stored in this extra field. + * + * @param ds ZipLong + */ + public void setDiskStartNumber(ZipLong ds) { + diskStart = ds; + } + + private int addSizes(byte[] data) { + int off = 0; + if (size != null) { + System.arraycopy(size.getBytes(), 0, data, 0, DWORD); + off += DWORD; + } + if (compressedSize != null) { + System.arraycopy(compressedSize.getBytes(), 0, data, off, DWORD); + off += DWORD; + } + return off; + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64Mode.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64Mode.java new file mode 100644 index 0000000..743343f --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64Mode.java @@ -0,0 +1,27 @@ +package org.xbib.gradle.plugin.shadow.zip; + +/** + * The different modes {@link ZipOutputStream} can operate in. + * + * @see ZipOutputStream#setUseZip64 + */ +public enum Zip64Mode { + /** + * Use Zip64 extensions for all entries, even if it is clear it is + * not required. + */ + Always, + /** + * Don't use Zip64 extensions for any entries. + * + *

This will cause a {@link Zip64RequiredException} to be + * thrown if {@link ZipOutputStream} detects it needs Zip64 + * support.

+ */ + Never, + /** + * Use Zip64 extensions for all entries where they are required, + * don't use them for entries that clearly don't require them. + */ + AsNeeded +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64RequiredException.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64RequiredException.java new file mode 100644 index 0000000..da88361 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/Zip64RequiredException.java @@ -0,0 +1,30 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.util.zip.ZipException; + +/** + * Exception thrown when attempting to write data that requires Zip64 + * support to an archive and {@link ZipOutputStream#setUseZip64 + * UseZip64} has been set to {@link Zip64Mode#Never Never}. + */ +public class Zip64RequiredException extends ZipException { + + private static final long serialVersionUID = 20110809L; + + /** + * Helper to format "entry too big" messages. + */ + static String getEntryTooBigMessage(ZipEntry ze) { + return ze.getName() + "'s size exceeds the limit of 4GByte."; + } + + static final String ARCHIVE_TOO_BIG_MESSAGE = + "archive's size exceeds the limit of 4GByte."; + + static final String TOO_MANY_ENTRIES_MESSAGE = + "archive contains more than 65535 entries."; + + public Zip64RequiredException(String reason) { + super(reason); + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipConstants.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipConstants.java new file mode 100644 index 0000000..c1b6bf4 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipConstants.java @@ -0,0 +1,43 @@ +package org.xbib.gradle.plugin.shadow.zip; + +/** + * Various constants used throughout the package. + */ +final class ZipConstants { + + private ZipConstants() { } + + /** Masks last eight bits */ + static final int BYTE_MASK = 0xFF; + + /** length of a ZipShort in bytes */ + static final int SHORT = 2; + + /** length of a ZipLong in bytes */ + static final int WORD = 4; + + /** length of a ZipEightByteInteger in bytes */ + static final int DWORD = 8; + + /** Initial ZIP specification version */ + static final int INITIAL_VERSION = 10; + + /** ZIP specification version that introduced data descriptor method */ + static final int DATA_DESCRIPTOR_MIN_VERSION = 20; + + /** ZIP specification version that introduced ZIP64 */ + static final int ZIP64_MIN_VERSION = 45; + + /** + * Value stored in two-byte size and similar fields if ZIP64 + * extensions are used. + */ + static final int ZIP64_MAGIC_SHORT = 0xFFFF; + + /** + * Value stored in four-byte size and similar fields if ZIP64 + * extensions are used. + */ + static final long ZIP64_MAGIC = 0xFFFFFFFFL; + +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEightByteInteger.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEightByteInteger.java new file mode 100644 index 0000000..29b5fbd --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEightByteInteger.java @@ -0,0 +1,210 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.math.BigInteger; + +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.BYTE_MASK; + +/** + * Utility class that represents an eight byte integer with conversion + * rules for the big endian byte order of ZIP files. + */ +public final class ZipEightByteInteger { + + private static final int BYTE_1 = 1; + private static final int BYTE_1_MASK = 0xFF00; + private static final int BYTE_1_SHIFT = 8; + + private static final int BYTE_2 = 2; + private static final int BYTE_2_MASK = 0xFF0000; + private static final int BYTE_2_SHIFT = 16; + + private static final int BYTE_3 = 3; + private static final long BYTE_3_MASK = 0xFF000000L; + private static final int BYTE_3_SHIFT = 24; + + private static final int BYTE_4 = 4; + private static final long BYTE_4_MASK = 0xFF00000000L; + private static final int BYTE_4_SHIFT = 32; + + private static final int BYTE_5 = 5; + private static final long BYTE_5_MASK = 0xFF0000000000L; + private static final int BYTE_5_SHIFT = 40; + + private static final int BYTE_6 = 6; + private static final long BYTE_6_MASK = 0xFF000000000000L; + private static final int BYTE_6_SHIFT = 48; + + private static final int BYTE_7 = 7; + private static final long BYTE_7_MASK = 0x7F00000000000000L; + private static final int BYTE_7_SHIFT = 56; + + private static final int LEFTMOST_BIT_SHIFT = 63; + private static final byte LEFTMOST_BIT = (byte) 0x80; + + private final BigInteger value; + + public static final ZipEightByteInteger ZERO = new ZipEightByteInteger(0); + + /** + * Create instance from a number. + * @param value the long to store as a ZipEightByteInteger + */ + public ZipEightByteInteger(long value) { + this(BigInteger.valueOf(value)); + } + + /** + * Create instance from a number. + * @param value the BigInteger to store as a ZipEightByteInteger + */ + public ZipEightByteInteger(BigInteger value) { + this.value = value; + } + + /** + * Create instance from bytes. + * @param bytes the bytes to store as a ZipEightByteInteger + */ + public ZipEightByteInteger(byte[] bytes) { + this(bytes, 0); + } + + /** + * Create instance from the eight bytes starting at offset. + * @param bytes the bytes to store as a ZipEightByteInteger + * @param offset the offset to start + */ + public ZipEightByteInteger(byte[] bytes, int offset) { + value = ZipEightByteInteger.getValue(bytes, offset); + } + + /** + * Get value as eight bytes in big endian byte order. + * @return value as eight bytes in big endian order + */ + public byte[] getBytes() { + return ZipEightByteInteger.getBytes(value); + } + + /** + * Get value as Java long. + * @return value as a long + */ + public long getLongValue() { + return value.longValue(); + } + + /** + * Get value as Java long. + * @return value as a long + */ + public BigInteger getValue() { + return value; + } + + /** + * Get value as eight bytes in big endian byte order. + * @param value the value to convert + * @return value as eight bytes in big endian byte order + */ + public static byte[] getBytes(long value) { + return getBytes(BigInteger.valueOf(value)); + } + + /** + * Get value as eight bytes in big endian byte order. + * @param value the value to convert + * @return value as eight bytes in big endian byte order + */ + public static byte[] getBytes(BigInteger value) { + byte[] result = new byte[8]; + long val = value.longValue(); + result[0] = (byte) ((val & BYTE_MASK)); + result[BYTE_1] = (byte) ((val & BYTE_1_MASK) >> BYTE_1_SHIFT); + result[BYTE_2] = (byte) ((val & BYTE_2_MASK) >> BYTE_2_SHIFT); + result[BYTE_3] = (byte) ((val & BYTE_3_MASK) >> BYTE_3_SHIFT); + result[BYTE_4] = (byte) ((val & BYTE_4_MASK) >> BYTE_4_SHIFT); + result[BYTE_5] = (byte) ((val & BYTE_5_MASK) >> BYTE_5_SHIFT); + result[BYTE_6] = (byte) ((val & BYTE_6_MASK) >> BYTE_6_SHIFT); + result[BYTE_7] = (byte) ((val & BYTE_7_MASK) >> BYTE_7_SHIFT); + if (value.testBit(LEFTMOST_BIT_SHIFT)) { + result[BYTE_7] |= LEFTMOST_BIT; + } + return result; + } + + /** + * Helper method to get the value as a Java long from eight bytes + * starting at given array offset + * @param bytes the array of bytes + * @param offset the offset to start + * @return the corresponding Java long value + */ + public static long getLongValue(byte[] bytes, int offset) { + return getValue(bytes, offset).longValue(); + } + + /** + * Helper method to get the value as a Java BigInteger from eight + * bytes starting at given array offset + * @param bytes the array of bytes + * @param offset the offset to start + * @return the corresponding Java BigInteger value + */ + public static BigInteger getValue(byte[] bytes, int offset) { + long value = ((long) bytes[offset + BYTE_7] << BYTE_7_SHIFT) & BYTE_7_MASK; + value += ((long) bytes[offset + BYTE_6] << BYTE_6_SHIFT) & BYTE_6_MASK; + value += ((long) bytes[offset + BYTE_5] << BYTE_5_SHIFT) & BYTE_5_MASK; + value += ((long) bytes[offset + BYTE_4] << BYTE_4_SHIFT) & BYTE_4_MASK; + value += ((long) bytes[offset + BYTE_3] << BYTE_3_SHIFT) & BYTE_3_MASK; + value += ((long) bytes[offset + BYTE_2] << BYTE_2_SHIFT) & BYTE_2_MASK; + value += ((long) bytes[offset + BYTE_1] << BYTE_1_SHIFT) & BYTE_1_MASK; + value += ((long) bytes[offset] & BYTE_MASK); + BigInteger val = BigInteger.valueOf(value); + return (bytes[offset + BYTE_7] & LEFTMOST_BIT) == LEFTMOST_BIT + ? val.setBit(LEFTMOST_BIT_SHIFT) : val; + } + + /** + * Helper method to get the value as a Java long from an eight-byte array + * @param bytes the array of bytes + * @return the corresponding Java long value + */ + public static long getLongValue(byte[] bytes) { + return getLongValue(bytes, 0); + } + + /** + * Helper method to get the value as a Java long from an eight-byte array + * @param bytes the array of bytes + * @return the corresponding Java BigInteger value + */ + public static BigInteger getValue(byte[] bytes) { + return getValue(bytes, 0); + } + + /** + * Override to make two instances with same value equal. + * @param o an object to compare + * @return true if the objects are equal + */ + @Override + public boolean equals(Object o) { + return o instanceof ZipEightByteInteger + && value.equals(((ZipEightByteInteger) o).getValue()); + } + + /** + * Override to make two instances with same value equal. + * @return the hashCode of the value stored in the ZipEightByteInteger + */ + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public String toString() { + return "ZipEightByteInteger value: " + value; + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEncoding.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEncoding.java new file mode 100644 index 0000000..c84d8d4 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEncoding.java @@ -0,0 +1,65 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * An interface for encoders that do a pretty encoding of ZIP + * filenames. + * + *

There are mostly two implementations, one that uses java.nio + * {@link java.nio.charset.Charset Charset} and one implementation, + * which copes with simple 8 bit charsets, because java-1.4 did not + * support Cp437 in java.nio.

+ * + *

The main reason for defining an own encoding layer comes from + * the problems with {@link java.lang.String#getBytes(String) + * String.getBytes}, which encodes unknown characters as ASCII + * quotation marks ('?'). Quotation marks are per definition an + * invalid filename on some operating systems like Windows, which + * leads to ignored ZIP entries.

+ * + *

All implementations should implement this interface in a + * reentrant way.

+ */ +public interface ZipEncoding { + /** + * Check, whether the given string may be losslessly encoded using this + * encoding. + * + * @param name A filename or ZIP comment. + * @return Whether the given name may be encoded with out any losses. + */ + boolean canEncode(String name); + + /** + * Encode a filename or a comment to a byte array suitable for + * storing it to a serialized zip entry. + * + *

Examples for CP 437 (in pseudo-notation, right hand side is + * C-style notation):

+ *
+     *  encode("\u20AC_for_Dollar.txt") = "%U20AC_for_Dollar.txt"
+     *  encode("\u00D6lf\u00E4sser.txt") = "\231lf\204sser.txt"
+     * 
+ * + * @param name A filename or ZIP comment. + * @return A byte buffer with a backing array containing the + * encoded name. Unmappable characters or malformed + * character sequences are mapped to a sequence of utf-16 + * words encoded in the format %Uxxxx. It is + * assumed, that the byte buffer is positioned at the + * beginning of the encoded result, the byte buffer has a + * backing array and the limit of the byte buffer points + * to the end of the encoded result. + * @throws IOException if something goes wrong + */ + ByteBuffer encode(String name) throws IOException; + + /** + * @param data The byte values to decode. + * @return The decoded string. + * @throws IOException if something goes wrong + */ + String decode(byte[] data) throws IOException; +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEncodingHelper.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEncodingHelper.java new file mode 100644 index 0000000..6b1680b --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEncodingHelper.java @@ -0,0 +1,232 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Static helper functions for robustly encoding filenames in zip files. + */ +public abstract class ZipEncodingHelper { + + /** + * A class, which holds the high characters of a simple encoding + * and lazily instantiates a Simple8BitZipEncoding instance in a + * thread-safe manner. + */ + private static class SimpleEncodingHolder { + + private final char[] highChars; + private Simple8BitZipEncoding encoding; + + /** + * Instantiate a simple encoding holder. + * + * @param highChars The characters for byte codes 128 to 255. + * + * @see Simple8BitZipEncoding#Simple8BitZipEncoding(char[]) + */ + SimpleEncodingHolder(final char[] highChars) { + this.highChars = highChars; + } + + /** + * @return The associated {@link Simple8BitZipEncoding}, which + * is instantiated if not done so far. + */ + public synchronized Simple8BitZipEncoding getEncoding() { + if (this.encoding == null) { + this.encoding = new Simple8BitZipEncoding(this.highChars); + } + return this.encoding; + } + } + + private static final Map simpleEncodings; + + static { + final Map se = new HashMap<>(); + + final char[] cp437_high_chars = + new char[] {0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, + 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, + 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, + 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, + 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, + 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, + 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, + 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, + 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, + 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, + 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, + 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, + 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, + 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, + 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, + 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, + 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, + 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, + 0x25a0, 0x00a0}; + + final SimpleEncodingHolder cp437 = new SimpleEncodingHolder(cp437_high_chars); + + se.put("CP437", cp437); + se.put("Cp437", cp437); + se.put("cp437", cp437); + se.put("IBM437", cp437); + se.put("ibm437", cp437); + + final char[] cp850_high_chars = + new char[] {0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, + 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, + 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, + 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, + 0x00ff, 0x00d6, 0x00dc, 0x00f8, 0x00a3, 0x00d8, + 0x00d7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, + 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x00ae, + 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, + 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x00c1, + 0x00c2, 0x00c0, 0x00a9, 0x2563, 0x2551, 0x2557, + 0x255d, 0x00a2, 0x00a5, 0x2510, 0x2514, 0x2534, + 0x252c, 0x251c, 0x2500, 0x253c, 0x00e3, 0x00c3, + 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, + 0x256c, 0x00a4, 0x00f0, 0x00d0, 0x00ca, 0x00cb, + 0x00c8, 0x0131, 0x00cd, 0x00ce, 0x00cf, 0x2518, + 0x250c, 0x2588, 0x2584, 0x00a6, 0x00cc, 0x2580, + 0x00d3, 0x00df, 0x00d4, 0x00d2, 0x00f5, 0x00d5, + 0x00b5, 0x00fe, 0x00de, 0x00da, 0x00db, 0x00d9, + 0x00fd, 0x00dd, 0x00af, 0x00b4, 0x00ad, 0x00b1, + 0x2017, 0x00be, 0x00b6, 0x00a7, 0x00f7, 0x00b8, + 0x00b0, 0x00a8, 0x00b7, 0x00b9, 0x00b3, 0x00b2, + 0x25a0, 0x00a0}; + + final SimpleEncodingHolder cp850 = new SimpleEncodingHolder(cp850_high_chars); + + se.put("CP850", cp850); + se.put("Cp850", cp850); + se.put("cp850", cp850); + se.put("IBM850", cp850); + se.put("ibm850", cp850); + simpleEncodings = Collections.unmodifiableMap(se); + } + + /** + * Grow a byte buffer, so it has a minimal capacity or at least + * the double capacity of the original buffer + * + * @param b The original buffer. + * @param newCapacity The minimal requested new capacity. + * @return A byte buffer r with + * r.capacity() = max(b.capacity() * 2,newCapacity) and + * all the data contained in b copied to the beginning + * of r. + * + */ + static ByteBuffer growBuffer(final ByteBuffer b, final int newCapacity) { + b.limit(b.position()); + b.rewind(); + + final int c2 = b.capacity() * 2; + final ByteBuffer on = ByteBuffer.allocate(c2 < newCapacity ? newCapacity : c2); + + on.put(b); + return on; + } + + + /** + * The hexadecimal digits 0,...,9,A,...,F encoded as + * ASCII bytes. + */ + private static final byte[] HEX_DIGITS = new byte[] { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46 + }; + + /** + * Append %Uxxxx to the given byte buffer. + * The caller must assure, that bb.remaining()>=6. + * + * @param bb The byte buffer to write to. + * @param c The character to write. + */ + static void appendSurrogate(final ByteBuffer bb, final char c) { + + bb.put((byte) '%'); + bb.put((byte) 'U'); + + bb.put(HEX_DIGITS[(c >> 12) & 0x0f]); + bb.put(HEX_DIGITS[(c >> 8) & 0x0f]); + bb.put(HEX_DIGITS[(c >> 4) & 0x0f]); + bb.put(HEX_DIGITS[c & 0x0f]); + } + + + /** + * name of the encoding UTF-8 + */ + static final String UTF8 = "UTF8"; + + /** + * variant name of the encoding UTF-8 used for comparisons. + */ + private static final String UTF_DASH_8 = "utf-8"; + + /** + * name of the encoding UTF-8 + */ + static final ZipEncoding UTF8_ZIP_ENCODING = new FallbackZipEncoding(UTF8); + + /** + * Instantiates a zip encoding. + * + * @param name The name of the zip encoding. Specify {@code null} for + * the platform's default encoding. + * @return A zip encoding for the given encoding name. + */ + public static ZipEncoding getZipEncoding(final String name) { + + // fallback encoding is good enough for utf-8. + if (isUTF8(name)) { + return UTF8_ZIP_ENCODING; + } + + if (name == null) { + return new FallbackZipEncoding(); + } + + final SimpleEncodingHolder h = simpleEncodings.get(name); + + if (h != null) { + return h.getEncoding(); + } + + try { + + final Charset cs = Charset.forName(name); + return new NioZipEncoding(cs); + + } catch (final UnsupportedCharsetException e) { + return new FallbackZipEncoding(name); + } + } + + /** + * Whether a given encoding - or the platform's default encoding + * if the parameter is null - is UTF-8. + */ + static boolean isUTF8(String encoding) { + if (encoding == null) { + // check platform's default encoding + encoding = System.getProperty("file.encoding"); + } + return UTF8.equalsIgnoreCase(encoding) + || UTF_DASH_8.equalsIgnoreCase(encoding); + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEntry.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEntry.java new file mode 100644 index 0000000..a7f985b --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipEntry.java @@ -0,0 +1,760 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.zip.ZipException; + +/** + * Extension that adds better handling of extra fields and provides + * access to the internal and external file attributes. + * + *

The extra data is expected to follow the recommendation of + * APPNOTE.txt:

+ *
    + *
  • the extra byte array consists of a sequence of extra fields
  • + *
  • each extra fields starts by a two byte header id followed by + * a two byte sequence holding the length of the remainder of + * data.
  • + *
+ * + *

Any extra data that cannot be parsed by the rules above will be + * consumed as "unparseable" extra data and treated differently by the + * methods of this class. Older versions would have thrown an + * exception if any attempt was made to read or write extra data not + * conforming to the recommendation.

+ * + */ +public class ZipEntry extends java.util.zip.ZipEntry implements Cloneable { + + public static final int PLATFORM_UNIX = 3; + public static final int PLATFORM_FAT = 0; + public static final int CRC_UNKNOWN = -1; + private static final int SHORT_MASK = 0xFFFF; + private static final int SHORT_SHIFT = 16; + private static final byte[] EMPTY = new byte[0]; + + /** + * The {@link java.util.zip.ZipEntry} base class only supports + * the compression methods STORED and DEFLATED. We override the + * field so that any compression methods can be used. + *

The default value -1 means that the method has not been specified.

+ */ + private int method = -1; + + /** + * The {@link java.util.zip.ZipEntry#setSize} method in the base + * class throws an IllegalArgumentException if the size is bigger + * than 2GB for Java versions < 7. Need to keep our own size + * information for Zip64 support. + */ + private long size = -1; + + private int internalAttributes = 0; + private int platform = PLATFORM_FAT; + private long externalAttributes = 0; + private ZipExtraField[] extraFields; + private UnparseableExtraFieldData unparseableExtra = null; + private String name = null; + private byte[] rawName = null; + private GeneralPurposeBit gpb = new GeneralPurposeBit(); + private static final ZipExtraField[] noExtraFields = new ZipExtraField[0]; + + /** + * Creates a new zip entry with the specified name. + * + *

Assumes the entry represents a directory if and only if the + * name ends with a forward slash "/".

+ * + * @param name the name of the entry + */ + public ZipEntry(final String name) { + super(name); + setName(name); + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + *

Assumes the entry represents a directory if and only if the + * name ends with a forward slash "/".

+ * + * @param entry the entry to get fields from + * @throws ZipException on error + */ + public ZipEntry(final java.util.zip.ZipEntry entry) throws ZipException { + super(entry); + setName(entry.getName()); + final byte[] extra = entry.getExtra(); + if (extra != null) { + setExtraFields(ExtraFieldUtils.parse(extra, true, + ExtraFieldUtils.UnparseableExtraField.READ)); + } else { + // initializes extra data to an empty byte array + setExtra(); + } + setMethod(entry.getMethod()); + this.size = entry.getSize(); + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + *

Assumes the entry represents a directory if and only if the + * name ends with a forward slash "/".

+ * + * @param entry the entry to get fields from + * @throws ZipException on error + */ + public ZipEntry(final ZipEntry entry) throws ZipException { + this((java.util.zip.ZipEntry) entry); + setInternalAttributes(entry.getInternalAttributes()); + setExternalAttributes(entry.getExternalAttributes()); + setExtraFields(getAllExtraFieldsNoCopy()); + setPlatform(entry.getPlatform()); + GeneralPurposeBit other = entry.getGeneralPurposeBit(); + setGeneralPurposeBit(other == null ? null : (GeneralPurposeBit) other.clone()); + } + + protected ZipEntry() { + this(""); + } + + /** + * Creates a new zip entry taking some information from the given + * file and using the provided name. + * + *

The name will be adjusted to end with a forward slash "/" if + * the file is a directory. If the file is not a directory a + * potential trailing forward slash will be stripped from the + * entry name.

+ * + * @param inputFile File + * @param entryName String + */ + public ZipEntry(final File inputFile, final String entryName) { + this(inputFile.isDirectory() && !entryName.endsWith("/") ? entryName + "/" : entryName); + if (inputFile.isFile()) { + setSize(inputFile.length()); + } + setTime(inputFile.lastModified()); + // TODO are there any other fields we can set here? + } + + /** + * Overwrite clone. + * + * @return a cloned copy of this ZipEntry + */ + @Override + public Object clone() { + final ZipEntry e = (ZipEntry) super.clone(); + + e.setInternalAttributes(getInternalAttributes()); + e.setExternalAttributes(getExternalAttributes()); + e.setExtraFields(getAllExtraFieldsNoCopy()); + return e; + } + + /** + * Returns the compression method of this entry, or -1 if the + * compression method has not been specified. + * + * @return compression method + */ + @Override + public int getMethod() { + return method; + } + + /** + * Sets the compression method of this entry. + * + * @param method compression method + */ + @Override + public void setMethod(final int method) { + if (method < 0) { + throw new IllegalArgumentException("ZIP compression method can not be negative: " + + method); + } + this.method = method; + } + + /** + * Retrieves the internal file attributes. + * + * @return the internal file attributes + */ + public int getInternalAttributes() { + return internalAttributes; + } + + /** + * Sets the internal file attributes. + * + * @param value an int value + */ + public void setInternalAttributes(final int value) { + internalAttributes = value; + } + + /** + * Retrieves the external file attributes. + * + * @return the external file attributes + */ + public long getExternalAttributes() { + return externalAttributes; + } + + /** + * Sets the external file attributes. + * + * @param value an long value + */ + public void setExternalAttributes(final long value) { + externalAttributes = value; + } + + /** + * Sets Unix permissions in a way that is understood by Info-Zip's + * unzip command. + * + * @param mode an int value + */ + public void setUnixMode(final int mode) { + // CheckStyle:MagicNumberCheck OFF - no point + setExternalAttributes((mode << SHORT_SHIFT) + // MS-DOS read-only attribute + | ((mode & 0200) == 0 ? 1 : 0) + // MS-DOS directory flag + | (isDirectory() ? 0x10 : 0)); + // CheckStyle:MagicNumberCheck ON + platform = PLATFORM_UNIX; + } + + /** + * Unix permission. + * + * @return the unix permissions + */ + public int getUnixMode() { + return platform != PLATFORM_UNIX ? 0 + : (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK); + } + + /** + * Platform specification to put into the "version made + * by" part of the central file header. + * + * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} + * has been called, in which case PLATFORM_UNIX will be returned. + */ + public int getPlatform() { + return platform; + } + + /** + * Set the platform (UNIX or FAT). + * + * @param platform an int value - 0 is FAT, 3 is UNIX + */ + protected void setPlatform(final int platform) { + this.platform = platform; + } + + /** + * Replaces all currently attached extra fields with the new array. + * + * @param fields an array of extra fields + */ + public void setExtraFields(final ZipExtraField[] fields) { + List newFields = new ArrayList<>(); + for (ZipExtraField field : fields) { + if (field instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) field; + } else { + newFields.add(field); + } + } + extraFields = newFields.toArray(new ZipExtraField[newFields.size()]); + setExtra(); + } + + /** + * Retrieves all extra fields that have been parsed successfully. + * + * @return an array of the extra fields + */ + public ZipExtraField[] getExtraFields() { + return getParseableExtraFields(); + } + + /** + * Retrieves extra fields. + * + * @param includeUnparseable whether to also return unparseable + * extra fields as {@link UnparseableExtraFieldData} if such data + * exists. + * @return an array of the extra fields + */ + public ZipExtraField[] getExtraFields(final boolean includeUnparseable) { + return includeUnparseable ? getAllExtraFields() : getParseableExtraFields(); + } + + private ZipExtraField[] getParseableExtraFieldsNoCopy() { + if (extraFields == null) { + return noExtraFields; + } + return extraFields; + } + + private ZipExtraField[] getParseableExtraFields() { + final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy(); + return (parseableExtraFields == extraFields) ? copyOf(parseableExtraFields) + : parseableExtraFields; + } + + private ZipExtraField[] copyOf(ZipExtraField[] src) { + return copyOf(src, src.length); + } + + private ZipExtraField[] copyOf(ZipExtraField[] src, int length) { + ZipExtraField[] cpy = new ZipExtraField[length]; + System.arraycopy(src, 0, cpy, 0, Math.min(src.length, length)); + return cpy; + } + + private ZipExtraField[] getMergedFields() { + final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); + zipExtraFields[extraFields.length] = unparseableExtra; + return zipExtraFields; + } + + private ZipExtraField[] getUnparseableOnly() { + return unparseableExtra == null ? noExtraFields : new ZipExtraField[] {unparseableExtra}; + } + + private ZipExtraField[] getAllExtraFields() { + final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy(); + return (allExtraFieldsNoCopy == extraFields) ? copyOf(allExtraFieldsNoCopy) + : allExtraFieldsNoCopy; + } + + /** + * Get all extra fields, including unparseable ones. + * + * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method + */ + private ZipExtraField[] getAllExtraFieldsNoCopy() { + if (extraFields == null) { + return getUnparseableOnly(); + } + return unparseableExtra != null ? getMergedFields() : extraFields; + } + + /** + * Adds an extra field - replacing an already present extra field + * of the same type. + * + *

If no extra field of the same type exists, the field will be + * added as last field.

+ * + * @param ze an extra field + */ + public void addExtraField(final ZipExtraField ze) { + if (ze instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) ze; + } else { + if (extraFields == null) { + extraFields = new ZipExtraField[] {ze}; + } else { + if (getExtraField(ze.getHeaderId()) != null) { + removeExtraField(ze.getHeaderId()); + } + final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); + zipExtraFields[extraFields.length] = ze; + extraFields = zipExtraFields; + } + } + setExtra(); + } + + /** + * Adds an extra field - replacing an already present extra field + * of the same type. + * + *

The new extra field will be the first one.

+ * + * @param ze an extra field + */ + public void addAsFirstExtraField(final ZipExtraField ze) { + if (ze instanceof UnparseableExtraFieldData) { + unparseableExtra = (UnparseableExtraFieldData) ze; + } else { + if (getExtraField(ze.getHeaderId()) != null) { + removeExtraField(ze.getHeaderId()); + } + ZipExtraField[] copy = extraFields; + int newLen = extraFields != null ? extraFields.length + 1 : 1; + extraFields = new ZipExtraField[newLen]; + extraFields[0] = ze; + if (copy != null) { + System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1); + } + } + setExtra(); + } + + /** + * Remove an extra field. + * + * @param type the type of extra field to remove + */ + public void removeExtraField(final ZipShort type) { + if (extraFields == null) { + throw new NoSuchElementException(); + } + List newResult = new ArrayList<>(); + for (ZipExtraField extraField : extraFields) { + if (!type.equals(extraField.getHeaderId())) { + newResult.add(extraField); + } + } + if (extraFields.length == newResult.size()) { + throw new NoSuchElementException(); + } + extraFields = newResult.toArray(new ZipExtraField[newResult.size()]); + setExtra(); + } + + /** + * Removes unparseable extra field data. + */ + public void removeUnparseableExtraFieldData() { + if (unparseableExtra == null) { + throw new NoSuchElementException(); + } + unparseableExtra = null; + setExtra(); + } + + /** + * Looks up an extra field by its header id. + * + * @param type ZipShort + * @return null if no such field exists. + */ + public ZipExtraField getExtraField(final ZipShort type) { + if (extraFields != null) { + for (ZipExtraField extraField : extraFields) { + if (type.equals(extraField.getHeaderId())) { + return extraField; + } + } + } + return null; + } + + /** + * Looks up extra field data that couldn't be parsed correctly. + * + * @return null if no such field exists. + */ + public UnparseableExtraFieldData getUnparseableExtraFieldData() { + return unparseableExtra; + } + + /** + * Parses the given bytes as extra field data and consumes any + * unparseable data as an {@link UnparseableExtraFieldData} + * instance. + * + * @param extra an array of bytes to be parsed into extra fields + * @throws RuntimeException if the bytes cannot be parsed + * @throws RuntimeException on error + */ + @Override + public void setExtra(final byte[] extra) throws RuntimeException { + try { + final ZipExtraField[] local = ExtraFieldUtils.parse(extra, true, + ExtraFieldUtils.UnparseableExtraField.READ); + mergeExtraFields(local, true); + } catch (final ZipException e) { + // actually this is not be possible + throw new RuntimeException("Error parsing extra fields for entry: " //NOSONAR + + getName() + " - " + e.getMessage(), e); + } + } + + /** + * Unfortunately {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} seems to access the extra data + * directly, so overriding getExtra doesn't help - we need to + * modify super's data directly. + */ + protected void setExtra() { + super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getExtraFields(true))); + } + + /** + * Sets the central directory part of extra fields. + * + * @param b boolean + */ + public void setCentralDirectoryExtra(final byte[] b) { + try { + final ZipExtraField[] central = ExtraFieldUtils.parse(b, false, + ExtraFieldUtils.UnparseableExtraField.READ); + mergeExtraFields(central, false); + } catch (final ZipException e) { + throw new RuntimeException(e.getMessage(), e); //NOSONAR + } + } + + /** + * Retrieves the extra data for the local file data. + * + * @return the extra data for local file + */ + public byte[] getLocalFileDataExtra() { + final byte[] extra = getExtra(); + return extra != null ? extra : EMPTY; + } + + /** + * Retrieves the extra data for the central directory. + * + * @return the central directory extra data + */ + public byte[] getCentralDirectoryExtra() { + return ExtraFieldUtils.mergeCentralDirectoryData(getExtraFields(true)); + } + + /** + * Make this class work in JDK 1.1 like a 1.2 class. + * + *

This either stores the size for later usage or invokes + * setCompressedSize via reflection.

+ * + * @param size the size to use + * @deprecated since 1.7. + * Use setCompressedSize directly. + */ + @Deprecated + public void setComprSize(final long size) { + setCompressedSize(size); + } + + /** + * Get the name of the entry. + * + * @return the entry name + */ + @Override + public String getName() { + return name == null ? super.getName() : name; + } + + /** + * Is this entry a directory? + * + * @return true if the entry is a directory + */ + @Override + public boolean isDirectory() { + return getName().endsWith("/"); + } + + /** + * Set the name of the entry. + * + * @param name the name to use + */ + protected void setName(String name) { + if (name != null && getPlatform() == PLATFORM_FAT && !name.contains("/")) { + name = name.replace('\\', '/'); + } + this.name = name; + } + + /** + * Gets the uncompressed size of the entry data. + * + * @return the entry size + */ + @Override + public long getSize() { + return size; + } + + /** + * Sets the uncompressed size of the entry data. + * + * @param size the uncompressed size in bytes + * @exception IllegalArgumentException if the specified size is less + * than 0 + */ + @Override + public void setSize(final long size) { + if (size < 0) { + throw new IllegalArgumentException("invalid entry size"); + } + this.size = size; + } + + /** + * Sets the name using the raw bytes and the string created from + * it by guessing or using the configured encoding. + * + * @param name the name to use created from the raw bytes using + * the guessed or configured encoding + * @param rawName the bytes originally read as name from the + * archive + */ + protected void setName(final String name, final byte[] rawName) { + setName(name); + this.rawName = rawName; + } + + /** + * Returns the raw bytes that made up the name before it has been + * converted using the configured or guessed encoding. + * + *

This method will return null if this instance has not been + * read from an archive.

+ * + * @return byte[] + */ + public byte[] getRawName() { + if (rawName != null) { + final byte[] b = new byte[rawName.length]; + System.arraycopy(rawName, 0, b, 0, rawName.length); + return b; + } + return null; + } + + /** + * Get the hashCode of the entry. + * This uses the name as the hashcode. + * + * @return a hashcode. + */ + @Override + public int hashCode() { + // this method has severe consequences on performance. We cannot rely + // on the super.hashCode() method since super.getName() always return + // the empty string in the current implementation (there's no setter) + // so it is basically draining the performance of a hashmap lookup + return getName().hashCode(); + } + + /** + * The "general purpose bit" field. + * + * @return GeneralPurposeBit + */ + public GeneralPurposeBit getGeneralPurposeBit() { + return gpb; + } + + /** + * The "general purpose bit" field. + * + * @param b GeneralPurposeBit + */ + public void setGeneralPurposeBit(final GeneralPurposeBit b) { + gpb = b; + } + + /** + * If there are no extra fields, use the given fields as new extra + * data - otherwise merge the fields assuming the existing fields + * and the new fields stem from different locations inside the + * archive. + * + * @param f the extra fields to merge + * @param local whether the new fields originate from local data + */ + private void mergeExtraFields(final ZipExtraField[] f, final boolean local) + throws ZipException { + if (extraFields == null) { + setExtraFields(f); + } else { + for (final ZipExtraField element : f) { + ZipExtraField existing; + if (element instanceof UnparseableExtraFieldData) { + existing = unparseableExtra; + } else { + existing = getExtraField(element.getHeaderId()); + } + if (existing == null) { + addExtraField(element); + } else { + if (local + || !(existing instanceof CentralDirectoryParsingZipExtraField)) { + final byte[] b = element.getLocalFileDataData(); + existing.parseFromLocalFileData(b, 0, b.length); + } else { + final byte[] b = element.getCentralDirectoryData(); + ((CentralDirectoryParsingZipExtraField) existing) + .parseFromCentralDirectoryData(b, 0, b.length); + } + } + } + setExtra(); + } + } + + public Date getLastModifiedDate() { + return new Date(getTime()); + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final ZipEntry other = (ZipEntry) obj; + final String myName = getName(); + final String otherName = other.getName(); + if (myName == null) { + if (otherName != null) { + return false; + } + } else if (!myName.equals(otherName)) { + return false; + } + String myComment = getComment(); + String otherComment = other.getComment(); + if (myComment == null) { + myComment = ""; + } + if (otherComment == null) { + otherComment = ""; + } + return getTime() == other.getTime() + && myComment.equals(otherComment) + && getInternalAttributes() == other.getInternalAttributes() + && getPlatform() == other.getPlatform() + && getExternalAttributes() == other.getExternalAttributes() + && getMethod() == other.getMethod() + && getSize() == other.getSize() + && getCrc() == other.getCrc() + && getCompressedSize() == other.getCompressedSize() + && Arrays.equals(getCentralDirectoryExtra(), other.getCentralDirectoryExtra()) + && Arrays.equals(getLocalFileDataExtra(), other.getLocalFileDataExtra()) + && gpb.equals(other.gpb); + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipExtraField.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipExtraField.java new file mode 100644 index 0000000..85c9239 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipExtraField.java @@ -0,0 +1,60 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.util.zip.ZipException; + +/** + * General format of extra field data. + * + *

Extra fields usually appear twice per file, once in the local + * file data and once in the central directory. Usually they are the + * same, but they don't have to be. {@link + * java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} will + * only use the local file data in both places.

+ * + */ +public interface ZipExtraField { + + /** + * The Header-ID. + * @return the header id + */ + ZipShort getHeaderId(); + + /** + * Length of the extra field in the local file data - without + * Header-ID or length specifier. + * @return the length of the field in the local file data + */ + ZipShort getLocalFileDataLength(); + + /** + * Length of the extra field in the central directory - without + * Header-ID or length specifier. + * @return the length of the field in the central directory + */ + ZipShort getCentralDirectoryLength(); + + /** + * The actual data to put into local file data - without Header-ID + * or length specifier. + * @return the data + */ + byte[] getLocalFileDataData(); + + /** + * The actual data to put into central directory - without Header-ID or + * length specifier. + * @return the data + */ + byte[] getCentralDirectoryData(); + + /** + * Populate data from this array as if it was in local file data. + * @param data an array of bytes + * @param offset the start offset + * @param length the number of bytes in the array from offset + * @throws ZipException on error + */ + void parseFromLocalFileData(byte[] data, int offset, int length) + throws ZipException; +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipFile.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipFile.java new file mode 100644 index 0000000..de8e4b7 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipFile.java @@ -0,0 +1,1003 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; +import java.util.zip.ZipException; + +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.DWORD; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.SHORT; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.WORD; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.ZIP64_MAGIC; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.ZIP64_MAGIC_SHORT; + +/** + * Replacement for java.util.ZipFile. + * + *

This class adds support for file name encodings other than UTF-8 + * (which is required to work on ZIP files created by native zip tools + * and is able to skip a preamble like the one found in self + * extracting archives. Furthermore it returns instances of + * ZipEntry instead of + * java.util.zip.ZipEntry.

+ * + *

It doesn't extend java.util.zip.ZipFile as it would + * have to reimplement all methods anyway. Like + * java.util.ZipFile, it uses RandomAccessFile under the + * covers and supports compressed and uncompressed entries. + * Also transparently supported are Zip64 + * extensions and thus individual entries and archives larger than 4 + * GB or with more than 65536 entries.

+ * + *

The method signatures mimic the ones of + * java.util.zip.ZipFile, with a couple of exceptions: + * + *

    + *
  • There is no getName method.
  • + *
  • entries has been renamed to getEntries.
  • + *
  • getEntries and getEntry return + * ZipEntry instances.
  • + *
  • close is allowed to throw IOException.
  • + *
+ * + */ +public class ZipFile implements Closeable { + private static final int HASH_SIZE = 509; + static final int NIBLET_MASK = 0x0f; + static final int BYTE_SHIFT = 8; + private static final int POS_0 = 0; + private static final int POS_1 = 1; + private static final int POS_2 = 2; + private static final int POS_3 = 3; + + /** + * List of entries in the order they appear inside the central + * directory. + */ + private final List entries = new LinkedList<>(); + + /** + * Maps String to list of ZipEntrys, name -> actual entries. + */ + private final Map> nameMap = + new HashMap<>(HASH_SIZE); + + private static final class OffsetEntry { + private long headerOffset = -1; + private long dataOffset = -1; + } + + /** + * The encoding to use for filenames and the file comment. + * + *

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. + * Defaults to the platform's default character encoding.

+ */ + private final String encoding; + + /** + * The zip encoding to use for filenames and the file comment. + */ + private final ZipEncoding zipEncoding; + + /** + * File name of actual source. + */ + private final String archiveName; + + /** + * The actual data source. + */ + private final RandomAccessFile archive; + + /** + * Whether to look for and use Unicode extra fields. + */ + private final boolean useUnicodeExtraFields; + + /** + * Whether the file is closed. + */ + private volatile boolean closed; + + // cached buffers + private final byte[] DWORD_BUF = new byte[DWORD]; + private final byte[] WORD_BUF = new byte[WORD]; + private final byte[] CFH_BUF = new byte[CFH_LEN]; + private final byte[] SHORT_BUF = new byte[SHORT]; + + /** + * Opens the given file for reading, assuming the platform's + * native encoding for file names. + * + * @param f the archive. + * + * @throws IOException if an error occurs while reading the file. + */ + public ZipFile(final File f) throws IOException { + this(f, null); + } + + /** + * Opens the given file for reading, assuming the platform's + * native encoding for file names. + * + * @param name name of the archive. + * + * @throws IOException if an error occurs while reading the file. + */ + public ZipFile(final String name) throws IOException { + this(new File(name), null); + } + + /** + * Opens the given file for reading, assuming the specified + * encoding for file names, scanning unicode extra fields. + * + * @param name name of the archive. + * @param encoding the encoding to use for file names, use null + * for the platform's default encoding + * + * @throws IOException if an error occurs while reading the file. + */ + public ZipFile(final String name, final String encoding) throws IOException { + this(new File(name), encoding, true); + } + + /** + * Opens the given file for reading, assuming the specified + * encoding for file names and scanning for unicode extra fields. + * + * @param f the archive. + * @param encoding the encoding to use for file names, use null + * for the platform's default encoding + * + * @throws IOException if an error occurs while reading the file. + */ + public ZipFile(final File f, final String encoding) throws IOException { + this(f, encoding, true); + } + + /** + * Opens the given file for reading, assuming the specified + * encoding for file names. + * + * @param f the archive. + * @param encoding the encoding to use for file names, use null + * for the platform's default encoding + * @param useUnicodeExtraFields whether to use InfoZIP Unicode + * Extra Fields (if present) to set the file names. + * + * @throws IOException if an error occurs while reading the file. + */ + public ZipFile(final File f, final String encoding, final boolean useUnicodeExtraFields) + throws IOException { + this.archiveName = f.getAbsolutePath(); + this.encoding = encoding; + this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); + this.useUnicodeExtraFields = useUnicodeExtraFields; + archive = new RandomAccessFile(f, "r"); + boolean success = false; + try { + final Map entriesWithoutUTF8Flag = + populateFromCentralDirectory(); + resolveLocalFileHeaderData(entriesWithoutUTF8Flag); + success = true; + } finally { + closed = !success; + if (!success) { + try { + archive.close(); + } catch (final IOException e2) { + // swallow, throw the original exception instead + } + } + } + } + + /** + * The encoding to use for filenames and the file comment. + * + * @return null if using the platform's default character encoding. + */ + public String getEncoding() { + return encoding; + } + + /** + * Closes the archive. + * @throws IOException if an error occurs closing the archive. + */ + @Override + public void close() throws IOException { + // this flag is only written here and read in finalize() which + // can never be run in parallel. + // no synchronization needed. + closed = true; + + archive.close(); + } + + /** + * close a zipfile quietly; throw no io fault, do nothing + * on a null parameter + * @param zipfile file to close, can be null + */ + public static void closeQuietly(final ZipFile zipfile) { + if (zipfile != null) { + try { + zipfile.close(); + } catch (final IOException e) { + //ignore + } + } + } + + /** + * Returns all entries. + * + *

Entries will be returned in the same order they appear + * within the archive's central directory.

+ * + * @return all entries as {@link ZipEntry} instances + */ + public Enumeration getEntries() { + return Collections.enumeration(entries); + } + + /** + * Returns all entries in physical order. + * + *

Entries will be returned in the same order their contents + * appear within the archive.

+ * + * @return all entries as {@link ZipEntry} instances + */ + public Enumeration getEntriesInPhysicalOrder() { + return entries.stream().sorted(OFFSET_COMPARATOR).collect(Collectors + .collectingAndThen(Collectors.toList(), Collections::enumeration)); + } + + /** + * Returns a named entry - or {@code null} if no entry by + * that name exists. + * + *

If multiple entries with the same name exist the first entry + * in the archive's central directory by that name is + * returned.

+ * + * @param name name of the entry. + * @return the ZipEntry corresponding to the given name - or + * {@code null} if not present. + */ + public ZipEntry getEntry(final String name) { + final LinkedList entriesOfThatName = nameMap.get(name); + return entriesOfThatName != null ? entriesOfThatName.getFirst() : null; + } + + /** + * Returns all named entries in the same order they appear within + * the archive's central directory. + * + * @param name name of the entry. + * @return the Iterable<ZipEntry> corresponding to the + * given name + */ + public Iterable getEntries(final String name) { + final List entriesOfThatName = nameMap.get(name); + return entriesOfThatName != null ? entriesOfThatName + : Collections.emptyList(); + } + + /** + * Returns all named entries in the same order their contents + * appear within the archive. + * + * @param name name of the entry. + * @return the Iterable<ZipEntry> corresponding to the + * given name + */ + public Iterable getEntriesInPhysicalOrder(final String name) { + if (nameMap.containsKey(name)) { + return nameMap.get(name).stream().sorted(OFFSET_COMPARATOR) + .collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + /** + * Whether this class is able to read the given entry. + * + *

May return false if it is set up to use encryption or a + * compression method that hasn't been implemented yet.

+ * + * @param ze ZipEntry + * @return boolean + */ + public boolean canReadEntryData(final ZipEntry ze) { + return ZipUtil.canHandleEntryData(ze); + } + + /** + * Returns an InputStream for reading the contents of the given entry. + * + * @param ze the entry to get the stream for. + * @return a stream to read the entry from. + * @throws IOException if unable to create an input stream from the zipentry + * @throws ZipException if the zipentry uses an unsupported feature + */ + public InputStream getInputStream(final ZipEntry ze) + throws IOException, ZipException { + if (!(ze instanceof Entry)) { + return null; + } + // cast validity is checked just above + final OffsetEntry offsetEntry = ((Entry) ze).getOffsetEntry(); + ZipUtil.checkRequestedFeatures(ze); + final long start = offsetEntry.dataOffset; + // doesn't get closed if the method is not supported, but + // doesn't hold any resources either + final BoundedInputStream bis = + new BoundedInputStream(start, ze.getCompressedSize()); //NOSONAR + switch (ze.getMethod()) { + case ZipEntry.STORED: + return bis; + case ZipEntry.DEFLATED: + bis.addDummy(); + final Inflater inflater = new Inflater(true); + return new InflaterInputStream(bis, inflater) { + @Override + public void close() throws IOException { + super.close(); + inflater.end(); + } + }; + default: + throw new ZipException("Found unsupported compression method " + + ze.getMethod()); + } + } + + /** + * Length of a "central directory" entry structure without file + * name, extra fields or comment. + */ + private static final int CFH_LEN = + /* version made by */ SHORT + /* version needed to extract */ + SHORT + /* general purpose bit flag */ + SHORT + /* compression method */ + SHORT + /* last mod file time */ + SHORT + /* last mod file date */ + SHORT + /* crc-32 */ + WORD + /* compressed size */ + WORD + /* uncompressed size */ + WORD + /* filename length */ + SHORT + /* extra field length */ + SHORT + /* file comment length */ + SHORT + /* disk number start */ + SHORT + /* internal file attributes */ + SHORT + /* external file attributes */ + WORD + /* relative offset of local header */ + WORD; + + private static final long CFH_SIG = + ZipLong.getValue(ZipOutputStream.CFH_SIG); + + /** + * Reads the central directory of the given archive and populates + * the internal tables with ZipEntry instances. + * + *

The ZipEntrys will know all data that can be obtained from + * the central directory alone, but not the data that requires the + * local file header or additional data to be read.

+ * + * @return a map of zipentries that didn't have the language + * encoding flag set when read. + */ + private Map populateFromCentralDirectory() + throws IOException { + final Map noUTF8Flag = new HashMap<>(); + + positionAtCentralDirectory(); + + archive.readFully(WORD_BUF); + long sig = ZipLong.getValue(WORD_BUF); + + if (sig != CFH_SIG && startsWithLocalFileHeader()) { + throw new IOException( + "central directory is empty, can't expand corrupt archive."); + } + + while (sig == CFH_SIG) { + readCentralDirectoryEntry(noUTF8Flag); + archive.readFully(WORD_BUF); + sig = ZipLong.getValue(WORD_BUF); + } + return noUTF8Flag; + } + + /** + * Reads an individual entry of the central directory, creates an + * ZipEntry from it and adds it to the global maps. + * + * @param noUTF8Flag map used to collect entries that don't have + * their UTF-8 flag set and whose name will be set by data read + * from the local file header later. The current entry may be + * added to this map. + */ + private void + readCentralDirectoryEntry(final Map noUTF8Flag) + throws IOException { + archive.readFully(CFH_BUF); + int off = 0; + final OffsetEntry offset = new OffsetEntry(); + final Entry ze = new Entry(offset); + + final int versionMadeBy = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + ze.setPlatform((versionMadeBy >> BYTE_SHIFT) & NIBLET_MASK); + + off += SHORT; // skip version info + + final GeneralPurposeBit gpFlag = GeneralPurposeBit.parse(CFH_BUF, off); + final boolean hasUTF8Flag = gpFlag.usesUTF8ForNames(); + final ZipEncoding entryEncoding = + hasUTF8Flag ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; + ze.setGeneralPurposeBit(gpFlag); + + off += SHORT; + + ze.setMethod(ZipShort.getValue(CFH_BUF, off)); + off += SHORT; + + final long time = ZipUtil.dosToJavaTime(ZipLong.getValue(CFH_BUF, off)); + ze.setTime(time); + off += WORD; + + ze.setCrc(ZipLong.getValue(CFH_BUF, off)); + off += WORD; + + ze.setCompressedSize(ZipLong.getValue(CFH_BUF, off)); + off += WORD; + + ze.setSize(ZipLong.getValue(CFH_BUF, off)); + off += WORD; + + final int fileNameLen = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + + final int extraLen = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + + final int commentLen = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + + final int diskStart = ZipShort.getValue(CFH_BUF, off); + off += SHORT; + + ze.setInternalAttributes(ZipShort.getValue(CFH_BUF, off)); + off += SHORT; + + ze.setExternalAttributes(ZipLong.getValue(CFH_BUF, off)); + off += WORD; + + final byte[] fileName = new byte[fileNameLen]; + archive.readFully(fileName); + ze.setName(entryEncoding.decode(fileName), fileName); + + // LFH offset, + offset.headerOffset = ZipLong.getValue(CFH_BUF, off); + // data offset will be filled later + entries.add(ze); + + final byte[] cdExtraData = new byte[extraLen]; + archive.readFully(cdExtraData); + ze.setCentralDirectoryExtra(cdExtraData); + + setSizesAndOffsetFromZip64Extra(ze, offset, diskStart); + + final byte[] comment = new byte[commentLen]; + archive.readFully(comment); + ze.setComment(entryEncoding.decode(comment)); + + if (!hasUTF8Flag && useUnicodeExtraFields) { + noUTF8Flag.put(ze, new NameAndComment(fileName, comment)); + } + } + + /** + * If the entry holds a Zip64 extended information extra field, + * read sizes from there if the entry's sizes are set to + * 0xFFFFFFFFF, do the same for the offset of the local file + * header. + * + *

Ensures the Zip64 extra either knows both compressed and + * uncompressed size or neither of both as the internal logic in + * ExtraFieldUtils forces the field to create local header data + * even if they are never used - and here a field with only one + * size would be invalid.

+ */ + private void setSizesAndOffsetFromZip64Extra(final ZipEntry ze, + final OffsetEntry offset, + final int diskStart) + throws IOException { + final Zip64ExtendedInformationExtraField z64 = + (Zip64ExtendedInformationExtraField) + ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID); + if (z64 != null) { + final boolean hasUncompressedSize = ze.getSize() == ZIP64_MAGIC; + final boolean hasCompressedSize = ze.getCompressedSize() == ZIP64_MAGIC; + final boolean hasRelativeHeaderOffset = + offset.headerOffset == ZIP64_MAGIC; + z64.reparseCentralDirectoryData(hasUncompressedSize, + hasCompressedSize, + hasRelativeHeaderOffset, + diskStart == ZIP64_MAGIC_SHORT); + + if (hasUncompressedSize) { + ze.setSize(z64.getSize().getLongValue()); + } else if (hasCompressedSize) { + z64.setSize(new ZipEightByteInteger(ze.getSize())); + } + + if (hasCompressedSize) { + ze.setCompressedSize(z64.getCompressedSize().getLongValue()); + } else if (hasUncompressedSize) { + z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize())); + } + + if (hasRelativeHeaderOffset) { + offset.headerOffset = + z64.getRelativeHeaderOffset().getLongValue(); + } + } + } + + /** + * Length of the "End of central directory record" - which is + * supposed to be the last structure of the archive - without file + * comment. + */ + private static final int MIN_EOCD_SIZE = + /* end of central dir signature */ WORD + /* number of this disk */ + SHORT + /* number of the disk with the */ + /* start of the central directory */ + SHORT + /* total number of entries in */ + /* the central dir on this disk */ + SHORT + /* total number of entries in */ + /* the central dir */ + SHORT + /* size of the central directory */ + WORD + /* offset of start of central */ + /* directory with respect to */ + /* the starting disk number */ + WORD + /* zipfile comment length */ + SHORT; + + /** + * Maximum length of the "End of central directory record" with a + * file comment. + */ + private static final int MAX_EOCD_SIZE = MIN_EOCD_SIZE + /* maximum length of zipfile comment */ + ZIP64_MAGIC_SHORT; + + /** + * Offset of the field that holds the location of the first + * central directory entry inside the "End of central directory + * record" relative to the start of the "End of central directory + * record". + */ + private static final int CFD_LOCATOR_OFFSET = + /* end of central dir signature */ WORD + /* number of this disk */ + SHORT + /* number of the disk with the */ + /* start of the central directory */ + SHORT + /* total number of entries in */ + /* the central dir on this disk */ + SHORT + /* total number of entries in */ + /* the central dir */ + SHORT + /* size of the central directory */ + WORD; + + /** + * Length of the "Zip64 end of central directory locator" - which + * should be right in front of the "end of central directory + * record" if one is present at all. + */ + private static final int ZIP64_EOCDL_LENGTH = + /* zip64 end of central dir locator sig */ WORD + /* number of the disk with the start */ + /* start of the zip64 end of */ + /* central directory */ + WORD + /* relative offset of the zip64 */ + /* end of central directory record */ + DWORD + /* total number of disks */ + WORD; + + /** + * Offset of the field that holds the location of the "Zip64 end + * of central directory record" inside the "Zip64 end of central + * directory locator" relative to the start of the "Zip64 end of + * central directory locator". + */ + private static final int ZIP64_EOCDL_LOCATOR_OFFSET = + /* zip64 end of central dir locator sig */ WORD + /* number of the disk with the start */ + /* start of the zip64 end of */ + /* central directory */ + WORD; + + /** + * Offset of the field that holds the location of the first + * central directory entry inside the "Zip64 end of central + * directory record" relative to the start of the "Zip64 end of + * central directory record". + */ + private static final int ZIP64_EOCD_CFD_LOCATOR_OFFSET = + /* zip64 end of central dir */ + /* signature */ WORD + /* size of zip64 end of central */ + /* directory record */ + DWORD + /* version made by */ + SHORT + /* version needed to extract */ + SHORT + /* number of this disk */ + WORD + /* number of the disk with the */ + /* start of the central directory */ + WORD + /* total number of entries in the */ + /* central directory on this disk */ + DWORD + /* total number of entries in the */ + /* central directory */ + DWORD + /* size of the central directory */ + DWORD; + + /** + * Searches for either the "Zip64 end of central directory + * locator" or the "End of central dir record", parses + * it and positions the stream at the first central directory + * record. + */ + private void positionAtCentralDirectory() + throws IOException { + positionAtEndOfCentralDirectoryRecord(); + boolean found = false; + final boolean searchedForZip64EOCD = + archive.getFilePointer() > ZIP64_EOCDL_LENGTH; + if (searchedForZip64EOCD) { + archive.seek(archive.getFilePointer() - ZIP64_EOCDL_LENGTH); + archive.readFully(WORD_BUF); + found = Arrays.equals(ZipOutputStream.ZIP64_EOCD_LOC_SIG, WORD_BUF); + } + if (!found) { + // not a ZIP64 archive + if (searchedForZip64EOCD) { + skipBytes(ZIP64_EOCDL_LENGTH - WORD); + } + positionAtCentralDirectory32(); + } else { + positionAtCentralDirectory64(); + } + } + + /** + * Parses the "Zip64 end of central directory locator", + * finds the "Zip64 end of central directory record" using the + * parsed information, parses that and positions the stream at the + * first central directory record. + */ + private void positionAtCentralDirectory64() + throws IOException { + skipBytes(ZIP64_EOCDL_LOCATOR_OFFSET + - WORD /* signature has already been read */); + archive.readFully(DWORD_BUF); + archive.seek(ZipEightByteInteger.getLongValue(DWORD_BUF)); + archive.readFully(WORD_BUF); + if (!Arrays.equals(WORD_BUF, ZipOutputStream.ZIP64_EOCD_SIG)) { + throw new ZipException( + "archive's ZIP64 end of central directory locator is corrupt."); + } + skipBytes(ZIP64_EOCD_CFD_LOCATOR_OFFSET + - WORD /* signature has already been read */); + archive.readFully(DWORD_BUF); + archive.seek(ZipEightByteInteger.getLongValue(DWORD_BUF)); + } + + /** + * Searches for the "End of central dir record", parses + * it and positions the stream at the first central directory + * record. + */ + private void positionAtCentralDirectory32() + throws IOException { + skipBytes(CFD_LOCATOR_OFFSET); + archive.readFully(WORD_BUF); + archive.seek(ZipLong.getValue(WORD_BUF)); + } + + /** + * Searches for the and positions the stream at the start of the + * "End of central dir record". + */ + private void positionAtEndOfCentralDirectoryRecord() + throws IOException { + final boolean found = tryToLocateSignature(MIN_EOCD_SIZE, MAX_EOCD_SIZE, + ZipOutputStream.EOCD_SIG); + if (!found) { + throw new ZipException("archive is not a ZIP archive"); + } + } + + /** + * Searches the archive backwards from minDistance to maxDistance + * for the given signature, positions the RandomAccessFile right + * at the signature if it has been found. + */ + private boolean tryToLocateSignature(final long minDistanceFromEnd, + final long maxDistanceFromEnd, + final byte[] sig) throws IOException { + boolean found = false; + long off = archive.length() - minDistanceFromEnd; + final long stopSearching = + Math.max(0L, archive.length() - maxDistanceFromEnd); + if (off >= 0) { + for (; off >= stopSearching; off--) { + archive.seek(off); + int curr = archive.read(); + if (curr == -1) { + break; + } + if (curr == sig[POS_0]) { + curr = archive.read(); + if (curr == sig[POS_1]) { + curr = archive.read(); + if (curr == sig[POS_2]) { + curr = archive.read(); + if (curr == sig[POS_3]) { + found = true; + break; + } + } + } + } + } + } + if (found) { + archive.seek(off); + } + return found; + } + + /** + * Skips the given number of bytes or throws an EOFException if + * skipping failed. + */ + private void skipBytes(final int count) throws IOException { + int totalSkipped = 0; + while (totalSkipped < count) { + final int skippedNow = archive.skipBytes(count - totalSkipped); + if (skippedNow <= 0) { + throw new EOFException(); + } + totalSkipped += skippedNow; + } + } + + /** + * Number of bytes in local file header up to the "length of + * filename" entry. + */ + private static final long LFH_OFFSET_FOR_FILENAME_LENGTH = + /* local file header signature */ WORD + /* version needed to extract */ + SHORT + /* general purpose bit flag */ + SHORT + /* compression method */ + SHORT + /* last mod file time */ + SHORT + /* last mod file date */ + SHORT + /* crc-32 */ + WORD + /* compressed size */ + WORD + /* uncompressed size */ + WORD; + + /** + * Walks through all recorded entries and adds the data available + * from the local file header. + * + *

Also records the offsets for the data to read from the + * entries.

+ */ + private void resolveLocalFileHeaderData(final Map + entriesWithoutUTF8Flag) + throws IOException { + for (ZipEntry zipEntry : entries) { + // entries is filled in populateFromCentralDirectory and + // never modified + final Entry ze = (Entry) zipEntry; + final OffsetEntry offsetEntry = ze.getOffsetEntry(); + final long offset = offsetEntry.headerOffset; + archive.seek(offset + LFH_OFFSET_FOR_FILENAME_LENGTH); + archive.readFully(SHORT_BUF); + final int fileNameLen = ZipShort.getValue(SHORT_BUF); + archive.readFully(SHORT_BUF); + final int extraFieldLen = ZipShort.getValue(SHORT_BUF); + int lenToSkip = fileNameLen; + while (lenToSkip > 0) { + final int skipped = archive.skipBytes(lenToSkip); + if (skipped <= 0) { + throw new IOException( + "failed to skip file name in local file header"); + } + lenToSkip -= skipped; + } + final byte[] localExtraData = new byte[extraFieldLen]; + archive.readFully(localExtraData); + ze.setExtra(localExtraData); + offsetEntry.dataOffset = offset + LFH_OFFSET_FOR_FILENAME_LENGTH + + SHORT + SHORT + fileNameLen + extraFieldLen; + + if (entriesWithoutUTF8Flag.containsKey(ze)) { + final NameAndComment nc = entriesWithoutUTF8Flag.get(ze); + ZipUtil.setNameAndCommentFromExtraFields(ze, nc.name, + nc.comment); + } + + final String name = ze.getName(); + LinkedList entriesOfThatName = nameMap.computeIfAbsent(name, k -> new LinkedList<>()); + entriesOfThatName.addLast(ze); + } + } + + /** + * Checks whether the archive starts with a LFH. If it doesn't, + * it may be an empty archive. + */ + private boolean startsWithLocalFileHeader() throws IOException { + archive.seek(0); + archive.readFully(WORD_BUF); + return Arrays.equals(WORD_BUF, ZipOutputStream.LFH_SIG); + } + + /** + * InputStream that delegates requests to the underlying + * RandomAccessFile, making sure that only bytes from a certain + * range can be read. + */ + private class BoundedInputStream extends InputStream { + private long remaining; + private long loc; + private boolean addDummyByte = false; + + BoundedInputStream(final long start, final long remaining) { + this.remaining = remaining; + loc = start; + } + + @Override + public int read() throws IOException { + if (remaining-- <= 0) { + if (addDummyByte) { + addDummyByte = false; + return 0; + } + return -1; + } + synchronized (archive) { + archive.seek(loc++); + return archive.read(); + } + } + + @Override + public int read(final byte[] b, final int off, int len) throws IOException { + if (remaining <= 0) { + if (addDummyByte) { + addDummyByte = false; + b[off] = 0; + return 1; + } + return -1; + } + + if (len <= 0) { + return 0; + } + + if (len > remaining) { + len = (int) remaining; + } + int ret; + synchronized (archive) { + archive.seek(loc); + ret = archive.read(b, off, len); + } + if (ret > 0) { + loc += ret; + remaining -= ret; + } + return ret; + } + + /** + * Inflater needs an extra dummy byte for nowrap - see + * Inflater's javadocs. + */ + void addDummy() { + addDummyByte = true; + } + } + + private static final class NameAndComment { + private final byte[] name; + private final byte[] comment; + private NameAndComment(final byte[] name, final byte[] comment) { + this.name = name; + this.comment = comment; + } + } + + /** + * Compares two ZipEntries based on their offset within the archive. + * + *

Won't return any meaningful results if one of the entries + * isn't part of the archive at all.

+ */ + private final Comparator OFFSET_COMPARATOR = (e1, e2) -> { + if (e1 == e2) { + return 0; + } + + final Entry ent1 = e1 instanceof Entry ? (Entry) e1 : null; + final Entry ent2 = e2 instanceof Entry ? (Entry) e2 : null; + if (ent1 == null) { + return 1; + } + if (ent2 == null) { + return -1; + } + final long val = (ent1.getOffsetEntry().headerOffset + - ent2.getOffsetEntry().headerOffset); + return val == 0 ? 0 : val < 0 ? -1 : +1; + }; + + /** + * Extends ZipEntry to store the offset within the archive. + */ + private static class Entry extends ZipEntry { + + private final OffsetEntry offsetEntry; + + Entry(final OffsetEntry offset) { + this.offsetEntry = offset; + } + + OffsetEntry getOffsetEntry() { + return offsetEntry; + } + + @Override + public int hashCode() { + return 3 * super.hashCode() + + (int) (offsetEntry.headerOffset % Integer.MAX_VALUE); + } + + @Override + public boolean equals(final Object other) { + if (super.equals(other)) { + // super.equals would return false if other were null or not an Entry + final Entry otherEntry = (Entry) other; + return offsetEntry.headerOffset + == otherEntry.offsetEntry.headerOffset + && offsetEntry.dataOffset + == otherEntry.offsetEntry.dataOffset; + } + return false; + } + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipLong.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipLong.java new file mode 100644 index 0000000..736b1ce --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipLong.java @@ -0,0 +1,171 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.BYTE_MASK; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.WORD; + +/** + * Utility class that represents a four byte integer with conversion + * rules for the big endian byte order of ZIP files. + * + */ +public final class ZipLong implements Cloneable { + + private static final int BYTE_1 = 1; + private static final int BYTE_1_MASK = 0xFF00; + private static final int BYTE_1_SHIFT = 8; + + private static final int BYTE_2 = 2; + private static final int BYTE_2_MASK = 0xFF0000; + private static final int BYTE_2_SHIFT = 16; + + private static final int BYTE_3 = 3; + private static final long BYTE_3_MASK = 0xFF000000L; + private static final int BYTE_3_SHIFT = 24; + + private final long value; + + /** Central File Header Signature */ + public static final ZipLong CFH_SIG = new ZipLong(0X02014B50L); + + /** Local File Header Signature */ + public static final ZipLong LFH_SIG = new ZipLong(0X04034B50L); + + /** + * Data Descriptor signature + */ + public static final ZipLong DD_SIG = new ZipLong(0X08074B50L); + + /** + * Value stored in size and similar fields if ZIP64 extensions are + * used. + */ + static final ZipLong ZIP64_MAGIC = new ZipLong(ZipConstants.ZIP64_MAGIC); + + /** + * Create instance from a number. + * @param value the long to store as a ZipLong + */ + public ZipLong(long value) { + this.value = value; + } + + /** + * Create instance from bytes. + * @param bytes the bytes to store as a ZipLong + */ + public ZipLong(byte[] bytes) { + this(bytes, 0); + } + + /** + * Create instance from the four bytes starting at offset. + * @param bytes the bytes to store as a ZipLong + * @param offset the offset to start + */ + public ZipLong(byte[] bytes, int offset) { + value = ZipLong.getValue(bytes, offset); + } + + /** + * Get value as four bytes in big endian byte order. + * @return value as four bytes in big endian order + */ + public byte[] getBytes() { + return ZipLong.getBytes(value); + } + + /** + * Get value as Java long. + * @return value as a long + */ + public long getValue() { + return value; + } + + /** + * Get value as four bytes in big endian byte order. + * @param value the value to convert + * @return value as four bytes in big endian byte order + */ + public static byte[] getBytes(long value) { + byte[] result = new byte[WORD]; + putLong(value, result, 0); + return result; + } + + /** + * put the value as four bytes in big endian byte order. + * @param value the Java long to convert to bytes + * @param buf the output buffer + * @param offset + * The offset within the output buffer of the first byte to be written. + * must be non-negative and no larger than buf.length-4 + */ + public static void putLong(long value, byte[] buf, int offset) { + buf[offset++] = (byte) ((value & BYTE_MASK)); + buf[offset++] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); + buf[offset++] = (byte) ((value & BYTE_2_MASK) >> BYTE_2_SHIFT); + buf[offset] = (byte) ((value & BYTE_3_MASK) >> BYTE_3_SHIFT); + } + + public void putLong(byte[] buf, int offset) { + putLong(value, buf, offset); + } + + /** + * Helper method to get the value as a Java long from four bytes starting at given array offset + * @param bytes the array of bytes + * @param offset the offset to start + * @return the corresponding Java long value + */ + public static long getValue(byte[] bytes, int offset) { + long value = (bytes[offset + BYTE_3] << BYTE_3_SHIFT) & BYTE_3_MASK; + value += (bytes[offset + BYTE_2] << BYTE_2_SHIFT) & BYTE_2_MASK; + value += (bytes[offset + BYTE_1] << BYTE_1_SHIFT) & BYTE_1_MASK; + value += (bytes[offset] & BYTE_MASK); + return value; + } + + /** + * Helper method to get the value as a Java long from a four-byte array + * @param bytes the array of bytes + * @return the corresponding Java long value + */ + public static long getValue(byte[] bytes) { + return getValue(bytes, 0); + } + + /** + * Override to make two instances with same value equal. + * @param o an object to compare + * @return true if the objects are equal + */ + @Override + public boolean equals(Object o) { + return o instanceof ZipLong && value == ((ZipLong) o).getValue(); + } + + /** + * Override to make two instances with same value equal. + * @return the value stored in the ZipLong + */ + @Override + public int hashCode() { + return (int) value; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException cnfe) { + // impossible + throw new RuntimeException(cnfe); + } + } + + @Override + public String toString() { + return "ZipLong value: " + value; + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipOutputStream.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipOutputStream.java new file mode 100644 index 0000000..2b28f76 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipOutputStream.java @@ -0,0 +1,1646 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.ZipException; + +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.DWORD; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.INITIAL_VERSION; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.SHORT; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.WORD; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.ZIP64_MAGIC; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.ZIP64_MAGIC_SHORT; +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.ZIP64_MIN_VERSION; +import static org.xbib.gradle.plugin.shadow.zip.ZipLong.putLong; +import static org.xbib.gradle.plugin.shadow.zip.ZipShort.putShort; + +/** + * Reimplementation of {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} that does handle the extended + * functionality of this package, especially internal/external file + * attributes and extra fields with different layouts for local file + * data and central directory entries. + * + *

This class will try to use {@link java.io.RandomAccessFile + * RandomAccessFile} when you know that the output is going to go to a + * file.

+ * + *

If RandomAccessFile cannot be used, this implementation will use + * a Data Descriptor to store size and CRC information for {@link + * #DEFLATED DEFLATED} entries, this means, you don't need to + * calculate them yourself. Unfortunately this is not possible for + * the {@link #STORED STORED} method, here setting the CRC and + * uncompressed size information is required before {@link + * #putNextEntry putNextEntry} can be called.

+ * + *

Transparently supported are Zip64 + * extensions and thus individual entries and archives larger than 4 + * GB or with more than 65536 entries in most cases but explicit + * control is provided via {@link #setUseZip64}. If the stream can not + * user RandomAccessFile and you try to write a ZipEntry of + * unknown size then Zip64 extensions will be disabled by default.

+ */ +public class ZipOutputStream extends FilterOutputStream { + + private static final int BUFFER_SIZE = 512; + private static final int LFH_SIG_OFFSET = 0; + private static final int LFH_VERSION_NEEDED_OFFSET = 4; + private static final int LFH_GPB_OFFSET = 6; + private static final int LFH_METHOD_OFFSET = 8; + private static final int LFH_TIME_OFFSET = 10; + private static final int LFH_CRC_OFFSET = 14; + private static final int LFH_COMPRESSED_SIZE_OFFSET = 18; + private static final int LFH_ORIGINAL_SIZE_OFFSET = 22; + private static final int LFH_FILENAME_LENGTH_OFFSET = 26; + private static final int LFH_EXTRA_LENGTH_OFFSET = 28; + private static final int LFH_FILENAME_OFFSET = 30; + private static final int CFH_SIG_OFFSET = 0; + private static final int CFH_VERSION_MADE_BY_OFFSET = 4; + private static final int CFH_VERSION_NEEDED_OFFSET = 6; + private static final int CFH_GPB_OFFSET = 8; + private static final int CFH_METHOD_OFFSET = 10; + private static final int CFH_TIME_OFFSET = 12; + private static final int CFH_CRC_OFFSET = 16; + private static final int CFH_COMPRESSED_SIZE_OFFSET = 20; + private static final int CFH_ORIGINAL_SIZE_OFFSET = 24; + private static final int CFH_FILENAME_LENGTH_OFFSET = 28; + private static final int CFH_EXTRA_LENGTH_OFFSET = 30; + private static final int CFH_COMMENT_LENGTH_OFFSET = 32; + private static final int CFH_DISK_NUMBER_OFFSET = 34; + private static final int CFH_INTERNAL_ATTRIBUTES_OFFSET = 36; + private static final int CFH_EXTERNAL_ATTRIBUTES_OFFSET = 38; + private static final int CFH_LFH_OFFSET = 42; + private static final int CFH_FILENAME_OFFSET = 46; + + /** + * indicates if this archive is finished. + */ + private boolean finished = false; + + /* + * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs + * when it gets handed a really big buffer. See + * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396 + * + * Using a buffer size of 8 kB proved to be a good compromise + */ + private static final int DEFLATER_BLOCK_SIZE = 8192; + + /** + * Compression method for deflated entries. + */ + public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED; + + /** + * Default compression level for deflated entries. + */ + public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION; + + /** + * Compression method for stored entries. + */ + public static final int STORED = java.util.zip.ZipEntry.STORED; + + /** + * default encoding for file names and comment. + */ + static final String DEFAULT_ENCODING = null; + + /** + * General purpose flag, which indicates that filenames are + * written in utf-8. + * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead + */ + @Deprecated + public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG; + + private static final byte[] EMPTY = new byte[0]; + + /** + * Current entry. + */ + private CurrentEntry entry; + + /** + * The file comment. + */ + private String comment = ""; + + /** + * Compression level for next entry. + */ + private int level = DEFAULT_COMPRESSION; + + /** + * Has the compression level changed when compared to the last + * entry? + */ + private boolean hasCompressionLevelChanged = false; + + /** + * Default compression method for next entry. + */ + private int method = java.util.zip.ZipEntry.DEFLATED; + + /** + * List of ZipEntries written so far. + */ + private final List entries = new LinkedList<>(); + + /** + * CRC instance to avoid parsing DEFLATED data twice. + */ + private final CRC32 crc = new CRC32(); + + /** + * Count the bytes written to out. + */ + private long written = 0; + + /** + * Start of central directory. + */ + private long cdOffset = 0; + + /** + * Length of central directory. + */ + private long cdLength = 0; + + /** + * Helper, a 0 as ZipShort. + */ + private static final byte[] ZERO = {0, 0}; + + /** + * Helper, a 0 as ZipLong. + */ + private static final byte[] LZERO = {0, 0, 0, 0}; + + private static final byte[] ONE = ZipLong.getBytes(1L); + + /** + * Holds the offsets of the LFH starts for each entry. + */ + private final Map offsets = new HashMap<>(); + + /** + * The encoding to use for filenames and the file comment. + * + *

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. + * Defaults to the platform's default character encoding.

+ */ + private String encoding = null; + + /** + * The zip encoding to use for filenames and the file comment. + *

+ * This field is of internal use and will be set in {@link + * #setEncoding(String)}. + *

+ */ + private ZipEncoding zipEncoding = + ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING); + + /** + * This Deflater object is used for output. + * + */ + protected final Deflater def = new Deflater(level, true); + + /** + * This buffer serves as a Deflater. + * + *

This attribute is only protected to provide a level of API + * backwards compatibility. This class used to extend {@link + * java.util.zip.DeflaterOutputStream DeflaterOutputStream}

+ */ + protected byte[] buf = new byte[BUFFER_SIZE]; + + /** + * Optional random access output. + */ + private final RandomAccessFile raf; + + /** + * whether to use the general purpose bit flag when writing UTF-8 + * filenames or not. + */ + private boolean useUTF8Flag = true; + + /** + * Whether to encode non-encodable file names as UTF-8. + */ + private boolean fallbackToUTF8 = false; + + /** + * whether to create UnicodePathExtraField-s for each entry. + */ + private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER; + + /** + * Whether anything inside this archive has used a ZIP64 feature. + */ + private boolean hasUsedZip64 = false; + + private Zip64Mode zip64Mode = Zip64Mode.AsNeeded; + + private final Calendar calendarInstance = Calendar.getInstance(); + + /** + * Creates a new ZIP OutputStream filtering the underlying stream. + * @param out the outputstream to zip + */ + public ZipOutputStream(OutputStream out) { + super(out); + this.raf = null; + } + + /** + * Creates a new ZIP OutputStream writing to a File. Will use + * random access if possible. + * @param file the file to zip to + * @throws IOException on error + */ + public ZipOutputStream(File file) throws IOException { + super(null); + RandomAccessFile ranf = null; + try { + ranf = new RandomAccessFile(file, "rw"); + ranf.setLength(0); + } catch (IOException e) { + if (ranf != null) { + try { + ranf.close(); + } catch (IOException inner) { // NOPMD + // ignore + } + ranf = null; + } + out = Files.newOutputStream(file.toPath()); + } + raf = ranf; + } + + /** + * This method indicates whether this archive is writing to a + * seekable stream (i.e., to a random access file). + * + *

For seekable streams, you don't need to calculate the CRC or + * uncompressed size for {@link #STORED} entries before + * invoking {@link #putNextEntry}. + * @return true if seekable + */ + public boolean isSeekable() { + return raf != null; + } + + /** + * The encoding to use for filenames and the file comment. + * + *

For a list of possible values see http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html. + * Defaults to the platform's default character encoding.

+ * @param encoding the encoding value + */ + public void setEncoding(final String encoding) { + this.encoding = encoding; + this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); + if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) { + useUTF8Flag = false; + } + } + + /** + * The encoding to use for filenames and the file comment. + * + * @return null if using the platform's default character encoding. + */ + public String getEncoding() { + return encoding; + } + + /** + * Whether to set the language encoding flag if the file name + * encoding is UTF-8. + * + *

Defaults to true.

+ * + * @param b boolean + */ + public void setUseLanguageEncodingFlag(boolean b) { + useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding); + } + + /** + * Whether to create Unicode Extra Fields. + * + *

Defaults to NEVER.

+ * + * @param b boolean + */ + public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) { + createUnicodeExtraFields = b; + } + + /** + * Whether to fall back to UTF and the language encoding flag if + * the file name cannot be encoded using the specified encoding. + * + *

Defaults to false.

+ * + * @param b boolean + */ + public void setFallbackToUTF8(boolean b) { + fallbackToUTF8 = b; + } + + /** + * Whether Zip64 extensions will be used. + * + *

When setting the mode to {@link Zip64Mode#Never Never}, + * {@link #putNextEntry}, {@link #closeEntry}, {@link + * #finish} or {@link #close} may throw a {@link + * Zip64RequiredException} if the entry's size or the total size + * of the archive exceeds 4GB or there are more than 65536 entries + * inside the archive. Any archive created in this mode will be + * readable by implementations that don't support Zip64.

+ * + *

When setting the mode to {@link Zip64Mode#Always Always}, + * Zip64 extensions will be used for all entries. Any archive + * created in this mode may be unreadable by implementations that + * don't support Zip64 even if all its contents would be.

+ * + *

When setting the mode to {@link Zip64Mode#AsNeeded + * AsNeeded}, Zip64 extensions will transparently be used for + * those entries that require them. This mode can only be used if + * the uncompressed size of the {@link ZipEntry} is known + * when calling {@link #putNextEntry} or the archive is written + * to a seekable output (i.e. you have used the {@link + * #ZipOutputStream(java.io.File) File-arg constructor}) - + * this mode is not valid when the output stream is not seekable + * and the uncompressed size is unknown when {@link + * #putNextEntry} is called.

+ * + *

If no entry inside the resulting archive requires Zip64 + * extensions then {@link Zip64Mode#Never Never} will create the + * smallest archive. {@link Zip64Mode#AsNeeded AsNeeded} will + * create a slightly bigger archive if the uncompressed size of + * any entry has initially been unknown and create an archive + * identical to {@link Zip64Mode#Never Never} otherwise. {@link + * Zip64Mode#Always Always} will create an archive that is at + * least 24 bytes per entry bigger than the one {@link + * Zip64Mode#Never Never} would create.

+ * + *

Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless + * {@link #putNextEntry} is called with an entry of unknown + * size and data is written to a non-seekable stream - in this + * case the default is {@link Zip64Mode#Never Never}.

+ * + * @param mode Zip64Mode + */ + public void setUseZip64(Zip64Mode mode) { + zip64Mode = mode; + } + + /** + * @throws Zip64RequiredException if the archive's size exceeds 4 + * GByte or there are more than 65535 entries inside the archive + * and {@link #setUseZip64} is {@link Zip64Mode#Never}. + */ + public void finish() throws IOException { + if (finished) { + throw new IOException("This archive has already been finished"); + } + + if (entry != null) { + closeEntry(); + } + + cdOffset = written; + writeCentralDirectoryInChunks(); + cdLength = written - cdOffset; + writeZip64CentralDirectory(); + writeCentralDirectoryEnd(); + offsets.clear(); + entries.clear(); + def.end(); + finished = true; + } + + private void writeCentralDirectoryInChunks() throws IOException { + final int NUM_PER_WRITE = 1000; + final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(70 * NUM_PER_WRITE); + int count = 0; + for (ZipEntry ze : entries) { + byteArrayOutputStream.write(createCentralFileHeader(ze)); + if (++count > NUM_PER_WRITE) { + writeCounted(byteArrayOutputStream.toByteArray()); + byteArrayOutputStream.reset(); + count = 0; + } + } + writeCounted(byteArrayOutputStream.toByteArray()); + } + + /** + * Writes all necessary data for this entry. + * + * @throws IOException on error + * @throws Zip64RequiredException if the entry's uncompressed or + * compressed size exceeds 4 GByte and {@link #setUseZip64} + * is {@link Zip64Mode#Never}. + */ + public void closeEntry() throws IOException { + preClose(); + + flushDeflater(); + + final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); + long bytesWritten = written - entry.dataStart; + long realCrc = crc.getValue(); + crc.reset(); + + final boolean actuallyNeedsZip64 = + handleSizesAndCrc(bytesWritten, realCrc, effectiveMode); + + closeEntry(actuallyNeedsZip64); + } + + private void closeEntry(boolean actuallyNeedsZip64) throws IOException { + if (raf != null) { + rewriteSizesAndCrc(actuallyNeedsZip64); + } + + writeDataDescriptor(entry.entry); + entry = null; + } + + private void preClose() throws IOException { + if (finished) { + throw new IOException("Stream has already been finished"); + } + + if (entry == null) { + throw new IOException("No current entry to close"); + } + + if (!entry.hasWritten) { + write(EMPTY, 0, 0); + } + } + + /** + * Ensures all bytes sent to the deflater are written to the stream. + */ + private void flushDeflater() throws IOException { + if (entry.entry.getMethod() == DEFLATED) { + def.finish(); + while (!def.finished()) { + deflate(); + } + } + } + + /** + * Ensures the current entry's size and CRC information is set to + * the values just written, verifies it isn't too big in the + * Zip64Mode.Never case and returns whether the entry would + * require a Zip64 extra field. + * + * @param bytesWritten long + * @param crc long + * @param effectiveMode Zip64Mode + * @return boolean + * @throws ZipException if size or CRC is incorrect + */ + private boolean handleSizesAndCrc(long bytesWritten, long crc, + Zip64Mode effectiveMode) + throws ZipException { + if (entry.entry.getMethod() == DEFLATED) { + /* It turns out def.getBytesRead() returns wrong values if + * the size exceeds 4 GB on Java < Java7 + entry.entry.setSize(def.getBytesRead()); + */ + entry.entry.setSize(entry.bytesRead); + entry.entry.setCompressedSize(bytesWritten); + entry.entry.setCrc(crc); + + def.reset(); + } else if (raf == null) { + if (entry.entry.getCrc() != crc) { + throw new ZipException("bad CRC checksum for entry " + + entry.entry.getName() + ": " + + Long.toHexString(entry.entry.getCrc()) + + " instead of " + + Long.toHexString(crc)); + } + + if (entry.entry.getSize() != bytesWritten) { + throw new ZipException("bad size for entry " + + entry.entry.getName() + ": " + + entry.entry.getSize() + + " instead of " + + bytesWritten); + } + } else { /* method is STORED and we used RandomAccessFile */ + entry.entry.setSize(bytesWritten); + entry.entry.setCompressedSize(bytesWritten); + entry.entry.setCrc(crc); + } + + return checkIfNeedsZip64(effectiveMode); + } + + /** + * Ensures the current entry's size and CRC information is set to + * the values just written, verifies it isn't too big in the + * Zip64Mode.Never case and returns whether the entry would + * require a Zip64 extra field. + * + * @param effectiveMode Zip64Mode + * @return boolean + * @throws ZipException if the entry is too big for Zip64Mode.Never + */ + private boolean checkIfNeedsZip64(Zip64Mode effectiveMode) + throws ZipException { + final boolean actuallyNeedsZip64 = isZip64Required(entry.entry, + effectiveMode); + if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) { + throw new Zip64RequiredException(Zip64RequiredException + .getEntryTooBigMessage(entry.entry)); + } + return actuallyNeedsZip64; + } + + private boolean isZip64Required(ZipEntry entry1, Zip64Mode requestedMode) { + return requestedMode == Zip64Mode.Always || isTooLageForZip32(entry1); + } + + private boolean isTooLageForZip32(ZipEntry zipArchiveEntry) { + return zipArchiveEntry.getSize() >= ZIP64_MAGIC + || zipArchiveEntry.getCompressedSize() >= ZIP64_MAGIC; + } + + /** + * When using random access output, write the local file header + * and potentially the ZIP64 extra containing the correct CRC and + * compressed/uncompressed sizes. + * + * @param actuallyNeedsZip64 boolean + */ + private void rewriteSizesAndCrc(boolean actuallyNeedsZip64) + throws IOException { + long save = raf.getFilePointer(); + + raf.seek(entry.localDataStart); + writeOut(ZipLong.getBytes(entry.entry.getCrc())); + if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) { + writeOut(ZipLong.getBytes(entry.entry.getCompressedSize())); + writeOut(ZipLong.getBytes(entry.entry.getSize())); + } else { + writeOut(ZipLong.ZIP64_MAGIC.getBytes()); + writeOut(ZipLong.ZIP64_MAGIC.getBytes()); + } + + if (hasZip64Extra(entry.entry)) { + // seek to ZIP64 extra, skip header and size information + raf.seek(entry.localDataStart + 3 * WORD + 2 * SHORT + + getName(entry.entry).limit() + 2 * SHORT); + // inside the ZIP64 extra uncompressed size comes + // first, unlike the LFH, CD or data descriptor + writeOut(ZipEightByteInteger.getBytes(entry.entry.getSize())); + writeOut(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize())); + + if (!actuallyNeedsZip64) { + // do some cleanup: + // * rewrite version needed to extract + raf.seek(entry.localDataStart - 5 * SHORT); + writeOut(ZipShort.getBytes(INITIAL_VERSION)); + + // * remove ZIP64 extra so it doesn't get written + // to the central directory + entry.entry.removeExtraField(Zip64ExtendedInformationExtraField + .HEADER_ID); + entry.entry.setExtra(); + + // * reset hasUsedZip64 if it has been set because + // of this entry + if (entry.causedUseOfZip64) { + hasUsedZip64 = false; + } + } + } + raf.seek(save); + } + + /** + * @throws Zip64RequiredException if the entry's uncompressed or + * compressed size is known to exceed 4 GByte and {@link #setUseZip64} + * is {@link Zip64Mode#Never}. + */ + public void putNextEntry(ZipEntry archiveEntry) throws IOException { + if (finished) { + throw new IOException("Stream has already been finished"); + } + + if (entry != null) { + closeEntry(); + } + + entry = new CurrentEntry(archiveEntry); + entries.add(entry.entry); + + setDefaults(entry.entry); + + final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); + validateSizeInformation(effectiveMode); + + if (shouldAddZip64Extra(entry.entry, effectiveMode)) { + + Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry); + + // just a placeholder, real data will be in data + // descriptor or inserted later via RandomAccessFile + ZipEightByteInteger size = ZipEightByteInteger.ZERO; + ZipEightByteInteger compressedSize = ZipEightByteInteger.ZERO; + if (entry.entry.getMethod() == STORED + && entry.entry.getSize() != -1) { + // actually, we already know the sizes + size = new ZipEightByteInteger(entry.entry.getSize()); + compressedSize = size; + } + z64.setSize(size); + z64.setCompressedSize(compressedSize); + entry.entry.setExtra(); + } + + if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) { + def.setLevel(level); + hasCompressionLevelChanged = false; + } + writeLocalFileHeader(entry.entry); + } + + /** + * Provides default values for compression method and last + * modification time. + * + * @param entry ZipEntry + */ + private void setDefaults(ZipEntry entry) { + if (entry.getMethod() == -1) { // not specified + entry.setMethod(method); + } + + if (entry.getTime() == -1) { // not specified + entry.setTime(System.currentTimeMillis()); + } + } + + /** + * Throws an exception if the size is unknown for a stored entry + * that is written to a non-seekable output or the entry is too + * big to be written without Zip64 extra but the mode has been set + * to Never. + * + * @param effectiveMode Zip64Mode + */ + private void validateSizeInformation(Zip64Mode effectiveMode) + throws ZipException { + // Size/CRC not required if RandomAccessFile is used + if (entry.entry.getMethod() == STORED && raf == null) { + if (entry.entry.getSize() == -1) { + throw new ZipException("uncompressed size is required for" + + " STORED method when not writing to a" + + " file"); + } + if (entry.entry.getCrc() == -1) { + throw new ZipException("crc checksum is required for STORED" + + " method when not writing to a file"); + } + entry.entry.setCompressedSize(entry.entry.getSize()); + } + + if ((entry.entry.getSize() >= ZIP64_MAGIC + || entry.entry.getCompressedSize() >= ZIP64_MAGIC) + && effectiveMode == Zip64Mode.Never) { + throw new Zip64RequiredException(Zip64RequiredException + .getEntryTooBigMessage(entry.entry)); + } + } + + /** + * Whether to add a Zip64 extended information extra field to the + * local file header. + * + *

Returns true if

+ * + *
    + *
  • mode is Always
  • + *
  • or we already know it is going to be needed
  • + *
  • or the size is unknown and we can ensure it won't hurt + * other implementations if we add it (i.e. we can erase its + * usage
  • + *
+ * + * @param entry ZipEntry + * @param mode Zip64Mode + */ + private boolean shouldAddZip64Extra(ZipEntry entry, Zip64Mode mode) { + return mode == Zip64Mode.Always + || entry.getSize() >= ZIP64_MAGIC + || entry.getCompressedSize() >= ZIP64_MAGIC + || (entry.getSize() == -1 + && raf != null && mode != Zip64Mode.Never); + } + + /** + * Set the file comment. + * + * @param comment the comment + */ + public void setComment(String comment) { + this.comment = comment; + } + + /** + * Sets the compression level for subsequent entries. + * + *

Default is Deflater.DEFAULT_COMPRESSION.

+ * + * @param level the compression level. + * @throws IllegalArgumentException if an invalid compression + * level is specified. + */ + public void setLevel(int level) { + if (level < Deflater.DEFAULT_COMPRESSION + || level > Deflater.BEST_COMPRESSION) { + throw new IllegalArgumentException("Invalid compression level: " + + level); + } + if (this.level == level) { + return; + } + hasCompressionLevelChanged = true; + this.level = level; + } + + /** + * Sets the default compression method for subsequent entries. + * + *

Default is DEFLATED.

+ * + * @param method an int from java.util.zip.ZipEntry + */ + public void setMethod(int method) { + this.method = method; + } + + /** + * Whether this stream is able to write the given entry. + * + *

May return false if it is set up to use encryption or a + * compression method that hasn't been implemented yet.

+ * + * @param ae ZipEntry + * @return boolean + */ + public boolean canWriteEntryData(ZipEntry ae) { + return ZipUtil.canHandleEntryData(ae); + } + + /** + * Writes bytes to ZIP entry. + * + * @param b the byte array to write + * @param offset the start position to write from + * @param length the number of bytes to write + * @throws IOException on error + */ + @Override + public void write(byte[] b, int offset, int length) throws IOException { + if (entry == null) { + throw new IllegalStateException("No current entry"); + } + ZipUtil.checkRequestedFeatures(entry.entry); + entry.hasWritten = true; + if (entry.entry.getMethod() == DEFLATED) { + writeDeflated(b, offset, length); + } else { + writeCounted(b, offset, length); + } + crc.update(b, offset, length); + } + + /** + * Write bytes to output or random access file. + * + * @param data the byte array to write + * @throws IOException on error + */ + private void writeCounted(byte[] data) throws IOException { + writeCounted(data, 0, data.length); + } + + private void writeCounted(byte[] data, int offset, int length) throws IOException { + writeOut(data, offset, length); + written += length; + } + + /** + * write implementation for DEFLATED entries. + * + * @param b byte[] + * @param offset int + * @param length int + */ + private void writeDeflated(byte[] b, int offset, int length) + throws IOException { + if (length > 0 && !def.finished()) { + entry.bytesRead += length; + if (length <= DEFLATER_BLOCK_SIZE) { + def.setInput(b, offset, length); + deflateUntilInputIsNeeded(); + } else { + final int fullblocks = length / DEFLATER_BLOCK_SIZE; + for (int i = 0; i < fullblocks; i++) { + def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, + DEFLATER_BLOCK_SIZE); + deflateUntilInputIsNeeded(); + } + final int done = fullblocks * DEFLATER_BLOCK_SIZE; + if (done < length) { + def.setInput(b, offset + done, length - done); + deflateUntilInputIsNeeded(); + } + } + } + } + + /** + * Closes this output stream and releases any system resources + * associated with the stream. + * + * @throws IOException if an I/O error occurs. + * @throws Zip64RequiredException if the archive's size exceeds 4 + * GByte or there are more than 65535 entries inside the archive + * and {@link #setUseZip64} is {@link Zip64Mode#Never}. + */ + @Override + public void close() throws IOException { + if (!finished) { + finish(); + } + destroy(); + } + + /** + * Flushes this output stream and forces any buffered output bytes + * to be written out to the stream. + * + * @exception IOException if an I/O error occurs. + */ + @Override + public void flush() throws IOException { + if (out != null) { + out.flush(); + } + } + + /* + * Various ZIP constants + */ + /** + * local file header signature + */ + protected static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); //NOSONAR + /** + * data descriptor signature + */ + protected static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes(); //NOSONAR + /** + * central file header signature + */ + protected static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); //NOSONAR + /** + * end of central dir signature + */ + protected static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); //NOSONAR + /** + * ZIP64 end of central dir signature + */ + static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L); //NOSONAR + /** + * ZIP64 end of central dir locator signature + */ + static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L); //NOSONAR + + /** + * Writes next block of compressed data to the output stream. + * + * @throws IOException on error + */ + protected final void deflate() throws IOException { + int len = def.deflate(buf, 0, buf.length); + if (len > 0) { + writeCounted(buf, 0, len); + } + } + + /** + * Writes the local file header entry + * + * @param ze the entry to write + * @throws IOException on error + */ + protected void writeLocalFileHeader(ZipEntry ze) throws IOException { + + boolean encodable = zipEncoding.canEncode(ze.getName()); + ByteBuffer name = getName(ze); + + if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) { + addUnicodeExtraFields(ze, encodable, name); + } + + final byte[] localHeader = createLocalFileHeader(ze, name, encodable); + final long localHeaderStart = written; + offsets.put(ze, localHeaderStart); + entry.localDataStart = localHeaderStart + LFH_CRC_OFFSET; // At crc offset + writeCounted(localHeader); + entry.dataStart = written; + } + + private byte[] createLocalFileHeader(ZipEntry ze, ByteBuffer name, boolean encodable) { + byte[] extra = ze.getLocalFileDataExtra(); + final int nameLen = name.limit() - name.position(); + int len = LFH_FILENAME_OFFSET + nameLen + extra.length; + byte[] buf = new byte[len]; + + System.arraycopy(LFH_SIG, 0, buf, LFH_SIG_OFFSET, WORD); + + //store method in local variable to prevent multiple method calls + final int zipMethod = ze.getMethod(); + + putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze)), + buf, LFH_VERSION_NEEDED_OFFSET); + + GeneralPurposeBit generalPurposeBit = + getGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8); + generalPurposeBit.encode(buf, LFH_GPB_OFFSET); + + // compression method + putShort(zipMethod, buf, LFH_METHOD_OFFSET); + + ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, LFH_TIME_OFFSET); + + // CRC + if (zipMethod == DEFLATED || raf != null) { + System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, WORD); + } else { + putLong(ze.getCrc(), buf, LFH_CRC_OFFSET); + } + + // compressed length + // uncompressed length + if (hasZip64Extra(entry.entry)) { + // point to ZIP64 extended information extra field for + // sizes, may get rewritten once sizes are known if + // stream is seekable + ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET); + ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET); + } else if (zipMethod == DEFLATED || raf != null) { + System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, WORD); + System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, WORD); + } else { // Stored + putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET); + putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET); + } + // file name length + putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET); + + // extra field length + putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET); + + // file name + System.arraycopy(name.array(), name.arrayOffset(), buf, + LFH_FILENAME_OFFSET, nameLen); + + System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length); + return buf; + } + + /** + * Adds UnicodeExtra fields for name and file comment if mode is + * ALWAYS or the data cannot be encoded using the configured + * encoding. + * + * @param ze ZipEntry + * @param encodable boolean + * @param name ByteBuffer + */ + private void addUnicodeExtraFields(ZipEntry ze, boolean encodable, + ByteBuffer name) + throws IOException { + if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS + || !encodable) { + ze.addExtraField(new UnicodePathExtraField(ze.getName(), + name.array(), + name.arrayOffset(), + name.limit() + - name.position())); + } + + String comm = ze.getComment(); + if (comm == null || comm.isEmpty()) { + return; + } + + if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS + || !zipEncoding.canEncode(comm)) { + ByteBuffer commentB = getEntryEncoding(ze).encode(comm); + ze.addExtraField(new UnicodeCommentExtraField(comm, + commentB.array(), commentB.arrayOffset(), + commentB.limit() - commentB.position())); + } + } + + /** + * Writes the data descriptor entry. + * + * @param ze the entry to write + * @throws IOException on error + */ + protected void writeDataDescriptor(ZipEntry ze) throws IOException { + if (ze.getMethod() != DEFLATED || raf != null) { + return; + } + writeCounted(DD_SIG); + writeCounted(ZipLong.getBytes(ze.getCrc())); + if (!hasZip64Extra(ze)) { + writeCounted(ZipLong.getBytes(ze.getCompressedSize())); + writeCounted(ZipLong.getBytes(ze.getSize())); + } else { + writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize())); + writeCounted(ZipEightByteInteger.getBytes(ze.getSize())); + } + } + + /** + * Writes the central file header entry. + * + * @param ze the entry to write + * @throws IOException on error + * @throws Zip64RequiredException if the archive's size exceeds 4 + * GByte and {@link Zip64Mode #setUseZip64} is {@link + * Zip64Mode#Never}. + */ + protected void writeCentralFileHeader(ZipEntry ze) throws IOException { + byte[] centralFileHeader = createCentralFileHeader(ze); + writeCounted(centralFileHeader); + } + + private byte[] createCentralFileHeader(ZipEntry ze) throws IOException { + final long lfhOffset = offsets.get(ze); + final boolean needsZip64Extra = hasZip64Extra(ze) + || ze.getCompressedSize() >= ZIP64_MAGIC + || ze.getSize() >= ZIP64_MAGIC + || lfhOffset >= ZIP64_MAGIC; + + if (needsZip64Extra && zip64Mode == Zip64Mode.Never) { + // must be the offset that is too big, otherwise an + // exception would have been throw in putArchiveEntry or + // closeArchiveEntry + throw new Zip64RequiredException(Zip64RequiredException + .ARCHIVE_TOO_BIG_MESSAGE); + } + + + handleZip64Extra(ze, lfhOffset, needsZip64Extra); + + return createCentralFileHeader(ze, getName(ze), lfhOffset, needsZip64Extra); + } + + /** + * Writes the central file header entry. + * + * @param ze the entry to write + * @param name The encoded name + * @param lfhOffset Local file header offset for this file + * @throws IOException on error + */ + private byte[] createCentralFileHeader(ZipEntry ze, ByteBuffer name, long lfhOffset, + boolean needsZip64Extra) throws IOException { + byte[] extra = ze.getCentralDirectoryExtra(); + + // file comment length + String comm = ze.getComment(); + if (comm == null) { + comm = ""; + } + + ByteBuffer commentB = getEntryEncoding(ze).encode(comm); + final int nameLen = name.limit() - name.position(); + final int commentLen = commentB.limit() - commentB.position(); + int len = CFH_FILENAME_OFFSET + nameLen + extra.length + commentLen; + byte[] buf = new byte[len]; + + System.arraycopy(CFH_SIG, 0, buf, CFH_SIG_OFFSET, WORD); + + // version made by + // CheckStyle:MagicNumber OFF + putShort((ze.getPlatform() << 8) | (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION : ZIP64_MIN_VERSION), + buf, CFH_VERSION_MADE_BY_OFFSET); + + final int zipMethod = ze.getMethod(); + final boolean encodable = zipEncoding.canEncode(ze.getName()); + putShort(versionNeededToExtract(zipMethod, needsZip64Extra), buf, CFH_VERSION_NEEDED_OFFSET); + getGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8).encode(buf, CFH_GPB_OFFSET); + + // compression method + putShort(zipMethod, buf, CFH_METHOD_OFFSET); + + + // last mod. time and date + ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, CFH_TIME_OFFSET); + + // CRC + // compressed length + // uncompressed length + putLong(ze.getCrc(), buf, CFH_CRC_OFFSET); + if (ze.getCompressedSize() >= ZIP64_MAGIC + || ze.getSize() >= ZIP64_MAGIC) { + ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET); + ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET); + } else { + putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET); + putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET); + } + + putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET); + + // extra field length + putShort(extra.length, buf, CFH_EXTRA_LENGTH_OFFSET); + + putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET); + + // disk number start + System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, SHORT); + + // internal file attributes + putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET); + + // external file attributes + putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET); + + // relative offset of LFH + putLong(Math.min(lfhOffset, ZIP64_MAGIC), buf, CFH_LFH_OFFSET); + + // file name + System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen); + + int extraStart = CFH_FILENAME_OFFSET + nameLen; + System.arraycopy(extra, 0, buf, extraStart, extra.length); + + int commentStart = extraStart + extra.length; + + // file comment + System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen); + return buf; + } + + /** + * If the entry needs Zip64 extra information inside the central + * directory then configure its data. + * + * @param ze ZipEntry + * @param lfhOffset long + * @param needsZip64Extra boolean + */ + private void handleZip64Extra(ZipEntry ze, long lfhOffset, + boolean needsZip64Extra) { + if (needsZip64Extra) { + Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze); + if (ze.getCompressedSize() >= ZIP64_MAGIC + || ze.getSize() >= ZIP64_MAGIC) { + z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize())); + z64.setSize(new ZipEightByteInteger(ze.getSize())); + } else { + // reset value that may have been set for LFH + z64.setCompressedSize(null); + z64.setSize(null); + } + if (lfhOffset >= ZIP64_MAGIC) { + z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset)); + } + ze.setExtra(); + } + } + + /** + * Writes the "End of central dir record". + * + * @throws IOException on error + * @throws Zip64RequiredException if the archive's size exceeds 4 + * GByte or there are more than 65535 entries inside the archive + * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}. + */ + protected void writeCentralDirectoryEnd() throws IOException { + writeCounted(EOCD_SIG); + + // disk numbers + writeCounted(ZERO); + writeCounted(ZERO); + + // number of entries + int numberOfEntries = entries.size(); + if (numberOfEntries > ZIP64_MAGIC_SHORT + && zip64Mode == Zip64Mode.Never) { + throw new Zip64RequiredException(Zip64RequiredException + .TOO_MANY_ENTRIES_MESSAGE); + } + if (cdOffset > ZIP64_MAGIC && zip64Mode == Zip64Mode.Never) { + throw new Zip64RequiredException(Zip64RequiredException + .ARCHIVE_TOO_BIG_MESSAGE); + } + + byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, + ZIP64_MAGIC_SHORT)); + writeCounted(num); + writeCounted(num); + + // length and location of CD + writeCounted(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC))); + writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC))); + + // ZIP file comment + ByteBuffer data = this.zipEncoding.encode(comment); + int dataLen = data.limit() - data.position(); + writeCounted(ZipShort.getBytes(dataLen)); + writeCounted(data.array(), data.arrayOffset(), dataLen); + } + + /** + * Convert a Date object to a DOS date/time field. + * + * @param time the Date to convert + * @return the date as a ZipLong + * @deprecated use ZipUtil#toDosTime + */ + @Deprecated + protected static ZipLong toDosTime(Date time) { + return ZipUtil.toDosTime(time); + } + + /** + * Convert a Date object to a DOS date/time field. + * + *

Stolen from InfoZip's fileio.c

+ * + * @param t number of milliseconds since the epoch + * @return the date as a byte array + * @deprecated use ZipUtil#toDosTime + */ + @Deprecated + protected static byte[] toDosTime(long t) { + return ZipUtil.toDosTime(t); + } + + /** + * Retrieve the bytes for the given String in the encoding set for + * this Stream. + * + * @param name the string to get bytes from + * @return the bytes as a byte array + * @throws ZipException on error + */ + protected byte[] getBytes(String name) throws ZipException { + try { + ByteBuffer b = + ZipEncodingHelper.getZipEncoding(encoding).encode(name); + byte[] result = new byte[b.limit()]; + System.arraycopy(b.array(), b.arrayOffset(), result, 0, + result.length); + return result; + } catch (IOException ex) { + throw new ZipException("Failed to encode name: " + ex.getMessage()); + } + } + + /** + * Writes the "ZIP64 End of central dir record" and + * "ZIP64 End of central dir locator". + * + * @throws IOException on error + */ + protected void writeZip64CentralDirectory() throws IOException { + if (zip64Mode == Zip64Mode.Never) { + return; + } + + if (!hasUsedZip64 + && (cdOffset >= ZIP64_MAGIC || cdLength >= ZIP64_MAGIC + || entries.size() >= ZIP64_MAGIC_SHORT)) { + // actually "will use" + hasUsedZip64 = true; + } + + if (!hasUsedZip64) { + return; + } + + long offset = written; + + writeOut(ZIP64_EOCD_SIG); + // size, we don't have any variable length as we don't support + // the extensible data sector, yet + writeOut(ZipEightByteInteger + .getBytes(SHORT /* version made by */ + + SHORT /* version needed to extract */ + + WORD /* disk number */ + + WORD /* disk with central directory */ + + DWORD /* number of entries in CD on this disk */ + + DWORD /* total number of entries */ + + DWORD /* size of CD */ + + DWORD /* offset of CD */ + )); + + // version made by and version needed to extract + writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); + writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); + + // disk numbers - four bytes this time + writeOut(LZERO); + writeOut(LZERO); + + // number of entries + byte[] num = ZipEightByteInteger.getBytes(entries.size()); + writeOut(num); + writeOut(num); + + // length and location of CD + writeOut(ZipEightByteInteger.getBytes(cdLength)); + writeOut(ZipEightByteInteger.getBytes(cdOffset)); + + // no "zip64 extensible data sector" for now + + // and now the "ZIP64 end of central directory locator" + writeOut(ZIP64_EOCD_LOC_SIG); + + // disk number holding the ZIP64 EOCD record + writeOut(LZERO); + // relative offset of ZIP64 EOCD record + writeOut(ZipEightByteInteger.getBytes(offset)); + // total number of disks + writeOut(ONE); + } + + /** + * Write bytes to output or random access file. + * + * @param data the byte array to write + * @throws IOException on error + */ + protected final void writeOut(byte[] data) throws IOException { + writeOut(data, 0, data.length); + } + + /** + * Write bytes to output or random access file. + * + * @param data the byte array to write + * @param offset the start position to write from + * @param length the number of bytes to write + * @throws IOException on error + */ + protected final void writeOut(byte[] data, int offset, int length) + throws IOException { + if (raf != null) { + raf.write(data, offset, length); + } else { + out.write(data, offset, length); + } + } + + /** + * Assumes a negative integer really is a positive integer that + * has wrapped around and re-creates the original value. + * + * @param i the value to treat as unsigned int. + * @return the unsigned int as a long. + * @deprecated use ZipUtil#adjustToLong + */ + @Deprecated + protected static long adjustToLong(int i) { + return ZipUtil.adjustToLong(i); + } + + private void deflateUntilInputIsNeeded() throws IOException { + while (!def.needsInput()) { + deflate(); + } + } + + private GeneralPurposeBit getGeneralPurposeBits(final int zipMethod, final boolean utfFallback) { + GeneralPurposeBit b = new GeneralPurposeBit(); + b.useUTF8ForNames(useUTF8Flag || utfFallback); + if (isDeflatedToOutputStream(zipMethod)) { + b.useDataDescriptor(true); + } + return b; + } + + private int versionNeededToExtract(final int zipMethod, final boolean zip64) { + if (zip64) { + return ZIP64_MIN_VERSION; + } + // requires version 2 as we are going to store length info + // in the data descriptor + return (isDeflatedToOutputStream(zipMethod)) + ? DATA_DESCRIPTOR_MIN_VERSION : INITIAL_VERSION; + } + + private boolean isDeflatedToOutputStream(int zipMethod) { + return zipMethod == DEFLATED && raf == null; + } + + /** + * Get the existing ZIP64 extended information extra field or + * create a new one and add it to the entry. + * + * @param ze ZipEntry + * @return Zip64ExtendedInformationExtraField + */ + private Zip64ExtendedInformationExtraField getZip64Extra(ZipEntry ze) { + if (entry != null) { + entry.causedUseOfZip64 = !hasUsedZip64; + } + hasUsedZip64 = true; + Zip64ExtendedInformationExtraField z64 = + (Zip64ExtendedInformationExtraField) + ze.getExtraField(Zip64ExtendedInformationExtraField + .HEADER_ID); + if (z64 == null) { + /* + System.err.println("Adding z64 for " + ze.getName() + + ", method: " + ze.getMethod() + + " (" + (ze.getMethod() == STORED) + ")" + + ", raf: " + (raf != null)); + */ + z64 = new Zip64ExtendedInformationExtraField(); + } + + // even if the field is there already, make sure it is the first one + ze.addAsFirstExtraField(z64); + + return z64; + } + + /** + * Is there a ZIP64 extended information extra field for the + * entry? + * + * @param ze ZipEntry + * @return boolean + */ + private boolean hasZip64Extra(ZipEntry ze) { + return ze.getExtraField(Zip64ExtendedInformationExtraField + .HEADER_ID) + != null; + } + + /** + * If the mode is AsNeeded and the entry is a compressed entry of + * unknown size that gets written to a non-seekable stream the + * change the default to Never. + * + * @param ze ZipEntry + * @return Zip64Mode + */ + private Zip64Mode getEffectiveZip64Mode(ZipEntry ze) { + if (zip64Mode != Zip64Mode.AsNeeded + || raf != null + || ze.getMethod() != DEFLATED + || ze.getSize() != -1) { + return zip64Mode; + } + return Zip64Mode.Never; + } + + private ZipEncoding getEntryEncoding(ZipEntry ze) { + boolean encodable = zipEncoding.canEncode(ze.getName()); + return !encodable && fallbackToUTF8 + ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; + } + + private ByteBuffer getName(ZipEntry ze) throws IOException { + return getEntryEncoding(ze).encode(ze.getName()); + } + + /** + * Closes the underlying stream/file without finishing the + * archive, the result will likely be a corrupt archive. + * + *

This method only exists to support tests that generate + * corrupt archives so they can clean up any temporary files.

+ * + * @throws IOException if close() fails + */ + void destroy() throws IOException { + if (raf != null) { + raf.close(); + } + if (out != null) { + out.close(); + } + } + + /** + * enum that represents the possible policies for creating Unicode + * extra fields. + */ + public static final class UnicodeExtraFieldPolicy { + /** + * Always create Unicode extra fields. + */ + public static final UnicodeExtraFieldPolicy ALWAYS = + new UnicodeExtraFieldPolicy("always"); + /** + * Never create Unicode extra fields. + */ + public static final UnicodeExtraFieldPolicy NEVER = + new UnicodeExtraFieldPolicy("never"); + /** + * Create Unicode extra fields for filenames that cannot be + * encoded using the specified encoding. + */ + public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = + new UnicodeExtraFieldPolicy("not encodeable"); + + private final String name; + private UnicodeExtraFieldPolicy(String n) { + name = n; + } + @Override + public String toString() { + return name; + } + } + + /** + * Structure collecting information for the entry that is + * currently being written. + */ + private static final class CurrentEntry { + private CurrentEntry(ZipEntry entry) { + this.entry = entry; + } + /** + * Current ZIP entry. + */ + private final ZipEntry entry; + /** + * Offset for CRC entry in the local file header data for the + * current entry starts here. + */ + private long localDataStart = 0; + /** + * Data for local header data + */ + private long dataStart = 0; + /** + * Number of bytes read for the current entry (can't rely on + * Deflater#getBytesRead) when using DEFLATED. + */ + private long bytesRead = 0; + /** + * Whether current entry was the first one using ZIP64 features. + */ + private boolean causedUseOfZip64 = false; + /** + * Whether write() has been called at all. + * + *

In order to create a valid archive {@link + * #closeEntry closeEntry} will write an empty + * array to get the CRC right if nothing has been written to + * the stream at all.

+ */ + private boolean hasWritten; + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipShort.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipShort.java new file mode 100644 index 0000000..f23a353 --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipShort.java @@ -0,0 +1,139 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import static org.xbib.gradle.plugin.shadow.zip.ZipConstants.BYTE_MASK; + +/** + * Utility class that represents a two byte integer with conversion + * rules for the big endian byte order of ZIP files. + * + */ +public final class ZipShort implements Cloneable { + + private static final int BYTE_1_MASK = 0xFF00; + private static final int BYTE_1_SHIFT = 8; + + private final int value; + + /** + * Create instance from a number. + * @param value the int to store as a ZipShort + */ + public ZipShort(int value) { + this.value = value; + } + + /** + * Create instance from bytes. + * @param bytes the bytes to store as a ZipShort + */ + public ZipShort(byte[] bytes) { + this(bytes, 0); + } + + /** + * Create instance from the two bytes starting at offset. + * @param bytes the bytes to store as a ZipShort + * @param offset the offset to start + */ + public ZipShort(byte[] bytes, int offset) { + value = ZipShort.getValue(bytes, offset); + } + + /** + * Get value as two bytes in big endian byte order. + * @return the value as a a two byte array in big endian byte order + */ + public byte[] getBytes() { + byte[] result = new byte[2]; + putShort(value, result, 0); + return result; + } + + /** + * put the value as two bytes in big endian byte order. + * @param value the Java int to convert to bytes + * @param buf the output buffer + * @param offset + * The offset within the output buffer of the first byte to be written. + * must be non-negative and no larger than buf.length-2 + */ + public static void putShort(int value, byte[] buf, int offset) { + buf[offset] = (byte) (value & BYTE_MASK); + buf[offset + 1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); + } + + /** + * Get value as Java int. + * @return value as a Java int + */ + public int getValue() { + return value; + } + + /** + * Get value as two bytes in big endian byte order. + * @param value the Java int to convert to bytes + * @return the converted int as a byte array in big endian byte order + */ + public static byte[] getBytes(int value) { + byte[] result = new byte[2]; + result[0] = (byte) (value & BYTE_MASK); + result[1] = (byte) ((value & BYTE_1_MASK) >> BYTE_1_SHIFT); + return result; + } + + /** + * Helper method to get the value as a java int from two bytes starting at given array offset + * @param bytes the array of bytes + * @param offset the offset to start + * @return the corresponding java int value + */ + public static int getValue(byte[] bytes, int offset) { + int value = (bytes[offset + 1] << BYTE_1_SHIFT) & BYTE_1_MASK; + value += (bytes[offset] & BYTE_MASK); + return value; + } + + /** + * Helper method to get the value as a java int from a two-byte array + * @param bytes the array of bytes + * @return the corresponding java int value + */ + public static int getValue(byte[] bytes) { + return getValue(bytes, 0); + } + + /** + * Override to make two instances with same value equal. + * @param o an object to compare + * @return true if the objects are equal + */ + @Override + public boolean equals(Object o) { + return o instanceof ZipShort && value == ((ZipShort) o).getValue(); + } + + /** + * Override to make two instances with same value equal. + * @return the value stored in the ZipShort + */ + @Override + public int hashCode() { + return value; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException cnfe) { + // impossible + throw new RuntimeException(cnfe); //NOSONAR + } + } + + @Override + public String toString() { + return "ZipShort value: " + value; + } +} diff --git a/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipUtil.java b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipUtil.java new file mode 100644 index 0000000..28cb57b --- /dev/null +++ b/gradle-plugin-shadow/src/main/java/org/xbib/gradle/plugin/shadow/zip/ZipUtil.java @@ -0,0 +1,252 @@ +package org.xbib.gradle.plugin.shadow.zip; + +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.zip.CRC32; + +/** + * Utility class for handling DOS and Java time conversions. + */ +public abstract class ZipUtil { + /** + * Smallest date/time ZIP can handle. + */ + private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L); + + /** + * Convert a Date object to a DOS date/time field. + * + * @param time the Date to convert + * @return the date as a ZipLong + */ + public static ZipLong toDosTime(Date time) { + return new ZipLong(toDosTime(time.getTime())); + } + + /** + * Convert a Date object to a DOS date/time field. + * + *

Stolen from InfoZip's fileio.c

+ * + * @param t number of milliseconds since the epoch + * @return the date as a byte array + */ + public static byte[] toDosTime(long t) { + byte[] result = new byte[4]; + toDosTime(t, result, 0); + return result; + } + + /** + * Convert a Date object to a DOS date/time field. + * + *

Stolen from InfoZip's fileio.c

+ * + * @param t number of milliseconds since the epoch + * @param buf the output buffer + * @param offset + * The offset within the output buffer of the first byte to be written. + * must be non-negative and no larger than buf.length-4 + */ + public static void toDosTime(long t, byte[] buf, int offset) { + toDosTime(Calendar.getInstance(), t, buf, offset); + } + + static void toDosTime(Calendar c, long t, byte[] buf, int offset) { + c.setTimeInMillis(t); + + int year = c.get(Calendar.YEAR); + if (year < 1980) { + System.arraycopy(DOS_TIME_MIN, 0, buf, offset, DOS_TIME_MIN.length); // stop callers from changing the array + return; + } + int month = c.get(Calendar.MONTH) + 1; + long value = ((year - 1980) << 25) + | (month << 21) + | (c.get(Calendar.DAY_OF_MONTH) << 16) + | (c.get(Calendar.HOUR_OF_DAY) << 11) + | (c.get(Calendar.MINUTE) << 5) + | (c.get(Calendar.SECOND) >> 1); + ZipLong.putLong(value, buf, offset); + } + + /** + * Assumes a negative integer really is a positive integer that + * has wrapped around and re-creates the original value. + * + *

This methods is no longer used as of Apache Ant 1.9.0

+ * + * @param i the value to treat as unsigned int. + * @return the unsigned int as a long. + */ + public static long adjustToLong(int i) { + if (i < 0) { + return 2 * ((long) Integer.MAX_VALUE) + 2 + i; + } else { + return i; + } + } + + /** + * Convert a DOS date/time field to a Date object. + * + * @param zipDosTime contains the stored DOS time. + * @return a Date instance corresponding to the given time. + */ + public static Date fromDosTime(ZipLong zipDosTime) { + long dosTime = zipDosTime.getValue(); + return new Date(dosToJavaTime(dosTime)); + } + + /** + * Converts DOS time to Java time (number of milliseconds since + * epoch). + * + * @param dosTime long + * @return long + */ + public static long dosToJavaTime(long dosTime) { + Calendar cal = Calendar.getInstance(); + // CheckStyle:MagicNumberCheck OFF - no point + cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980); + cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1); + cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); + cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); + cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); + cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); + cal.set(Calendar.MILLISECOND, 0); + // CheckStyle:MagicNumberCheck ON + return cal.getTime().getTime(); + } + + /** + * If the entry has Unicode*ExtraFields and the CRCs of the + * names/comments match those of the extra fields, transfer the + * known Unicode values from the extra field. + * + * @param ze ZipEntry + * @param originalNameBytes byte[] + * @param commentBytes byte[] + */ + static void setNameAndCommentFromExtraFields(ZipEntry ze, + byte[] originalNameBytes, + byte[] commentBytes) { + UnicodePathExtraField name = (UnicodePathExtraField) + ze.getExtraField(UnicodePathExtraField.UPATH_ID); + String originalName = ze.getName(); + String newName = getUnicodeStringIfOriginalMatches(name, + originalNameBytes); + if (newName != null && !originalName.equals(newName)) { + ze.setName(newName); + } + + if (commentBytes != null && commentBytes.length > 0) { + UnicodeCommentExtraField cmt = (UnicodeCommentExtraField) + ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); + String newComment = + getUnicodeStringIfOriginalMatches(cmt, commentBytes); + if (newComment != null) { + ze.setComment(newComment); + } + } + } + + /** + * If the stored CRC matches the one of the given name, return the + * Unicode name of the given field. + * + *

If the field is null or the CRCs don't match, return null + * instead.

+ * + * @param f AbstractUnicodeExtraField + * @param orig byte[] + */ + private static + String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f, + byte[] orig) { + if (f != null) { + CRC32 crc32 = new CRC32(); + crc32.update(orig); + long origCRC32 = crc32.getValue(); + + if (origCRC32 == f.getNameCRC32()) { + try { + return ZipEncodingHelper + .UTF8_ZIP_ENCODING.decode(f.getUnicodeName()); + } catch (IOException ex) { + // UTF-8 unsupported? should be impossible the + // Unicode*ExtraField must contain some bad bytes + + // TODO log this anywhere? + return null; + } + } + } + return null; + } + + /** + * Create a copy of the given array - or return null if the + * argument is null. + * + * @param from byte[] + * @return byte[] + */ + static byte[] copy(byte[] from) { + if (from != null) { + byte[] to = new byte[from.length]; + System.arraycopy(from, 0, to, 0, to.length); + return to; + } + return null; + } + + /** + * Whether this library is able to read or write the given entry. + * + * @return boolean + */ + static boolean canHandleEntryData(ZipEntry entry) { + return supportsEncryptionOf(entry) && supportsMethodOf(entry); + } + + /** + * Whether this library supports the encryption used by the given + * entry. + * + * @return true if the entry isn't encrypted at all + */ + private static boolean supportsEncryptionOf(ZipEntry entry) { + return !entry.getGeneralPurposeBit().usesEncryption(); + } + + /** + * Whether this library supports the compression method used by + * the given entry. + * + * @return true if the compression method is STORED or DEFLATED + */ + private static boolean supportsMethodOf(ZipEntry entry) { + return entry.getMethod() == ZipEntry.STORED + || entry.getMethod() == ZipEntry.DEFLATED; + } + + /** + * Checks whether the entry requires features not (yet) supported + * by the library and throws an exception if it does. + */ + static void checkRequestedFeatures(ZipEntry ze) + throws UnsupportedZipFeatureException { + if (!supportsEncryptionOf(ze)) { + throw + new UnsupportedZipFeatureException(UnsupportedZipFeatureException + .Feature.ENCRYPTION, ze); + } + if (!supportsMethodOf(ze)) { + throw + new UnsupportedZipFeatureException(UnsupportedZipFeatureException + .Feature.METHOD, ze); + } + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/ConfigureShadowPluginSpec.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/ConfigureShadowPluginSpec.groovy new file mode 100644 index 0000000..3c640b3 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/ConfigureShadowPluginSpec.groovy @@ -0,0 +1,43 @@ +package org.xbib.gradle.plugin.shadow + +import org.xbib.gradle.plugin.shadow.util.PluginSpecification + +class ConfigureShadowPluginSpec extends PluginSpecification { + + def "auto relocate plugin dependencies"() { + given: + buildFile << """ + task relocateShadowJar(type: org.xbib.gradle.plugin.shadow.tasks.ConfigureShadowRelocation) { + target = tasks.shadowJar + } + tasks.shadowJar.dependsOn tasks.relocateShadowJar + + dependencies { + implementation 'junit:junit:3.8.2' + } + """.stripIndent() + + when: + runner.withArguments('shadowJar', '-s').build() + + then: + contains(output, [ + 'META-INF/MANIFEST.MF', + 'shadow/junit/textui/ResultPrinter.class', + 'shadow/junit/textui/TestRunner.class', + 'shadow/junit/framework/Assert.class', + 'shadow/junit/framework/AssertionFailedError.class', + 'shadow/junit/framework/ComparisonCompactor.class', + 'shadow/junit/framework/ComparisonFailure.class', + 'shadow/junit/framework/Protectable.class', + 'shadow/junit/framework/Test.class', + 'shadow/junit/framework/TestCase.class', + 'shadow/junit/framework/TestFailure.class', + 'shadow/junit/framework/TestListener.class', + 'shadow/junit/framework/TestResult$1.class', + 'shadow/junit/framework/TestResult.class', + 'shadow/junit/framework/TestSuite$1.class', + 'shadow/junit/framework/TestSuite.class' + ]) + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/FilteringSpec.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/FilteringSpec.groovy new file mode 100644 index 0000000..405bfde --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/FilteringSpec.groovy @@ -0,0 +1,378 @@ +package org.xbib.gradle.plugin.shadow + +import org.xbib.gradle.plugin.shadow.util.PluginSpecification +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.TaskOutcome +import spock.lang.Ignore +import spock.lang.IgnoreRest +import spock.lang.Issue + +class FilteringSpec extends PluginSpecification { + + def setup() { + repo.module('shadow', 'a', '1.0') + .insertFile('a.properties', 'a') + .insertFile('a2.properties', 'a2') + .publish() + repo.module('shadow', 'b', '1.0') + .insertFile('b.properties', 'b') + .publish() + + buildFile << """ + dependencies { + implementation 'shadow:a:1.0' + implementation 'shadow:b:1.0' + } + """.stripIndent() + + } + + def 'include all dependencies'() { + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties', 'a2.properties', 'b.properties']) + } + + def 'exclude files'() { + given: + buildFile << """ + // tag::excludeFile[] + shadowJar { + exclude 'a2.properties' + } + // end::excludeFile[] + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties', 'b.properties']) + + and: + doesNotContain(output, ['a2.properties']) + } + + def "exclude dependency"() { + given: + repo.module('shadow', 'c', '1.0') + .insertFile('c.properties', 'c') + .publish() + repo.module('shadow', 'd', '1.0') + .insertFile('d.properties', 'd') + .dependsOn('c') + .publish() + + buildFile << ''' + // tag::excludeDep[] + dependencies { + implementation 'shadow:d:1.0' + } + + shadowJar { + dependencies { + exclude(dependency('shadow:d:1.0')) + } + } + // end::excludeDep[] + '''.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) + + and: + doesNotContain(output, ['d.properties']) + } + + @Issue('SHADOW-83') + def "exclude dependency using wildcard syntax"() { + given: + repo.module('shadow', 'c', '1.0') + .insertFile('c.properties', 'c') + .publish() + repo.module('shadow', 'd', '1.0') + .insertFile('d.properties', 'd') + .dependsOn('c') + .publish() + + buildFile << ''' + // tag::excludeDepWildcard[] + dependencies { + implementation 'shadow:d:1.0' + } + + shadowJar { + dependencies { + exclude(dependency('shadow:d:.*')) + } + } + // end::excludeDepWildcard[] + '''.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) + + and: + doesNotContain(output, ['d.properties']) + } + + @Issue("SHADOW-54") + def "dependency exclusions affect UP-TO-DATE check"() { + given: + repo.module('shadow', 'c', '1.0') + .insertFile('c.properties', 'c') + .publish() + repo.module('shadow', 'd', '1.0') + .insertFile('d.properties', 'd') + .dependsOn('c') + .publish() + + buildFile << ''' + dependencies { + implementation 'shadow:d:1.0' + } + + shadowJar { + dependencies { + exclude(dependency('shadow:d:1.0')) + } + } + '''.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) + + and: + doesNotContain(output, ['d.properties']) + + when: 'Update build file shadowJar dependency exclusion' + buildFile.text = buildFile.text.replace('exclude(dependency(\'shadow:d:1.0\'))', + 'exclude(dependency(\'shadow:c:1.0\'))') + + BuildResult result = runner.withArguments('shadowJar').build() + + then: + assert result.task(':shadowJar').outcome == TaskOutcome.SUCCESS + + and: + contains(output, ['a.properties', 'a2.properties', 'b.properties', 'd.properties']) + + and: + doesNotContain(output, ['c.properties']) + } + + def "include dependency, excluding all others"() { + given: + repo.module('shadow', 'c', '1.0') + .insertFile('c.properties', 'c') + .publish() + repo.module('shadow', 'd', '1.0') + .insertFile('d.properties', 'd') + .dependsOn('c') + .publish() + + file('src/main/java/shadow/Passed.java') << ''' + package shadow; + public class Passed {} + '''.stripIndent() + + buildFile << ''' + dependencies { + implementation 'shadow:d:1.0' + } + + shadowJar { + dependencies { + include(dependency('shadow:d:1.0')) + } + } + '''.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['d.properties', 'shadow/Passed.class']) + + and: + doesNotContain(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) + } + + def 'filter project dependencies'() { + given: + buildFile.text = '' + + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client {} + """.stripIndent() + + file('client/build.gradle') << """ + ${defaultBuildScript} + dependencies { implementation 'junit:junit:3.8.2' } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + import client.Client; + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + ${defaultBuildScript} + + // tag::excludeProject[] + dependencies { + implementation project(':client') + } + + shadowJar { + dependencies { + exclude(project(':client')) + } + } + // end::excludeProject[] + """.stripIndent() + + File serverOutput = file('server/build/libs/server-1.0-all.jar') + + when: + runner.withArguments(':server:shadowJar', '--stacktrace').build() + + then: + doesNotContain(serverOutput, [ + 'client/Client.class', + ]) + + and: + contains(serverOutput, ['server/Server.class', 'junit/framework/Test.class']) + } + + def 'exclude a transitive project dependency'() { + given: + buildFile.text = '' + + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client {} + """.stripIndent() + + file('client/build.gradle') << """ + ${defaultBuildScript} + dependencies { implementation 'junit:junit:3.8.2' } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + import client.Client; + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + ${defaultBuildScript} + dependencies { implementation project(':client') } + + // tag::excludeSpec[] + shadowJar { + dependencies { + exclude(dependency { + it.moduleGroup == 'junit' + }) + } + } + // end::excludeSpec[] + """.stripIndent() + + File serverOutput = file('server/build/libs/server-1.0-all.jar') + + when: + runner.withArguments(':server:shadowJar','--stacktrace').build() + + then: + doesNotContain(serverOutput, [ + 'junit/framework/Test.class' + ]) + + and: + contains(serverOutput, [ + 'client/Client.class', + 'server/Server.class']) + } + + //http://mail-archives.apache.org/mod_mbox/ant-user/200506.mbox/%3C001d01c57756$6dc35da0$dc00a8c0@CTEGDOMAIN.COM%3E + def 'verify exclude precedence over include'() { + given: + buildFile << """ + // tag::excludeOverInclude[] + shadowJar { + include '*.jar' + include '*.properties' + exclude 'a2.properties' + } + // end::excludeOverInclude[] + """.stripIndent() + + when: + runner.withArguments('shadowJar','--stacktrace').build() + + then: + contains(output, ['a.properties', 'b.properties']) + + and: + doesNotContain(output, ['a2.properties']) + } + + @Issue("SHADOW-69") + def "handle exclude with circular dependency"() { + given: + repo.module('shadow', 'c', '1.0') + .insertFile('c.properties', 'c') + .dependsOn('d') + .publish() + repo.module('shadow', 'd', '1.0') + .insertFile('d.properties', 'd') + .dependsOn('c') + .publish() + + buildFile << ''' + dependencies { + implementation 'shadow:d:1.0' + } + + shadowJar { + dependencies { + exclude(dependency('shadow:d:1.0')) + } + } + '''.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties', 'a2.properties', 'b.properties', 'c.properties']) + + and: + doesNotContain(output, ['d.properties']) + } + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/PublishingSpec.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/PublishingSpec.groovy new file mode 100644 index 0000000..89ef9d1 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/PublishingSpec.groovy @@ -0,0 +1,78 @@ +package org.xbib.gradle.plugin.shadow + +import org.xbib.gradle.plugin.shadow.util.AppendableMavenFileRepository +import org.xbib.gradle.plugin.shadow.util.PluginSpecification + +class PublishingSpec extends PluginSpecification { + + AppendableMavenFileRepository repo + AppendableMavenFileRepository publishingRepo + + def setup() { + repo = repo() + publishingRepo = repo('remote_repo') + } + + def "publish shadow jar with maven-publish plugin"() { + given: + repo.module('shadow', 'a', '1.0') + .insertFile('a.properties', 'a') + .insertFile('a2.properties', 'a2') + .publish() + repo.module('shadow', 'b', '1.0') + .insertFile('b.properties', 'b') + .publish() + + settingsFile << "rootProject.name = 'maven'" + buildFile << """ + apply plugin: 'maven-publish' + + dependencies { + implementation 'shadow:a:1.0' + shadow 'shadow:b:1.0' + } + + shadowJar { + classifier = '' + baseName = 'maven-all' + } + + publishing { + publications { + shadow(MavenPublication) { publication -> + project.shadow.component(publication) + artifactId = 'maven-all' + } + } + repositories { + maven { + url "${publishingRepo.uri}" + } + } + } + """.stripIndent() + + when: + runner.withArguments('publish').build() + + then: + File publishedFile = publishingRepo.rootDir.file('shadow/maven-all/1.0/maven-all-1.0.jar').canonicalFile + assert publishedFile.exists() + + and: + contains(publishedFile, ['a.properties', 'a2.properties']) + + and: + File pom = publishingRepo.rootDir.file('shadow/maven-all/1.0/maven-all-1.0.pom').canonicalFile + assert pom.exists() + + def contents = new XmlSlurper().parse(pom) + assert contents.dependencies.size() == 1 + assert contents.dependencies[0].dependency.size() == 1 + + def dependency = contents.dependencies[0].dependency[0] + assert dependency.groupId.text() == 'shadow' + assert dependency.artifactId.text() == 'b' + assert dependency.version.text() == '1.0' + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/RelocationSpec.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/RelocationSpec.groovy new file mode 100644 index 0000000..5717c5d --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/RelocationSpec.groovy @@ -0,0 +1,323 @@ +package org.xbib.gradle.plugin.shadow + +import org.xbib.gradle.plugin.shadow.util.PluginSpecification +import spock.lang.Issue + +import java.util.jar.Attributes +import java.util.jar.JarFile + +class RelocationSpec extends PluginSpecification { + + @Issue('SHADOW-58') + def "relocate dependency files"() { + given: + buildFile << """ + dependencies { + implementation 'junit:junit:3.8.2' + } + + shadowJar { + relocate 'junit.textui', 'a' + relocate 'junit.framework', 'b' + manifest { + attributes 'TEST-VALUE': 'FOO' + } + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, [ + 'META-INF/MANIFEST.MF', + 'a/ResultPrinter.class', + 'a/TestRunner.class', + 'b/Assert.class', + 'b/AssertionFailedError.class', + 'b/ComparisonCompactor.class', + 'b/ComparisonFailure.class', + 'b/Protectable.class', + 'b/Test.class', + 'b/TestCase.class', + 'b/TestFailure.class', + 'b/TestListener.class', + 'b/TestResult$1.class', + 'b/TestResult.class', + 'b/TestSuite$1.class', + 'b/TestSuite.class' + ]) + + and: + doesNotContain(output, [ + 'junit/textui/ResultPrinter.class', + 'junit/textui/TestRunner.class', + 'junit/framework/Assert.class', + 'junit/framework/AssertionFailedError.class', + 'junit/framework/ComparisonCompactor.class', + 'junit/framework/ComparisonFailure.class', + 'junit/framework/Protectable.class', + 'junit/framework/Test.class', + 'junit/framework/TestCase.class', + 'junit/framework/TestFailure.class', + 'junit/framework/TestListener.class', + 'junit/framework/TestResult$1.class', + 'junit/framework/TestResult.class', + 'junit/framework/TestSuite$1.class', + 'junit/framework/TestSuite.class' + ]) + + and: 'Test that manifest file exists with contents' + JarFile jar = new JarFile(output) + Attributes attributes = jar.manifest.getMainAttributes() + String val = attributes.getValue('TEST-VALUE') + assert val == 'FOO' + } + + def "relocate dependency files with filtering"() { + given: + buildFile << """ + dependencies { + implementation 'junit:junit:3.8.2' + } + + // tag::relocateFilter[] + shadowJar { + relocate('junit.textui', 'a') { + exclude 'junit.textui.TestRunner' + } + relocate('junit.framework', 'b') { + include 'junit.framework.Test*' + } + } + // end::relocateFilter[] + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, [ + 'a/ResultPrinter.class', + 'b/Test.class', + 'b/TestCase.class', + 'b/TestFailure.class', + 'b/TestListener.class', + 'b/TestResult$1.class', + 'b/TestResult.class', + 'b/TestSuite$1.class', + 'b/TestSuite.class' + ]) + + and: + doesNotContain(output, [ + 'a/TestRunner.class', + 'b/Assert.class', + 'b/AssertionFailedError.class', + 'b/ComparisonCompactor.class', + 'b/ComparisonFailure.class', + 'b/Protectable.class' + ]) + + and: + contains(output, [ + 'junit/textui/TestRunner.class', + 'junit/framework/Assert.class', + 'junit/framework/AssertionFailedError.class', + 'junit/framework/ComparisonCompactor.class', + 'junit/framework/ComparisonFailure.class', + 'junit/framework/Protectable.class' + ]) + } + + @Issue(['SHADOW-55', 'SHADOW-53']) + def "remap class names for relocated files in project source"() { + given: + buildFile << """ + dependencies { + implementation 'junit:junit:3.8.2' + } + + // tag::relocate[] + shadowJar { + relocate 'junit.framework', 'shadow.junit' + } + // end::relocate[] + """.stripIndent() + + file('src/main/java/shadow/ShadowTest.java') << ''' + package shadow; + + import junit.framework.Test; + import junit.framework.TestResult; + public class ShadowTest implements Test { + public int countTestCases() { return 0; } + public void run(TestResult result) { } + } + '''.stripIndent() + + when: + runner.withArguments('shadowJar', '--stacktrace').build() + + then: + contains(output, [ + 'shadow/ShadowTest.class', + 'shadow/junit/Test.class', + 'shadow/junit' + ]) + + and: + doesNotContain(output, [ + 'junit/framework', + 'junit/framework/Test.class' + ]) + + and: 'check that the class can be loaded. If the file was not relocated properly, we should get a NoDefClassFound' + // Isolated class loader with only the JVM system jars and the output jar from the test project + URLClassLoader classLoader = new URLClassLoader([output.toURI().toURL()] as URL[], + ClassLoader.systemClassLoader.parent) + classLoader.loadClass('shadow.ShadowTest') + } + + @Issue('SHADOW-61') + def "relocate does not drop dependency resources"() { + given: 'Core project with dependency and resource' + file('core/build.gradle') << """ + apply plugin: 'java-library' + + repositories { maven { url "${repo.uri}" } } + dependencies { api 'junit:junit:3.8.2' } + """.stripIndent() + + file('core/src/main/resources/TEST') << 'TEST RESOURCE' + file('core/src/main/resources/test.properties') << 'name=test' + file('core/src/main/java/core/Core.java') << ''' + package core; + + import junit.framework.Test; + + public class Core {} + '''.stripIndent() + + and: 'App project with shadow, relocation, and project dependency' + file('app/build.gradle') << """ + apply plugin: 'java-library' + apply plugin: 'org.xbib.gradle.plugin.shadow' + + repositories { maven { url "${repo.uri}" } } + dependencies { api project(':core') } + + shadowJar { + relocate 'core', 'app.core' + relocate 'junit.framework', 'app.junit.framework' + } + """.stripIndent() + + file('app/src/main/resources/APP-TEST') << 'APP TEST RESOURCE' + file('app/src/main/java/app/App.java') << ''' + package app; + + import core.Core; + import junit.framework.Test; + + public class App {} + '''.stripIndent() + + and: 'Configure multi-project build' + settingsFile << ''' + include 'core', 'app' + '''.stripIndent() + + when: + runner.withArguments(':app:shadowJar', '--stacktrace').build() + + then: + File appOutput = file('app/build/libs/app-all.jar') + assert appOutput.exists() + + and: + contains(appOutput, [ + 'TEST', + 'APP-TEST', + 'test.properties', + 'app/core/Core.class', + 'app/App.class', + 'app/junit/framework/Test.class' + ]) + } + + @Issue(['SHADOW-93', 'SHADOW-114']) + def "relocate resource files"() { + given: + repo.module('shadow', 'dep', '1.0') + .insertFile('foo/dep.properties', 'c') + .publish() + file('src/main/java/foo/Foo.java') << ''' + package foo; + + class Foo {} + '''.stripIndent() + file('src/main/resources/foo/foo.properties') << 'name=foo' + + buildFile << """ + dependencies { + implementation 'shadow:dep:1.0' + } + + shadowJar { + relocate 'foo', 'bar' + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, [ + 'bar/Foo.class', + 'bar/foo.properties', + 'bar/dep.properties' + ]) + + and: + doesNotContain(output, [ + 'foo/Foo.class', + 'foo/foo.properties', + 'foo/dep.properties' + ]) + } + + @Issue("SHADOW-294") + def "does not error on relocating java9 classes"() { + given: + buildFile << """ + repositories { + jcenter() + maven { + url 'https://repository.mapr.com/nexus/content/groups/mapr-public' + } + } + + dependencies { + implementation 'org.slf4j:slf4j-api:1.7.21' + implementation group: 'io.netty', name: 'netty-all', version: '4.0.23.Final' + implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '2.5.0' + implementation group: 'org.apache.zookeeper', name: 'zookeeper', version: '3.4.6' + implementation group: 'org.hbase', name: 'asynchbase', version: '1.7.0-mapr-1603' + } + + shadowJar { + zip64 true + relocate 'com.google.protobuf', 'shaded.com.google.protobuf' + relocate 'io.netty', 'shaded.io.netty' + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + noExceptionThrown() + + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/ShadowPluginSpec.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/ShadowPluginSpec.groovy new file mode 100644 index 0000000..b1236b5 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/ShadowPluginSpec.groovy @@ -0,0 +1,626 @@ +package org.xbib.gradle.plugin.shadow + +import org.gradle.util.GradleVersion +import org.xbib.gradle.plugin.shadow.tasks.ShadowJar +import org.xbib.gradle.plugin.shadow.util.PluginSpecification +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.plugins.JavaPlugin +import org.gradle.testfixtures.ProjectBuilder +import org.gradle.testkit.runner.GradleRunner +import spock.lang.Issue + +import java.util.jar.Attributes +import java.util.jar.JarFile + +class ShadowPluginSpec extends PluginSpecification { + + def 'apply plugin'() { + given: + String projectName = 'myshadow' + String version = '1.0.0' + + Project project = ProjectBuilder.builder().withName(projectName).build() + project.version = version + + when: + project.plugins.apply(ShadowPlugin) + + then: + project.plugins.hasPlugin(ShadowPlugin) + + and: + assert !project.tasks.findByName('shadowJar') + + when: + project.plugins.apply(JavaPlugin) + + then: + ShadowJar shadow = project.tasks.findByName('shadowJar') as ShadowJar + assert shadow + assert shadow.getProperty('baseName') == projectName + assert shadow.getProperty('destinationDir') == new File(project.buildDir, 'libs') + assert shadow.getProperty('version') == version + assert shadow.getProperty('classifier') == 'all' + assert shadow.getProperty('extension') == 'jar' + + and: + Configuration shadowConfig = project.configurations.findByName('shadow') + assert shadowConfig + shadowConfig.artifacts.files.contains(shadow.archiveFile.get().getAsFile()) + + } + + def 'shadow copy'() { + given: + URL artifact = this.class.classLoader.getResource('test-artifact-1.0-SNAPSHOT.jar') + URL project = this.class.classLoader.getResource('test-project-1.0-SNAPSHOT.jar') + + buildFile << """ + shadowJar { + from('${artifact.path}') + from('${project.path}') + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + } + + def 'include project sources'() { + given: + file('src/main/java/shadow/Passed.java') << ''' + package shadow; + public class Passed {} + '''.stripIndent() + + buildFile << """ + dependencies { implementation 'junit:junit:3.8.2' } + + // tag::rename[] + shadowJar { + baseName = 'shadow' + classifier = null + version = null + } + // end::rename[] + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output("shadow.jar"), ['shadow/Passed.class', 'junit/framework/Test.class']) + + and: + doesNotContain(output("shadow.jar"), ['/']) + } + + def 'include project dependencies'() { + given: + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client {} + """.stripIndent() + + file('client/build.gradle') << """ + apply plugin: 'java' + repositories { maven { url "${repo.uri}" } } + dependencies { implementation 'junit:junit:3.8.2' } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + + import client.Client; + + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + apply plugin: 'java' + apply plugin: 'org.xbib.gradle.plugin.shadow' + + repositories { maven { url "${repo.uri}" } } + dependencies { implementation project(':client') } + + """.stripIndent() + + File serverOutput = file('server/build/libs/server-all.jar') + + when: + runner.withArguments(':server:shadowJar').build() + + then: + contains(serverOutput, [ + 'client/Client.class', + 'server/Server.class', + 'junit/framework/Test.class' + ]) + } + + /** + * 'Server' depends on 'Client'. 'junit' is independent. + * The minimize shall remove 'junit'. + */ + def 'minimize by keeping only transitive dependencies'() { + given: + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client {} + """.stripIndent() + + file('client/build.gradle') << """ + apply plugin: 'java' + repositories { maven { url "${repo.uri}" } } + dependencies { implementation 'junit:junit:3.8.2' } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + + import client.Client; + + public class Server { + private final String client = Client.class.getName(); + } + """.stripIndent() + + file('server/build.gradle') << """ + apply plugin: 'java-library' + apply plugin: 'org.xbib.gradle.plugin.shadow' + + shadowJar { + minimize() + } + + tasks.withType(org.xbib.gradle.plugin.shadow.tasks.ShadowJar) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } + tasks.withType(Jar) { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } + + repositories { maven { url "${repo.uri}" } } + dependencies { implementation project(':client') } + """.stripIndent() + + File serverOutput = file('server/build/libs/server-all.jar') + + when: + runner.withArguments(':server:shadowJar', '--stacktrace').withDebug(true).build() + + then: + contains(serverOutput, [ + 'client/Client.class', + 'server/Server.class' + ]) + doesNotContain(serverOutput, ['junit/framework/Test.class']) + } + + /** + * 'Client', 'Server' and 'junit' are independent. + * 'junit' is excluded from the minimize. + * The minimize shall remove 'Client' but not 'junit'. + */ + def 'exclude a dependency from minimize'() { + given: + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client {} + """.stripIndent() + + file('client/build.gradle') << """ + apply plugin: 'java' + repositories { maven { url "${repo.uri}" } } + dependencies { implementation 'junit:junit:3.8.2' } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + apply plugin: 'java' + apply plugin: 'org.xbib.gradle.plugin.shadow' + + shadowJar { + minimize { + exclude(dependency('junit:junit:.*')) + } + } + + repositories { maven { url "${repo.uri}" } } + dependencies { implementation project(':client') } + """.stripIndent() + + File serverOutput = file('server/build/libs/server-all.jar') + + when: + runner.withArguments(':server:shadowJar', '--stacktrace').withDebug(true).build() + + then: + contains(serverOutput, [ + 'server/Server.class', + 'junit/framework/Test.class' + ]) + doesNotContain(serverOutput, ['client/Client.class']) + } + + def 'depend on project shadow jar'() { + given: + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client {} + """.stripIndent() + + file('client/build.gradle') << """ + apply plugin: 'java' + apply plugin: 'org.xbib.gradle.plugin.shadow' + repositories { maven { url "${repo.uri}" } } + dependencies { implementation 'junit:junit:3.8.2' } + + shadowJar { + relocate 'junit.framework', 'client.junit.framework' + } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + + import client.Client; + import client.junit.framework.Test; + + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + apply plugin: 'java' + + repositories { maven { url "${repo.uri}" } } + dependencies { implementation project(path: ':client', configuration: 'shadow') } + """.stripIndent() + + File serverOutput = file('server/build/libs/server.jar') + + when: + runner.withArguments(':server:jar').build() + + then: + contains(serverOutput, [ + 'server/Server.class' + ]) + + and: + doesNotContain(serverOutput, [ + 'client/Client.class', + 'junit/framework/Test.class', + 'client/junit/framework/Test.class' + ]) + } + + def 'shadow a project shadow jar'() { + given: + file('settings.gradle') << """ + include 'client', 'server' + """.stripIndent() + + file('client/src/main/java/client/Client.java') << """ + package client; + public class Client {} + """.stripIndent() + + file('client/build.gradle') << """ + apply plugin: 'java' + apply plugin: 'org.xbib.gradle.plugin.shadow' + repositories { maven { url "${repo.uri}" } } + dependencies { implementation 'junit:junit:3.8.2' } + + shadowJar { + relocate 'junit.framework', 'client.junit.framework' + } + """.stripIndent() + + file('server/src/main/java/server/Server.java') << """ + package server; + + import client.Client; + import client.junit.framework.Test; + + public class Server {} + """.stripIndent() + + file('server/build.gradle') << """ + apply plugin: 'java' + apply plugin: 'org.xbib.gradle.plugin.shadow' + + repositories { maven { url "${repo.uri}" } } + dependencies { implementation project(path: ':client', configuration: 'shadow') } + """.stripIndent() + + File serverOutput = file('server/build/libs/server-all.jar') + + when: + runner.withArguments(':server:shadowJar').build() + + then: + contains(serverOutput, [ + 'client/Client.class', + 'client/junit/framework/Test.class', + 'server/Server.class', + ]) + + and: + doesNotContain(serverOutput, [ + 'junit/framework/Test.class' + ]) + } + + def "exclude INDEX.LIST, *.SF, *.DSA, and *.RSA by default"() { + given: + repo.module('shadow', 'a', '1.0') + .insertFile('a.properties', 'a') + .insertFile('META-INF/INDEX.LIST', 'JarIndex-Version: 1.0') + .insertFile('META-INF/a.SF', 'Signature File') + .insertFile('META-INF/a.DSA', 'DSA Signature Block') + .insertFile('META-INF/a.RSA', 'RSA Signature Block') + .insertFile('META-INF/a.properties', 'key=value') + .publish() + + file('src/main/java/shadow/Passed.java') << ''' + package shadow; + public class Passed {} + '''.stripIndent() + + buildFile << """ + dependencies { implementation 'shadow:a:1.0' } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties', 'META-INF/a.properties']) + + and: + doesNotContain(output, ['META-INF/INDEX.LIST', 'META-INF/a.SF', 'META-INF/a.DSA', 'META-INF/a.RSA']) + } + + def "include runtimeOnly configuration by default"() { + given: + repo.module('shadow', 'a', '1.0') + .insertFile('a.properties', 'a') + .publish() + + repo.module('shadow', 'b', '1.0') + .insertFile('b.properties', 'b') + .publish() + + buildFile << """ + dependencies { + runtimeOnly 'shadow:a:1.0' + shadow 'shadow:b:1.0' + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties']) + + and: + doesNotContain(output, ['b.properties']) + } + + // https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_known_issues + def "include java-library configurations by default"() { + given: + GradleRunner versionRunner = runner + .withGradleVersion(GradleVersion.current().version) + .withArguments('--stacktrace') + .withDebug(true) + + repo.module('shadow', 'api', '1.0') + .insertFile('api.properties', 'api') + .publish() + + repo.module('shadow', 'implementation-dep', '1.0') + .insertFile('implementation-dep.properties', 'implementation-dep') + .publish() + + repo.module('shadow', 'implementation', '1.0') + .insertFile('implementation.properties', 'implementation') + .dependsOn('implementation-dep') + .publish() + + repo.module('shadow', 'runtimeOnly', '1.0') + .insertFile('runtimeOnly.properties', 'runtimeOnly') + .publish() + + buildFile.text = defaultBuildScript.replace('java', 'java-library') + buildFile << """ + dependencies { + api 'shadow:api:1.0' + implementation 'shadow:implementation:1.0' + runtimeOnly 'shadow:runtimeOnly:1.0' + } + """.stripIndent() + + when: + versionRunner.withArguments('shadowJar').build() + + then: + contains(output, ['api.properties', + 'implementation.properties', + 'runtimeOnly.properties', + 'implementation-dep.properties']) + } + + def "doesn't include compileOnly configuration by default"() { + given: + repo.module('shadow', 'a', '1.0') + .insertFile('a.properties', 'a') + .publish() + + repo.module('shadow', 'b', '1.0') + .insertFile('b.properties', 'b') + .publish() + + buildFile << """ + dependencies { + runtimeOnly 'shadow:a:1.0' + compileOnly 'shadow:b:1.0' + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + contains(output, ['a.properties']) + + and: + doesNotContain(output, ['b.properties']) + } + + def "default copying strategy"() { + given: + repo.module('shadow', 'a', '1.0') + .insertFile('META-INF/MANIFEST.MF', 'MANIFEST A') + .publish() + + repo.module('shadow', 'b', '1.0') + .insertFile('META-INF/MANIFEST.MF', 'MANIFEST B') + .publish() + + buildFile << """ + dependencies { + runtimeOnly 'shadow:a:1.0' + runtimeOnly 'shadow:b:1.0' + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + JarFile jar = new JarFile(output) + assert jar.entries().collect().size() == 2 + } + + def "Class-Path in Manifest not added if empty"() { + given: + + buildFile << """ + dependencies { implementation 'junit:junit:3.8.2' } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + JarFile jar = new JarFile(output) + Attributes attributes = jar.manifest.getMainAttributes() + assert attributes.getValue('Class-Path') == null + } + + @Issue('SHADOW-65') + def "add shadow configuration to Class-Path in Manifest"() { + given: + + buildFile << """ + // tag::shadowConfig[] + dependencies { + shadow 'junit:junit:3.8.2' + } + // end::shadowConfig[] + + // tag::jarManifest[] + jar { + manifest { + attributes 'Class-Path': '/libs/a.jar' + } + } + // end::jarManifest[] + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: 'SHADOW-65 - combine w/ existing Class-Path' + JarFile jar = new JarFile(output) + Attributes attributes = jar.manifest.getMainAttributes() + String classpath = attributes.getValue('Class-Path') + assert classpath == '/libs/a.jar junit-3.8.2.jar' + + } + + @Issue('SHADOW-92') + def "do not include null value in Class-Path when jar file does not contain Class-Path"() { + given: + + buildFile << """ + dependencies { shadow 'junit:junit:3.8.2' } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + JarFile jar = new JarFile(output) + Attributes attributes = jar.manifest.getMainAttributes() + String classpath = attributes.getValue('Class-Path') + assert classpath == 'junit-3.8.2.jar' + } + + @Issue('SHADOW-203') + def "support ZipCompression.STORED"() { + given: + + buildFile << """ + dependencies { shadow 'junit:junit:3.8.2' } + + shadowJar { + zip64 true + entryCompression = org.gradle.api.tasks.bundling.ZipEntryCompression.STORED + } + """.stripIndent() + + when: + runner.withArguments('shadowJar', '--stacktrace').build() + + then: + assert output.exists() + + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/TransformerSpec.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/TransformerSpec.groovy new file mode 100644 index 0000000..7c206d3 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/TransformerSpec.groovy @@ -0,0 +1,636 @@ +package org.xbib.gradle.plugin.shadow + +import org.xbib.gradle.plugin.shadow.transformers.AppendingTransformer +import org.xbib.gradle.plugin.shadow.transformers.GroovyExtensionModuleTransformer +import org.xbib.gradle.plugin.shadow.transformers.ServiceFileTransformer +import org.xbib.gradle.plugin.shadow.util.PluginSpecification +import spock.lang.Issue + +import java.util.jar.JarInputStream +import java.util.jar.Manifest + +class TransformerSpec extends PluginSpecification { + + def 'service resource transformer'() { + given: + File one = buildJar('one.jar') + .insertFile('META-INF/services/org.apache.maven.Shade', + 'one # NOTE: No newline terminates this line/file') + .insertFile('META-INF/services/com.acme.Foo', 'one') + .write() + + File two = buildJar('two.jar') + .insertFile('META-INF/services/org.apache.maven.Shade', + 'two # NOTE: No newline terminates this line/file') + .insertFile('META-INF/services/com.acme.Foo', 'two') + .write() + + buildFile << """ + import ${ServiceFileTransformer.name} + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + shadowJar { + transform(ServiceFileTransformer) { + exclude 'META-INF/services/com.acme.*' + } + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + String text1 = getJarFileContents(output, 'META-INF/services/org.apache.maven.Shade') + assert text1.split("\\r?\\n").size() == 2 + assert text1 == +'''one # NOTE: No newline terminates this line/file +two # NOTE: No newline terminates this line/file'''.stripIndent() + + and: + String text2 = getJarFileContents(output, 'META-INF/services/com.acme.Foo') + assert text2.split("\\r?\\n").size() == 1 + assert text2 == 'one' + } + + def 'service resource transformer alternate path'() { + given: + File one = buildJar('one.jar').insertFile('META-INF/foo/org.apache.maven.Shade', + 'one # NOTE: No newline terminates this line/file').write() + + File two = buildJar('two.jar').insertFile('META-INF/foo/org.apache.maven.Shade', + 'two # NOTE: No newline terminates this line/file').write() + + buildFile << """ + import ${ServiceFileTransformer.name} + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + shadowJar { + transform(ServiceFileTransformer) { + path = 'META-INF/foo' + } + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + String text = getJarFileContents(output, 'META-INF/foo/org.apache.maven.Shade') + assert text.split("\\r?\\n").size() == 2 + assert text == +'''one # NOTE: No newline terminates this line/file +two # NOTE: No newline terminates this line/file'''.stripIndent() + } + + def 'service resource transformer short syntax'() { + given: + File one = buildJar('one.jar') + .insertFile('META-INF/services/org.apache.maven.Shade', + 'one # NOTE: No newline terminates this line/file') + .insertFile('META-INF/services/com.acme.Foo', 'one') + .write() + + File two = buildJar('two.jar') + .insertFile('META-INF/services/org.apache.maven.Shade', + 'two # NOTE: No newline terminates this line/file') + .insertFile('META-INF/services/com.acme.Foo', 'two') + .write() + + buildFile << """ + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + shadowJar { + mergeServiceFiles { + exclude 'META-INF/services/com.acme.*' + } + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + String text1 = getJarFileContents(output, 'META-INF/services/org.apache.maven.Shade') + assert text1.split("\\r?\\n").size() == 2 + assert text1 == +'''one # NOTE: No newline terminates this line/file +two # NOTE: No newline terminates this line/file'''.stripIndent() + + and: + String text2 = getJarFileContents(output, 'META-INF/services/com.acme.Foo') + assert text2.split("\\r?\\n").size() == 1 + assert text2 == 'one' + } + + def 'service resource transformer short syntax relocation'() { + given: + File one = buildJar('one.jar') + .insertFile('META-INF/services/java.sql.Driver', +'''oracle.jdbc.OracleDriver +org.apache.hive.jdbc.HiveDriver'''.stripIndent()) + .insertFile('META-INF/services/org.apache.axis.components.compiler.Compiler', + 'org.apache.axis.components.compiler.Javac') + .insertFile('META-INF/services/org.apache.commons.logging.LogFactory', + 'org.apache.commons.logging.impl.LogFactoryImpl') + .write() + + File two = buildJar('two.jar') + .insertFile('META-INF/services/java.sql.Driver', +'''org.apache.derby.jdbc.AutoloadedDriver +com.mysql.jdbc.Driver'''.stripIndent()) + .insertFile('META-INF/services/org.apache.axis.components.compiler.Compiler', + 'org.apache.axis.components.compiler.Jikes') + .insertFile('META-INF/services/org.apache.commons.logging.LogFactory', + 'org.mortbay.log.Factory') + .write() + + buildFile << """ + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + shadowJar { + mergeServiceFiles() + relocate('org.apache', 'myapache') { + exclude 'org.apache.axis.components.compiler.Jikes' + exclude 'org.apache.commons.logging.LogFactory' + } + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + String text1 = getJarFileContents(output, 'META-INF/services/java.sql.Driver') + assert text1.split("\\r?\\n").size() == 4 + assert text1 == +'''oracle.jdbc.OracleDriver +myapache.hive.jdbc.HiveDriver +myapache.derby.jdbc.AutoloadedDriver +com.mysql.jdbc.Driver'''.stripIndent() + + and: + String text2 = getJarFileContents(output, 'META-INF/services/myapache.axis.components.compiler.Compiler') + assert text2.split("\\r?\\n").size() == 2 + assert text2 == +'''myapache.axis.components.compiler.Javac +org.apache.axis.components.compiler.Jikes'''.stripIndent() + + and: + String text3 = getJarFileContents(output, 'META-INF/services/org.apache.commons.logging.LogFactory') + assert text3.split("\\r?\\n").size() == 2 + assert text3 == +'''myapache.commons.logging.impl.LogFactoryImpl +org.mortbay.log.Factory'''.stripIndent() + } + + def 'service resource transformer short syntax alternate path'() { + given: + File one = buildJar('one.jar').insertFile('META-INF/foo/org.apache.maven.Shade', + 'one # NOTE: No newline terminates this line/file').write() + + File two = buildJar('two.jar').insertFile('META-INF/foo/org.apache.maven.Shade', + 'two # NOTE: No newline terminates this line/file').write() + + buildFile << """ + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + shadowJar { + mergeServiceFiles('META-INF/foo') + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + String text = getJarFileContents(output, 'META-INF/foo/org.apache.maven.Shade') + assert text.split("\\r?\\n").size() == 2 + assert text == +'''one # NOTE: No newline terminates this line/file +two # NOTE: No newline terminates this line/file'''.stripIndent() + } + + @Issue(['SHADOW-70', 'SHADOW-71']) + def 'apply transformers to project resources'() { + given: + File one = buildJar('one.jar').insertFile('META-INF/services/shadow.Shadow', + 'one # NOTE: No newline terminates this line/file').write() + + repo.module('shadow', 'two', '1.0').insertFile('META-INF/services/shadow.Shadow', + 'two # NOTE: No newline terminates this line/file').publish() + + buildFile << """ + dependencies { + implementation 'shadow:two:1.0' + implementation files('${escapedPath(one)}') + } + + shadowJar { + mergeServiceFiles() + } + """.stripIndent() + + file('src/main/resources/META-INF/services/shadow.Shadow') << + 'three # NOTE: No newline terminates this line/file' + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + String text = getJarFileContents(output, 'META-INF/services/shadow.Shadow') + assert text.split("\\r?\\n").size() == 3 + assert text == +'''three # NOTE: No newline terminates this line/file +one # NOTE: No newline terminates this line/file +two # NOTE: No newline terminates this line/file'''.stripIndent() + } + + def 'appending transformer'() { + given: + File one = buildJar('one.jar').insertFile('test.properties', + 'one # NOTE: No newline terminates this line/file').write() + + File two = buildJar('two.jar').insertFile('test.properties', + 'two # NOTE: No newline terminates this line/file').write() + + buildFile << """ + import ${AppendingTransformer.name} + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + shadowJar { + transform(AppendingTransformer) { + resource = 'test.properties' + } + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + String text = getJarFileContents(output, 'test.properties') + assert text.split("\\r?\\n").size() == 2 + assert text == +'''one # NOTE: No newline terminates this line/file +two # NOTE: No newline terminates this line/file +'''.stripIndent() + } + + def 'appending transformer short syntax'() { + given: + File one = buildJar('one.jar').insertFile('test.properties', + 'one # NOTE: No newline terminates this line/file').write() + + File two = buildJar('two.jar').insertFile('test.properties', + 'two # NOTE: No newline terminates this line/file').write() + + buildFile << """ + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + shadowJar { + append('test.properties') + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + String text = getJarFileContents(output, 'test.properties') + assert text.split("\\r?\\n").size() == 2 + assert text == +'''one # NOTE: No newline terminates this line/file +two # NOTE: No newline terminates this line/file +'''.stripIndent() + } + + def 'manifest retained'() { + given: + File main = file('src/main/java/shadow/Main.java') + main << ''' + package shadow; + + public class Main { + + public static void main(String[] args) { } + } + '''.stripIndent() + + buildFile << """ + jar { + manifest { + attributes 'Main-Class': 'shadow.Main' + attributes 'Test-Entry': 'PASSED' + } + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + JarInputStream jis = new JarInputStream(output.newInputStream()) + Manifest mf = jis.manifest + jis.close() + + assert mf + assert mf.mainAttributes.getValue('Test-Entry') == 'PASSED' + assert mf.mainAttributes.getValue('Main-Class') == 'shadow.Main' + } + + def 'manifest transformed'() { + given: + File main = file('src/main/java/shadow/Main.java') + main << ''' + package shadow; + + public class Main { + + public static void main(String[] args) { } + } + '''.stripIndent() + + buildFile << """ + jar { + manifest { + attributes 'Main-Class': 'shadow.Main' + attributes 'Test-Entry': 'FAILED' + } + } + + shadowJar { + manifest { + attributes 'Test-Entry': 'PASSED' + attributes 'New-Entry': 'NEW' + } + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + JarInputStream jis = new JarInputStream(output.newInputStream()) + Manifest mf = jis.manifest + jis.close() + + assert mf + assert mf.mainAttributes.getValue('Test-Entry') == 'PASSED' + assert mf.mainAttributes.getValue('Main-Class') == 'shadow.Main' + assert mf.mainAttributes.getValue('New-Entry') == 'NEW' + } + + @Issue('SHADOW-82') + def 'shadow.manifest leaks to jar.manifest'() { + given: + File main = file('src/main/java/shadow/Main.java') + main << ''' + package shadow; + + public class Main { + + public static void main(String[] args) { } + } + '''.stripIndent() + + buildFile << """ + jar { + manifest { + attributes 'Main-Class': 'shadow.Main' + attributes 'Test-Entry': 'FAILED' + } + } + + shadowJar { + manifest { + attributes 'Test-Entry': 'PASSED' + attributes 'New-Entry': 'NEW' + } + } + """.stripIndent() + + when: + runner.withArguments('jar', 'shadowJar').build() + + then: + File jar = file('build/libs/shadow-1.0.jar') + assert jar.exists() + assert output.exists() + + then: 'Check contents of Shadow jar manifest' + JarInputStream jis = new JarInputStream(output.newInputStream()) + Manifest mf = jis.manifest + + assert mf + assert mf.mainAttributes.getValue('Test-Entry') == 'PASSED' + assert mf.mainAttributes.getValue('Main-Class') == 'shadow.Main' + assert mf.mainAttributes.getValue('New-Entry') == 'NEW' + + then: 'Check contents of jar manifest' + JarInputStream jis2 = new JarInputStream(jar.newInputStream()) + Manifest mf2 = jis2.manifest + + assert mf2 + assert mf2.mainAttributes.getValue('Test-Entry') == 'FAILED' + assert mf2.mainAttributes.getValue('Main-Class') == 'shadow.Main' + assert !mf2.mainAttributes.getValue('New-Entry') + + cleanup: + jis?.close() + jis2?.close() + } + + @Issue('SHADOW-82') + def 'shadow manifest leaks to jar manifest'() { + given: + File main = file('src/main/java/shadow/Main.java') + main << ''' + package shadow; + + public class Main { + + public static void main(String[] args) { } + } + '''.stripIndent() + + buildFile << """ + jar { + manifest { + attributes 'Main-Class': 'shadow.Main' + attributes 'Test-Entry': 'FAILED' + } + } + + shadowJar { + manifest { + attributes 'Test-Entry': 'PASSED' + attributes 'New-Entry': 'NEW' + } + } + """.stripIndent() + + when: + runner.withArguments('jar', 'shadowJar').build() + + then: + File jar = file('build/libs/shadow-1.0.jar') + assert jar.exists() + assert output.exists() + + then: 'Check contents of Shadow jar manifest' + JarInputStream jis = new JarInputStream(output.newInputStream()) + Manifest mf = jis.manifest + + assert mf + assert mf.mainAttributes.getValue('Test-Entry') == 'PASSED' + assert mf.mainAttributes.getValue('Main-Class') == 'shadow.Main' + assert mf.mainAttributes.getValue('New-Entry') == 'NEW' + + then: 'Check contents of jar manifest' + JarInputStream jis2 = new JarInputStream(jar.newInputStream()) + Manifest mf2 = jis2.manifest + + assert mf2 + assert mf2.mainAttributes.getValue('Test-Entry') == 'FAILED' + assert mf2.mainAttributes.getValue('Main-Class') == 'shadow.Main' + assert !mf2.mainAttributes.getValue('New-Entry') + + cleanup: + jis?.close() + jis2?.close() + } + + def 'Groovy extension module transformer'() { + given: + def one = buildJar('one.jar') + .insertFile('META-INF/services/org.codehaus.groovy.runtime.ExtensionModule', +'''moduleName=foo +moduleVersion=1.0.5 +extensionClasses=com.acme.foo.FooExtension,com.acme.foo.BarExtension +staticExtensionClasses=com.acme.foo.FooStaticExtension'''.stripIndent()).write() + + def two = buildJar('two.jar') + .insertFile('META-INF/services/org.codehaus.groovy.runtime.ExtensionModule', +'''moduleName=bar +moduleVersion=2.3.5 +extensionClasses=com.acme.bar.SomeExtension,com.acme.bar.AnotherExtension +staticExtensionClasses=com.acme.bar.SomeStaticExtension'''.stripIndent()).write() + + buildFile << """ + import ${GroovyExtensionModuleTransformer.name} + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + + shadowJar { + transform(GroovyExtensionModuleTransformer) + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + def text = getJarFileContents(output, 'META-INF/services/org.codehaus.groovy.runtime.ExtensionModule') + def props = new Properties() + props.load(new StringReader(text)) + assert props.getProperty('moduleName') == 'MergedByShadowJar' + assert props.getProperty('moduleVersion') == '1.0.0' + assert props.getProperty('extensionClasses') == 'com.acme.foo.FooExtension,com.acme.foo.BarExtension,com.acme.bar.SomeExtension,com.acme.bar.AnotherExtension' + assert props.getProperty('staticExtensionClasses') == 'com.acme.foo.FooStaticExtension,com.acme.bar.SomeStaticExtension' + } + + def 'Groovy extension module transformer short syntax'() { + given: + def one = buildJar('one.jar') + .insertFile('META-INF/services/org.codehaus.groovy.runtime.ExtensionModule', +'''moduleName=foo +moduleVersion=1.0.5 +extensionClasses=com.acme.foo.FooExtension,com.acme.foo.BarExtension +staticExtensionClasses=com.acme.foo.FooStaticExtension'''.stripIndent()).write() + + def two = buildJar('two.jar') + .insertFile('META-INF/services/org.codehaus.groovy.runtime.ExtensionModule', +'''moduleName=bar +moduleVersion=2.3.5 +extensionClasses=com.acme.bar.SomeExtension,com.acme.bar.AnotherExtension +staticExtensionClasses=com.acme.bar.SomeStaticExtension'''.stripIndent()).write() + + buildFile << """ + shadowJar { + from('${escapedPath(one)}') + from('${escapedPath(two)}') + } + shadowJar { + mergeGroovyExtensionModules() + } + """.stripIndent() + + when: + runner.withArguments('shadowJar').build() + + then: + assert output.exists() + + and: + def text = getJarFileContents(output, 'META-INF/services/org.codehaus.groovy.runtime.ExtensionModule') + def props = new Properties() + props.load(new StringReader(text)) + assert props.getProperty('moduleName') == 'MergedByShadowJar' + assert props.getProperty('moduleVersion') == '1.0.0' + assert props.getProperty('extensionClasses') == 'com.acme.foo.FooExtension,com.acme.foo.BarExtension,com.acme.bar.SomeExtension,com.acme.bar.AnotherExtension' + assert props.getProperty('staticExtensionClasses') == 'com.acme.foo.FooStaticExtension,com.acme.bar.SomeStaticExtension' + } + + private static String escapedPath(File file) { + file.path.replaceAll('\\\\', '\\\\\\\\') + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/ClazzpathTest.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/ClazzpathTest.groovy new file mode 100644 index 0000000..f423455 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/ClazzpathTest.groovy @@ -0,0 +1,176 @@ +package org.xbib.gradle.plugin.shadow.internal + +import static org.junit.Assert.assertEquals +import static org.junit.Assert.assertNotNull +import static org.junit.Assert.assertNull +import static org.junit.Assert.assertTrue + +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.Paths + +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameters + +@RunWith(Parameterized.class) +class ClazzpathTest { + + private final AddClazzpathUnit addClazzpathUnit + + private static abstract class AddClazzpathUnit { + + abstract ClazzpathUnit to(Clazzpath clazzpath, String filename, String id) throws IOException + + ClazzpathUnit to(Clazzpath clazzpath, String filename) throws IOException { + to(clazzpath, filename, filename) + } + } + + /** + * Parameters for test + * + * 1. AddClazzpathUnit for classpath-based jars + * 2. AddClazzpathUnit for filesystem-based jars + * 3. AddClazzpathUnit for filesystem-based directories + */ + @Parameters(name = "{index}: {1}") + static Collection data() { + def obj1 = [ + new AddClazzpathUnit() { + @Override + ClazzpathUnit to(Clazzpath toClazzpath, String filename, String id) throws IOException { + InputStream resourceAsStream = getClass() + .getClassLoader() + .getResourceAsStream(filename + ".jar") + assertNotNull(resourceAsStream) + toClazzpath.addClazzpathUnit(resourceAsStream, id) + } + }, "classpath"] as Object[] + def obj2 = [ + new AddClazzpathUnit() { + @Override + ClazzpathUnit to(Clazzpath toClazzpath, String filename, String id) throws IOException { + File file = new File(new File("src/test/resources/" + filename + ".jar").getAbsolutePath()) + assertTrue(file.exists()) + assertTrue(file.isFile()) + toClazzpath.addClazzpathUnit(file, id) + } + }, "file-jar" ] as Object[] + def obj3 = [ + new AddClazzpathUnit() { + @Override + ClazzpathUnit to(Clazzpath toClazzpath, String filename, String id) throws IOException { + File file = new File(new File("src/test/resources/" + filename).getAbsolutePath()) + assertTrue(file.exists()) + assertTrue(file.isDirectory()) + toClazzpath.addClazzpathUnit(file, id) + } + }, "file-directory" ] as Object[] + def obj4 = [ + new AddClazzpathUnit() { + @Override + ClazzpathUnit to(Clazzpath toClazzpath, String filename, String id) throws IOException { + Path path = Paths.get("src/test/resources/" + filename + ".jar") + assertTrue(Files.exists(path)) + assertTrue(Files.isRegularFile(path)) + toClazzpath.addClazzpathUnit(path, id) + } + }, "path-jar"] as Object[] + def obj5 = [ + new AddClazzpathUnit() { + @Override + ClazzpathUnit to(Clazzpath toClazzpath, String filename, String id) throws IOException { + Path path = Paths.get("src/test/resources/" + filename) + assertTrue(Files.exists(path)) + assertTrue(Files.isDirectory(path)) + toClazzpath.addClazzpathUnit(path, id) + } + }, "path-directory"] as Object[] + [obj1, obj2, obj3, obj4, obj5] + } + + ClazzpathTest(AddClazzpathUnit addClazzpathUnit, String kind) { + super() + this.addClazzpathUnit = addClazzpathUnit + } + + @Test + void testShouldAddClasses() throws IOException { + Clazzpath cp = new Clazzpath() + addClazzpathUnit.to(cp, "jar1") + addClazzpathUnit.to(cp, "jar2") + ClazzpathUnit[] units = cp.getUnits() + assertEquals(2, units.length) + assertEquals(129, cp.getClazzes().size()) + } + + @Test + void testShouldRemoveClasspathUnit() throws IOException { + Clazzpath cp = new Clazzpath() + ClazzpathUnit unit1 = addClazzpathUnit.to(cp, "jar1") + assertEquals(59, cp.getClazzes().size()) + ClazzpathUnit unit2 = addClazzpathUnit.to(cp, "jar2") + assertEquals(129, cp.getClazzes().size()) + cp.removeClazzpathUnit(unit1) + assertEquals(70, cp.getClazzes().size()) + cp.removeClazzpathUnit(unit2) + assertEquals(0, cp.getClazzes().size()) + } + + @Test + void testShouldRevealMissingClasses() throws IOException { + Clazzpath cp = new Clazzpath() + addClazzpathUnit.to(cp, "jar1-missing") + Set missing = cp.getMissingClazzes() + Set actual = new HashSet() + for (Clazz clazz : missing) { + String name = clazz.getName() + // ignore the rt + if (!name.startsWith("java")) { + actual.add(name) + } + } + Set expected = new HashSet(Arrays.asList( + "org.apache.commons.io.output.ProxyOutputStream", + "org.apache.commons.io.input.ProxyInputStream" + )) + assertEquals(expected, actual) + } + + @Test + void testShouldShowClasspathUnitsResponsibleForClash() throws IOException { + Clazzpath cp = new Clazzpath() + ClazzpathUnit a = addClazzpathUnit.to(cp, "jar1") + ClazzpathUnit b = addClazzpathUnit.to(cp, "jar1", "foo") + Set clashed = cp.getClashedClazzes() + Set all = cp.getClazzes() + assertEquals(all, clashed) + for (Clazz clazz : clashed) { + assertTrue(clazz.getClazzpathUnits().contains(a)) + assertTrue(clazz.getClazzpathUnits().contains(b)) + } + } + + @Test + void testShouldFindUnusedClasses() throws IOException { + Clazzpath cp = new Clazzpath() + ClazzpathUnit artifact = addClazzpathUnit.to(cp, "jar3using1") + addClazzpathUnit.to(cp, "jar1") + Set removed = cp.getClazzes() + removed.removeAll(artifact.getClazzes()) + removed.removeAll(artifact.getTransitiveDependencies()) + assertEquals("" + removed, 56, removed.size()) + Set kept = cp.getClazzes() + kept.removeAll(removed) + assertEquals("" + kept, 4, kept.size()) + } + + @Test + void testWithModuleInfo() throws Exception { + Clazzpath cp = new Clazzpath() + ClazzpathUnit artifact = addClazzpathUnit.to(cp, "asm-6.0_BETA") + assertNull(artifact.getClazz("module-info")) + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyUtilsTest.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyUtilsTest.groovy new file mode 100644 index 0000000..e0888d9 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/DependencyUtilsTest.groovy @@ -0,0 +1,32 @@ +package org.xbib.gradle.plugin.shadow.internal + +import java.util.stream.Collectors + +import static org.junit.Assert.assertEquals + +import org.junit.Test + +import static org.xbib.gradle.plugin.shadow.internal.DependencyUtils.getDependenciesOfClass + +class DependencyUtilsTest { + + @Test + void testShouldFindDependenciesOfClassObject() throws Exception { + Collection dependencies = getDependenciesOfClass(Object).sort() + Collection expectedDependencies = Arrays.asList( + "java.lang.Class", + "java.lang.CloneNotSupportedException", + "java.lang.Deprecated", + "java.lang.IllegalArgumentException", + "java.lang.Integer", + "java.lang.InterruptedException", + "java.lang.Object", + "java.lang.String", + "java.lang.StringBuilder", + "java.lang.Throwable") + .sort() + // we do not care about jdk.internal classes, they vary between JDK versions and even JVMs + assertEquals(expectedDependencies, dependencies.stream() + .filter(d -> !d.startsWith("jdk.internal")).collect(Collectors.toList()) ) + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/UtilsTest.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/UtilsTest.groovy new file mode 100644 index 0000000..ab179a8 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/internal/UtilsTest.groovy @@ -0,0 +1,46 @@ +package org.xbib.gradle.plugin.shadow.internal + +import org.junit.Test + +import java.nio.charset.Charset + +import static org.junit.Assert.assertEquals + +class UtilsTest { + + @Test + void testCopyLarge() { + def temporaryFile = File.createTempFile("test", ".dat") + temporaryFile.deleteOnExit() + InputStream inputStream = getClass().getResourceAsStream('/jar2.jar') + Utils.copyLarge(inputStream, new FileOutputStream(temporaryFile)) + assertEquals(143847, temporaryFile.bytes.length) + } + + @Test + void testFileWrite() { + File file = new File('build/testfile') + Utils.writeStringToFile(file, 'Hello world', Charset.defaultCharset()) + assertEquals('Hello world', file.text) + file.delete() + } + + @Test + void testReadFileToString() { + File file = new File('build/testfile') + Utils.writeStringToFile(file, 'Hello world', Charset.defaultCharset()) + String text = Utils.readFileToString(file, Charset.defaultCharset()) + assertEquals('Hello world', text) + file.delete() + } + + + @Test + void testReadByteArray() { + File file = new File('build/testfile') + Utils.writeStringToFile(file, 'Hello world', Charset.defaultCharset()) + byte[] bytes = Utils.readFileToByteArray(file) + assertEquals('Hello world', new String(bytes, Charset.defaultCharset())) + file.delete() + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocatorParameterTest.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocatorParameterTest.groovy new file mode 100644 index 0000000..69e41c5 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocatorParameterTest.groovy @@ -0,0 +1,30 @@ +package org.xbib.gradle.plugin.shadow.relocation + +import org.junit.Test + +import static org.junit.Assert.fail + +/** + * Modified from org.apache.maven.plugins.shade.relocation.SimpleRelocatorParameterTest + */ +class SimpleRelocatorParameterTest { + + @Test + void testThatNullPatternInConstructorShouldNotThrowNullPointerException() { + constructThenFailOnNullPointerException(null, "") + } + + @Test + void testThatNullShadedPatternInConstructorShouldNotThrowNullPointerException() { + constructThenFailOnNullPointerException("", null) + } + + private static void constructThenFailOnNullPointerException(String pattern, String shadedPattern) { + try { + new SimpleRelocator(pattern, shadedPattern, Collections. emptyList(), Collections. emptyList()) + } + catch (NullPointerException e) { + fail("Constructor should not throw null pointer exceptions") + } + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocatorTest.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocatorTest.groovy new file mode 100644 index 0000000..cc637ae --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/relocation/SimpleRelocatorTest.groovy @@ -0,0 +1,137 @@ +package org.xbib.gradle.plugin.shadow.relocation + +import org.junit.BeforeClass +import org.junit.Test +import org.xbib.gradle.plugin.shadow.ShadowStats + +import static org.junit.Assert.assertEquals + +/** + * Test for {@link SimpleRelocator}. + * Modified from org.apache.maven.plugins.shade.relocation.SimpleRelocatorTest + */ +class SimpleRelocatorTest { + + static ShadowStats stats + + @BeforeClass + static void setUp() { + stats = new ShadowStats() + } + + @Test + void testCanRelocatePath() { + SimpleRelocator relocator + + relocator = new SimpleRelocator("org.foo", null, null, null) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/Class"))) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/Class.class"))) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/bar/Class"))) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/bar/Class.class"))) + assertEquals(false, relocator.canRelocatePath(pathContext("com/foo/bar/Class"))) + assertEquals(false, relocator.canRelocatePath(pathContext("com/foo/bar/Class.class"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/Foo/Class"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/Foo/Class.class"))) + + relocator = new SimpleRelocator("org.foo", null, null, Arrays.asList( + [ "org.foo.Excluded", "org.foo.public.*", "org.foo.Public*Stuff" ] as String[])) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/Class"))) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/Class.class"))) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/excluded"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/Excluded"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/Excluded.class"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/public"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/public/Class"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/public/Class.class"))) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/publicRELOC/Class"))) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/PrivateStuff"))) + assertEquals(true, relocator.canRelocatePath(pathContext("org/foo/PrivateStuff.class"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/PublicStuff"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/PublicStuff.class"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/PublicUtilStuff"))) + assertEquals(false, relocator.canRelocatePath(pathContext("org/foo/PublicUtilStuff.class"))) + } + + @Test + void testCanRelocateClass() { + SimpleRelocator relocator + + relocator = new SimpleRelocator("org.foo", null, null, null) + assertEquals(true, relocator.canRelocateClass(classContext("org.foo.Class"))) + assertEquals(true, relocator.canRelocateClass(classContext("org.foo.bar.Class"))) + assertEquals(false, relocator.canRelocateClass(classContext("com.foo.bar.Class"))) + assertEquals(false, relocator.canRelocateClass(classContext("org.Foo.Class"))) + + relocator = new SimpleRelocator("org.foo", null, null, Arrays.asList( + [ "org.foo.Excluded", "org.foo.public.*", "org.foo.Public*Stuff" ] as String[])) + assertEquals(true, relocator.canRelocateClass(classContext("org.foo.Class"))) + assertEquals(true, relocator.canRelocateClass(classContext("org.foo.excluded"))) + assertEquals(false, relocator.canRelocateClass(classContext("org.foo.Excluded"))) + assertEquals(false, relocator.canRelocateClass(classContext("org.foo.public"))) + assertEquals(false, relocator.canRelocateClass(classContext("org.foo.public.Class"))) + assertEquals(true, relocator.canRelocateClass(classContext("org.foo.publicRELOC.Class"))) + assertEquals(true, relocator.canRelocateClass(classContext("org.foo.PrivateStuff"))) + assertEquals(false, relocator.canRelocateClass(classContext("org.foo.PublicStuff"))) + assertEquals(false, relocator.canRelocateClass(classContext("org.foo.PublicUtilStuff"))) + } + + @Test + void testCanRelocateRawString() { + SimpleRelocator relocator + + relocator = new SimpleRelocator("org/foo", null, null, null, true) + assertEquals(true, relocator.canRelocatePath(pathContext("(I)org/foo/bar/Class"))) + + relocator = new SimpleRelocator("^META-INF/org.foo.xml\$", null, null, null, true) + assertEquals(true, relocator.canRelocatePath(pathContext("META-INF/org.foo.xml"))) + } + + //MSHADE-119, make sure that the easy part of this works. + @Test + void testCanRelocateAbsClassPath() { + SimpleRelocator relocator = new SimpleRelocator("org.apache.velocity", "org.apache.momentum", null, null) + assertEquals("/org/apache/momentum/mass.properties", relocator.relocatePath(pathContext("/org/apache/velocity/mass.properties"))) + + } + + @Test + void testRelocatePath() { + SimpleRelocator relocator + + relocator = new SimpleRelocator("org.foo", null, null, null) + assertEquals("hidden/org/foo/bar/Class.class", relocator.relocatePath(pathContext("org/foo/bar/Class.class"))) + + relocator = new SimpleRelocator("org.foo", "private.stuff", null, null) + assertEquals("private/stuff/bar/Class.class", relocator.relocatePath(pathContext("org/foo/bar/Class.class"))) + } + + @Test + void testRelocateClass() { + SimpleRelocator relocator + + relocator = new SimpleRelocator("org.foo", null, null, null) + assertEquals("hidden.org.foo.bar.Class", relocator.relocateClass(classContext("org.foo.bar.Class"))) + + relocator = new SimpleRelocator("org.foo", "private.stuff", null, null) + assertEquals("private.stuff.bar.Class", relocator.relocateClass(classContext("org.foo.bar.Class"))) + } + + @Test + void testRelocateRawString() { + SimpleRelocator relocator + + relocator = new SimpleRelocator("Lorg/foo", "Lhidden/org/foo", null, null, true) + assertEquals("(I)Lhidden/org/foo/bar/Class", relocator.relocatePath(pathContext("(I)Lorg/foo/bar/Class"))) + + relocator = new SimpleRelocator("^META-INF/org.foo.xml\$", "META-INF/hidden.org.foo.xml", null, null, true) + assertEquals("META-INF/hidden.org.foo.xml", relocator.relocatePath(pathContext("META-INF/org.foo.xml"))) + } + + protected RelocatePathContext pathContext(String path) { + return RelocatePathContext.builder().path(path).stats(stats).build() + } + + protected RelocateClassContext classContext(String className) { + return RelocateClassContext.builder().className(className).stats(stats).build() + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheLicenseResourceTransformerTest.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheLicenseResourceTransformerTest.groovy new file mode 100644 index 0000000..f794af9 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheLicenseResourceTransformerTest.groovy @@ -0,0 +1,39 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.junit.Before +import org.junit.Test + +import static org.junit.Assert.assertTrue +import static org.junit.Assert.assertFalse + +/** + * Test for {@link ApacheLicenseResourceTransformer}. + * + * Modified from org.apache.maven.plugins.shade.resources.ApacheLicenseResourceTransformerTest + */ +class ApacheLicenseResourceTransformerTest extends TransformerTestSupport { + + private ApacheLicenseResourceTransformer transformer + + static { + /* + * NOTE: The Turkish locale has an usual case transformation for the letters "I" and "i", making it a prime + * choice to test for improper case-less string comparisions. + */ + Locale.setDefault(new Locale("tr")) + } + + @Before + void setUp() { + this.transformer = new ApacheLicenseResourceTransformer() + } + + @Test + void testCanTransformResource() { + assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/LICENSE"))) + assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/LICENSE.TXT"))) + assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/License.txt"))) + assertFalse(this.transformer.canTransformResource(getFileElement("META-INF/MANIFEST.MF"))) + } + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformerParameterTests.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformerParameterTests.groovy new file mode 100644 index 0000000..e5da83d --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformerParameterTests.groovy @@ -0,0 +1,62 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.xbib.gradle.plugin.shadow.ShadowStats +import junit.framework.TestCase +import org.xbib.gradle.plugin.shadow.relocation.Relocator + +/** + * Tests {@link ApacheLicenseResourceTransformer} parameters. + * + * Modified from org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformerParameterTests + */ +class ApacheNoticeResourceTransformerParameterTests extends TestCase { + + private static final String NOTICE_RESOURCE = "META-INF/NOTICE" + private ApacheNoticeResourceTransformer subject + private ShadowStats stats + + protected void setUp() { + super.setUp() + subject = new ApacheNoticeResourceTransformer() + stats = new ShadowStats() + } + + void testNoParametersShouldNotThrowNullPointerWhenNoInput() { + processAndFailOnNullPointer("") + } + + void testNoParametersShouldNotThrowNullPointerWhenNoLinesOfInput() { + processAndFailOnNullPointer("Some notice text") + } + + void testNoParametersShouldNotThrowNullPointerWhenOneLineOfInput() { + processAndFailOnNullPointer("Some notice text\n") + } + + void testNoParametersShouldNotThrowNullPointerWhenTwoLinesOfInput() { + processAndFailOnNullPointer("Some notice text\nSome notice text\n") + } + + void testNoParametersShouldNotThrowNullPointerWhenLineStartsWithSlashSlash() { + processAndFailOnNullPointer("Some notice text\n//Some notice text\n") + } + + void testNoParametersShouldNotThrowNullPointerWhenLineIsSlashSlash() { + processAndFailOnNullPointer("//\n") + } + + void testNoParametersShouldNotThrowNullPointerWhenLineIsEmpty() { + processAndFailOnNullPointer("\n") + } + + private void processAndFailOnNullPointer(final String noticeText) { + try { + final ByteArrayInputStream noticeInputStream = new ByteArrayInputStream(noticeText.getBytes()) + final List emptyList = Collections.emptyList() + subject.transform(TransformerContext.builder().path(NOTICE_RESOURCE).inputStream(noticeInputStream).relocators(emptyList).stats(stats).build()) + } + catch (NullPointerException e) { + fail("Null pointer should not be thrown when no parameters are set.") + } + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformerTest.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformerTest.groovy new file mode 100644 index 0000000..bb95433 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ApacheNoticeResourceTransformerTest.groovy @@ -0,0 +1,37 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.junit.Before +import org.junit.Test + +import static org.junit.Assert.* + +/** + * Test for {@link ApacheNoticeResourceTransformer}. + * Modified from org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformerTest + */ +class ApacheNoticeResourceTransformerTest extends TransformerTestSupport { + + private ApacheNoticeResourceTransformer transformer + + static { + /* + * NOTE: The Turkish locale has an usual case transformation for the letters "I" and "i", making it a prime + * choice to test for improper case-less string comparisions. + */ + Locale.setDefault(new Locale("tr")) + } + + @Before + void setUp() { + this.transformer = new ApacheNoticeResourceTransformer() + } + + @Test + void testCanTransformResource() { + assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/NOTICE"))) + assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/NOTICE.TXT"))) + assertTrue(this.transformer.canTransformResource(getFileElement("META-INF/Notice.txt"))) + assertFalse(this.transformer.canTransformResource(getFileElement("META-INF/MANIFEST.MF"))) + } + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/AppendingTransformerTest.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/AppendingTransformerTest.groovy new file mode 100644 index 0000000..c856516 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/AppendingTransformerTest.groovy @@ -0,0 +1,38 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.junit.Before +import org.junit.Test + +import static org.junit.Assert.* + +/** + * Test for {@link AppendingTransformer}. + */ +class AppendingTransformerTest extends TransformerTestSupport { + + private AppendingTransformer transformer + + static + { + /* + * NOTE: The Turkish locale has an usual case transformation for the letters "I" and "i", making it a prime + * choice to test for improper case-less string comparisions. + */ + Locale.setDefault(new Locale("tr")) + } + + @Before + void setUp() { + this.transformer = new AppendingTransformer() + } + + @Test + void testCanTransformResource() { + this.transformer.resource = "abcdefghijklmnopqrstuvwxyz" + + assertTrue(this.transformer.canTransformResource(getFileElement("abcdefghijklmnopqrstuvwxyz"))) + assertTrue(this.transformer.canTransformResource(getFileElement("ABCDEFGHIJKLMNOPQRSTUVWXYZ"))) + assertFalse(this.transformer.canTransformResource(getFileElement("META-INF/MANIFEST.MF"))) + } + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/PropertiesFileTransformerSpec.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/PropertiesFileTransformerSpec.groovy new file mode 100644 index 0000000..ee2c4b7 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/PropertiesFileTransformerSpec.groovy @@ -0,0 +1,126 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import spock.lang.Unroll + +import static groovy.lang.Closure.IDENTITY + +@Unroll +class PropertiesFileTransformerSpec extends TransformerSpecSupport { + + void "Path #path #transform transformed"() { + given: + Transformer transformer = new PropertiesFileTransformer() + + when: + boolean actual = transformer.canTransformResource(getFileElement(path)) + + then: + actual == expected + + where: + path || expected + 'foo.properties' || true + 'foo/bar.properties' || true + 'foo.props' || false + + transform = expected ? 'can be' : 'can not be' + } + + void exerciseAllTransformConfigurations() { + given: + def element = getFileElement(path) + Transformer transformer = new PropertiesFileTransformer() + transformer.mergeStrategy = mergeStrategy + transformer.mergeSeparator = mergeSeparator + + when: + if (transformer.canTransformResource(element)) { + transformer.transform(context(path, input1)) + transformer.transform(context(path, input2)) + } + + then: + output == toMap(transformer.propertiesEntries[path]) + + where: + path | mergeStrategy | mergeSeparator | input1 | input2 || output + 'f.properties' | 'first' | '' | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo'] + 'f.properties' | 'latest' | '' | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'bar'] + 'f.properties' | 'append' | ',' | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo,bar'] + 'f.properties' | 'append' | ';' | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo;bar'] + } + + void exerciseAllTransformConfigurationsWithPaths() { + given: + def element = getFileElement(path) + Transformer transformer = new PropertiesFileTransformer() + transformer.paths = paths + transformer.mergeStrategy = 'first' + + when: + if (transformer.canTransformResource(element)) { + transformer.transform(context(path, input1)) + transformer.transform(context(path, input2)) + } + + then: + output == toMap(transformer.propertiesEntries[path]) + + where: + path | paths | input1 | input2 || output + 'f.properties' | ['f.properties'] | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo'] + 'foo.properties' | ['.*.properties'] | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo'] + 'foo.properties' | ['.*bar'] | ['foo': 'foo'] | ['foo': 'bar'] || [:] + 'foo.properties' | [] | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo'] + } + + void exerciseAllTransformConfigurationsWithMappings() { + given: + def element = getFileElement(path) + Transformer transformer = new PropertiesFileTransformer() + transformer.mappings = mappings + transformer.mergeStrategy = 'latest' + + when: + if (transformer.canTransformResource(element)) { + transformer.transform(context(path, input1)) + transformer.transform(context(path, input2)) + } + + then: + output == toMap(transformer.propertiesEntries[path]) + + where: + path | mappings | input1 | input2 || output + 'f.properties' | ['f.properties': [mergeStrategy: 'first']] | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo'] + 'f.properties' | ['f.properties': [mergeStrategy: 'latest']] | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'bar'] + 'f.properties' | ['f.properties': [mergeStrategy: 'append']] | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo,bar'] + 'f.properties' | ['f.properties': [mergeStrategy: 'append', mergeSeparator: ';']] | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo;bar'] + 'foo.properties' | ['.*.properties': [mergeStrategy: 'first']] | ['foo': 'foo'] | ['foo': 'bar'] || ['foo': 'foo'] + 'foo.properties' | ['.*bar': [mergeStrategy: 'first']] | ['foo': 'foo'] | ['foo': 'bar'] || [:] + } + + void appliesKeyTransformer() { + given: + def element = getFileElement(path) + Transformer transformer = new PropertiesFileTransformer() + transformer.keyTransformer = keyTransformer + transformer.mergeStrategy = 'append' + + when: + if (transformer.canTransformResource(element)) { + transformer.transform(context(path, input1)) + transformer.transform(context(path, input2)) + } + + then: + output == toMap(transformer.propertiesEntries[path]) + + where: + path | keyTransformer | input1 | input2 || output + 'foo.properties' | IDENTITY | ['foo': 'bar'] | ['FOO': 'baz'] || ['foo': 'bar', 'FOO': 'baz'] + 'foo.properties' | { key -> key.toUpperCase() } | ['foo': 'bar'] | ['FOO': 'baz'] || ['FOO': 'bar,baz'] + 'foo.properties' | { key -> 'bar.' + key.toLowerCase() } | ['foo': 'bar'] | ['FOO': 'baz'] || ['bar.foo': 'bar,baz'] + 'foo.properties' | { key -> key.replaceAll('^(foo)', 'bar.$1') } | ['foo': 'bar'] | ['FOO': 'baz'] || ['bar.foo': 'bar', 'FOO': 'baz'] + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ServiceFileTransformerSpec.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ServiceFileTransformerSpec.groovy new file mode 100644 index 0000000..52c377c --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/ServiceFileTransformerSpec.groovy @@ -0,0 +1,63 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import spock.lang.Unroll + +@Unroll +class ServiceFileTransformerSpec extends TransformerSpecSupport { + + def "#status path #path #transform transformed"() { + given: + def transformer = new ServiceFileTransformer() + if (exclude) { + transformer.exclude(path) + } + + when: + def actual = transformer.canTransformResource(getFileElement(path)) + + then: + actual == expected + + where: + path | exclude | expected + 'META-INF/services/java.sql.Driver' | false | true + 'META-INF/services/io.dropwizard.logging.AppenderFactory' | false | true + 'META-INF/services/org.apache.maven.Shade' | true | false + 'META-INF/services/foo/bar/moo.goo.Zoo' | false | true + 'foo/bar.properties' | false | false + 'foo.props' | false | false + + transform = expected ? 'can be' : 'can not be' + status = exclude ? 'excluded' : 'non-excluded' + } + + def "transforms service file"() { + given: + def element = getFileElement(path) + def transformer = new ServiceFileTransformer() + + when: + if (transformer.canTransformResource(element)) { + transformer.transform(context(path, input1)) + transformer.transform(context(path, input2)) + } + + then: + transformer.hasTransformedResource() + output == transformer.serviceEntries[path].toInputStream().text + + where: + path | input1 | input2 || output + 'META-INF/services/com.acme.Foo' | 'foo' | 'bar' || 'foo\nbar' + 'META-INF/services/com.acme.Bar' | 'foo\nbar' | 'zoo' || 'foo\nbar\nzoo' + } + + def "excludes Groovy extension module descriptor files by default"() { + given: + def transformer = new ServiceFileTransformer() + def element = getFileElement('META-INF/services/org.codehaus.groovy.runtime.ExtensionModule') + + expect: + !transformer.canTransformResource(element) + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerSpecSupport.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerSpecSupport.groovy new file mode 100644 index 0000000..cc59d21 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerSpecSupport.groovy @@ -0,0 +1,55 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.xbib.gradle.plugin.shadow.ShadowStats +import org.gradle.api.file.FileTreeElement +import org.gradle.api.file.RelativePath +import org.gradle.api.internal.file.DefaultFileTreeElement +import spock.lang.Shared +import spock.lang.Specification + +class TransformerSpecSupport extends Specification { + + @Shared + ShadowStats stats + + def setup() { + stats = new ShadowStats() + } + + protected static FileTreeElement getFileElement(String path) { + return new DefaultFileTreeElement(null, RelativePath.parse(true, path), null, null) + } + + protected static InputStream toInputStream(String str) { + return new ByteArrayInputStream(str.bytes) + } + + protected static InputStream toInputStream(Properties props) { + ByteArrayOutputStream baos = new ByteArrayOutputStream() + props.store(baos, '') + new ByteArrayInputStream(baos.toByteArray()) + } + + protected static Properties toProperties(Map map) { + map.inject(new Properties()) { Properties props, entry -> + props.put(entry.key, entry.value) + props + } + } + + protected static Map toMap(Properties props) { + props.inject([:]) { Map map, entry -> + map.put(entry.key, entry.value) + map + } + } + + protected TransformerContext context(String path, Map input) { + TransformerContext.builder().path(path).inputStream(toInputStream(toProperties(input))).relocators([]).stats(stats).build() + } + + protected TransformerContext context(String path, String input) { + TransformerContext.builder().path(path).inputStream(toInputStream(input)).relocators([]).stats(stats).build() + } + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerTestSupport.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerTestSupport.groovy new file mode 100644 index 0000000..bad5e88 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/transformers/TransformerTestSupport.groovy @@ -0,0 +1,13 @@ +package org.xbib.gradle.plugin.shadow.transformers + +import org.gradle.api.file.FileTreeElement +import org.gradle.api.file.RelativePath +import org.gradle.api.internal.file.DefaultFileTreeElement + +class TransformerTestSupport { + + protected static FileTreeElement getFileElement(String path) { + return new DefaultFileTreeElement(null, RelativePath.parse(true, path), null, null) + } + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableJar.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableJar.groovy new file mode 100644 index 0000000..e8755f8 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableJar.groovy @@ -0,0 +1,25 @@ +package org.xbib.gradle.plugin.shadow.util + +class AppendableJar { + + Map contents = [:] + File file + + AppendableJar(File file) { + this.file = file + } + + AppendableJar insertFile(String path, String content) { + contents[path] = content + return this + } + + File write() { + JarBuilder builder = new JarBuilder(file.newOutputStream()) + contents.each { path, contents -> + builder.withFile(path, contents) + } + builder.build() + return file + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableMavenFileModule.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableMavenFileModule.groovy new file mode 100644 index 0000000..e3f1019 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableMavenFileModule.groovy @@ -0,0 +1,70 @@ +package org.xbib.gradle.plugin.shadow.util + +import org.xbib.gradle.plugin.shadow.internal.Utils +import org.xbib.gradle.plugin.shadow.util.repo.maven.MavenFileModule +import groovy.transform.InheritConstructors + +@InheritConstructors +class AppendableMavenFileModule extends MavenFileModule { + + Map> contents = [:].withDefault { [:] } + Map files = [:] + + AppendableMavenFileModule use(File file) { + return use('', file) + } + + AppendableMavenFileModule use(String classifier, File file) { + files[classifier] = file + return this + } + + AppendableMavenFileModule insertFile(String path, String content) { + insertFile('', path, content) + return this + } + + AppendableMavenFileModule insertFile(String classifier, String path, String content) { + contents[classifier][path] = content + return this + } + + @Override + File publishArtifact(Map artifact) { + def artifactFile = artifactFile(artifact) + if (type == 'pom') { + return artifactFile + } + String classifier = (String) artifact['classifier'] ?: '' + if (files.containsKey(classifier)) { + publishWithStream(artifactFile) { OutputStream os -> + Utils.copyLarge(files[classifier].newInputStream(), os) + } + } else { + publishWithStream(artifactFile) { OutputStream os -> + writeJar(os, contents[classifier]) + } + } + return artifactFile + } + + void writeJar(OutputStream os, Map contents) { + if (contents) { + JarBuilder builder = new JarBuilder(os) + contents.each { path, content -> + builder.withFile(path, content) + } + builder.build() + } + } + + /** + * Adds an additional artifact to this module. + * @param options Can specify any of: type or classifier + */ + AppendableMavenFileModule artifact(Map options) { + artifacts << options + return this + } + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableMavenFileRepository.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableMavenFileRepository.groovy new file mode 100644 index 0000000..fb82d1f --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/AppendableMavenFileRepository.groovy @@ -0,0 +1,15 @@ +package org.xbib.gradle.plugin.shadow.util + +import org.xbib.gradle.plugin.shadow.util.repo.maven.MavenFileRepository +import groovy.transform.InheritConstructors + +@InheritConstructors +class AppendableMavenFileRepository extends MavenFileRepository { + + @Override + AppendableMavenFileModule module(String groupId, String artifactId, Object version = '1.0') { + def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version") + return new AppendableMavenFileModule(artifactDir, groupId, artifactId, version as String) + } + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/JarBuilder.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/JarBuilder.groovy new file mode 100644 index 0000000..be2f706 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/JarBuilder.groovy @@ -0,0 +1,47 @@ +package org.xbib.gradle.plugin.shadow.util + +import java.util.jar.JarEntry +import java.util.jar.JarOutputStream + +class JarBuilder { + + List entries = [] + JarOutputStream jos + + JarBuilder(OutputStream os) { + jos = new JarOutputStream(os) + } + + private void addDirectory(String name) { + if (!entries.contains(name)) { + if (name.lastIndexOf('/') > 0) { + String parent = name.substring(0, name.lastIndexOf('/')) + if (!entries.contains(parent)) { + addDirectory(parent) + } + } + // directory entries must end in "/" + JarEntry entry = new JarEntry(name + "/") + jos.putNextEntry(entry) + entries.add(name) + } + } + + JarBuilder withFile(String path, String data) { + def idx = path.lastIndexOf('/') + if (idx != -1) { + addDirectory(path.substring(0, idx)) + } + if (!entries.contains(path)) { + JarEntry entry = new JarEntry(path) + jos.putNextEntry(entry) + entries << path + jos.write(data.bytes) + } + return this + } + + void build() { + jos.close() + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/PluginSpecification.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/PluginSpecification.groovy new file mode 100644 index 0000000..814a920 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/PluginSpecification.groovy @@ -0,0 +1,136 @@ +package org.xbib.gradle.plugin.shadow.util + +import org.xbib.gradle.plugin.shadow.util.file.TestFile +import org.gradle.testkit.runner.GradleRunner +import org.junit.Rule +import org.junit.rules.TemporaryFolder +import spock.lang.Specification + +import java.util.jar.JarEntry +import java.util.jar.JarFile + +class PluginSpecification extends Specification { + + @Rule TemporaryFolder dir + + AppendableMavenFileRepository repo + + def setup() { + repo = repo() + repo.module('junit', 'junit', '3.8.2').use(testJar).publish() + + buildFile << defaultBuildScript + + settingsFile << ''' + rootProject.name = 'shadow' + ''' + } + + def cleanup() { + println buildFile.text + } + + String getDefaultBuildScript() { + return """ + plugins { + id 'java' + id 'org.xbib.gradle.plugin.shadow' + } + + version = "1.0" + group = 'shadow' + + sourceSets { + integTest + } + + repositories { maven { url "${repo.uri}" } } + """.stripIndent() + } + + GradleRunner getRunner() { + GradleRunner.create() + .withProjectDir(dir.root) + .forwardOutput() + .withPluginClasspath() + } + + File getLocalRepo() { + def rootRelative = new File("build/localrepo") + rootRelative.directory ? rootRelative : new File(new File(System.getProperty("user.dir")).parentFile, "build/localrepo") + } + + File getBuildFile() { + file('build.gradle') + } + + File getSettingsFile() { + file('settings.gradle') + } + + File file(String path) { + File f = new File(dir.root, path) + if (!f.exists()) { + f.parentFile.mkdirs() + return dir.newFile(path) + } + return f + } + + AppendableMavenFileRepository repo(String path = 'maven-repo') { + new AppendableMavenFileRepository(new TestFile(dir.root, path)) + } + + void assertJarFileContentsEqual(File f, String path, String contents) { + assert getJarFileContents(f, path) == contents + } + + String getJarFileContents(File f, String path) { + JarFile jf = new JarFile(f) + InputStream is = jf.getInputStream(new JarEntry(path)) + String string = is.text + jf.close() + is.close() + return string + } + + void contains(File f, List paths) { + JarFile jar = new JarFile(f) + paths.each { path -> + assert jar.getJarEntry(path), "${f.path} does not contain [$path]" + } + jar.close() + } + + void doesNotContain(File f, List paths) { + JarFile jar = new JarFile(f) + paths.each { path -> + assert !jar.getJarEntry(path), "${f.path} contains [$path]" + } + jar.close() + } + + AppendableJar buildJar(String path) { + return new AppendableJar(file(path)) + } + + protected getOutput() { + file('build/libs/shadow-1.0-all.jar') + } + + protected output(String name) { + file("build/libs/${name}") + } + + protected File getTestJar(String name = 'junit-3.8.2.jar') { + return new File(this.class.classLoader.getResource(name).toURI()) + } + + public static File getTestKitDir() { + def gradleUserHome = System.getenv("GRADLE_USER_HOME") + if (!gradleUserHome) { + gradleUserHome = new File(System.getProperty("user.home"), ".gradle").absolutePath + } + return new File(gradleUserHome, "testkit") + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/ExecOutput.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/ExecOutput.groovy new file mode 100644 index 0000000..3754c14 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/ExecOutput.groovy @@ -0,0 +1,13 @@ +package org.xbib.gradle.plugin.shadow.util.file + +class ExecOutput { + ExecOutput(String rawOutput, String error) { + this.rawOutput = rawOutput + this.out = rawOutput.replaceAll("\r\n|\r", "\n") + this.error = error + } + + String rawOutput + String out + String error +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/Results.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/Results.groovy new file mode 100644 index 0000000..e257b08 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/Results.groovy @@ -0,0 +1,58 @@ +package org.xbib.gradle.plugin.shadow.util.file + +import org.gradle.tooling.GradleConnectionException +import org.gradle.tooling.ResultHandler + +class Results implements ResultHandler { + + private final Object lock = new Object() + + private boolean success = false + private GradleConnectionException exception + + void waitForCompletion() { + synchronized(lock) { + while(!successful && !failed) { + lock.wait() + } + } + } + + boolean getSuccessful() { + return success && !exception + } + + boolean getFailed() { + return exception as boolean + } + + GradleConnectionException getException() { + return exception + } + + void markComplete() { + synchronized(lock) { + success = true + exception = null + lock.notifyAll() + } + } + + void markFailed(GradleConnectionException e) { + synchronized(lock) { + success = false + exception = e + lock.notifyAll() + } + } + + @Override + void onComplete(Void aVoid) { + markComplete() + } + + @Override + void onFailure(GradleConnectionException e) { + markFailed(e) + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestDirectoryProvider.java b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestDirectoryProvider.java new file mode 100644 index 0000000..f0a28da --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestDirectoryProvider.java @@ -0,0 +1,17 @@ +package org.xbib.gradle.plugin.shadow.util.file; + +/** + * Implementations provide a working space to be used in tests. + * + * The client is not responsible for removing any files. + */ +public interface TestDirectoryProvider { + + /** + * The directory to use, guaranteed to exist. + * + * @return The directory to use, guaranteed to exist. + */ + TestFile getTestDirectory(); + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestFile.java b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestFile.java new file mode 100644 index 0000000..02f4b9d --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestFile.java @@ -0,0 +1,310 @@ +package org.xbib.gradle.plugin.shadow.util.file; + +import groovy.lang.Closure; +import org.codehaus.groovy.runtime.ResourceGroovyMethods; +import org.xbib.gradle.plugin.shadow.internal.Utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectStreamException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestFile extends File { + + public TestFile(File file, Object... path) { + super(join(file, path).getAbsolutePath()); + } + + public TestFile(URI uri) { + this(new File(uri)); + } + + public TestFile(String path) { + this(new File(path)); + } + + public TestFile(URL url) { + this(toUri(url)); + } + + Object writeReplace() throws ObjectStreamException { + return new File(getAbsolutePath()); + } + + @Override + public File getCanonicalFile() throws IOException { + return new File(getAbsolutePath()).getCanonicalFile(); + } + + @Override + public String getCanonicalPath() throws IOException { + return new File(getAbsolutePath()).getCanonicalPath(); + } + + private static URI toUri(URL url) { + try { + return url.toURI(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private static File join(File file, Object[] path) { + File current = file.getAbsoluteFile(); + for (Object p : path) { + current = new File(current, p.toString()); + } + try { + return current.getCanonicalFile(); + } catch (IOException e) { + throw new RuntimeException(String.format("Could not canonicalise '%s'.", current), e); + } + } + + public TestFile file(Object... path) { + try { + return new TestFile(this, path); + } catch (RuntimeException e) { + throw new RuntimeException(String.format("Could not locate file '%s' relative to '%s'.", Arrays.toString(path), this), e); + } + } + + public List files(Object... paths) { + List files = new ArrayList(); + for (Object path : paths) { + files.add(file(path)); + } + return files; + } + + public TestFile write(Object content) { + try { + Utils.writeStringToFile(this, content.toString(), Charset.defaultCharset()); + } catch (IOException e) { + throw new RuntimeException(String.format("Could not write to test file '%s'", this), e); + } + return this; + } + + public TestFile leftShift(Object content) { + getParentFile().mkdirs(); + try { + ResourceGroovyMethods.leftShift(this, content); + return this; + } catch (IOException e) { + throw new RuntimeException(String.format("Could not append to test file '%s'", this), e); + } + } + + public TestFile[] listFiles() { + File[] children = super.listFiles(); + TestFile[] files = new TestFile[children.length]; + for (int i = 0; i < children.length; i++) { + File child = children[i]; + files[i] = new TestFile(child); + } + return files; + } + + public String getText() { + assertIsFile(); + try { + return Utils.readFileToString(this, Charset.defaultCharset()); + } catch (IOException e) { + throw new RuntimeException(String.format("Could not read from test file '%s'", this), e); + } + } + + public Map getProperties() { + assertIsFile(); + Properties properties = new Properties(); + try { + try (FileInputStream inStream = new FileInputStream(this)) { + properties.load(inStream); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + Map map = new HashMap<>(); + for (Object key : properties.keySet()) { + map.put(key.toString(), properties.getProperty(key.toString())); + } + return map; + } + + public Manifest getManifest() { + assertIsFile(); + try { + try (JarFile jarFile = new JarFile(this)) { + return jarFile.getManifest(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public TestFile create(Closure structure) { + assertTrue(isDirectory() || mkdirs()); + new TestWorkspaceBuilder(this).apply(structure); + return this; + } + + @Override + public TestFile getParentFile() { + return super.getParentFile() == null ? null : new TestFile(super.getParentFile()); + } + + @Override + public String toString() { + return getPath(); + } + + public TestFile assertExists() { + assertTrue(exists(), String.format("%s does not exist", this)); + return this; + } + + public TestFile assertIsFile() { + assertTrue(isFile(), String.format("%s is not a file", this)); + return this; + } + + public TestFile assertIsDir() { + assertTrue(isDirectory(), String.format("%s is not a directory", this)); + return this; + } + + public TestFile assertDoesNotExist() { + assertFalse( exists(), String.format("%s should not exist", this)); + return this; + } + + private byte[] getHash(String algorithm) { + try { + MessageDigest messageDigest = MessageDigest.getInstance(algorithm); + messageDigest.update(Utils.readFileToByteArray(this)); + return messageDigest.digest(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public String getPermissions() { + assertExists(); + return new TestFileHelper(this).getPermissions(); + } + + public TestFile setPermissions(String permissions) { + assertExists(); + new TestFileHelper(this).setPermissions(permissions); + return this; + } + + public TestFile setMode(int mode) { + assertExists(); + new TestFileHelper(this).setMode(mode); + return this; + } + + private void visit(Set names, String prefix, File file) { + for (File child : file.listFiles()) { + if (child.isFile()) { + names.add(prefix + child.getName()); + } else if (child.isDirectory()) { + visit(names, prefix + child.getName() + "/", child); + } + } + } + + public TestFile createDir() { + if (mkdirs()) { + return this; + } + if (isDirectory()) { + return this; + } + throw new AssertionError("Problems creating dir: " + this + + ". Diagnostics: exists=" + this.exists() + ", isFile=" + this.isFile() + ", isDirectory=" + this.isDirectory()); + } + + public TestFile deleteDir() { + new TestFileHelper(this).delete(false); + return this; + } + + /** + * Attempts to delete this directory, ignoring failures to do so. + * @return this + */ + public TestFile maybeDeleteDir() { + try { + deleteDir(); + } catch (RuntimeException e) { + // Ignore + } + return this; + } + + public TestFile createFile() { + new TestFile(getParentFile()).createDir(); + try { + assertTrue(isFile() || createNewFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + + public Snapshot snapshot() { + assertIsFile(); + return new Snapshot(lastModified(), getHash("MD5")); + } + + /*public void assertHasChangedSince(Snapshot snapshot) { + Snapshot now = snapshot(); + assertTrue(now.modTime != snapshot.modTime || !Arrays.equals(now.hash, snapshot.hash)); + } + + public void assertContentsHaveChangedSince(Snapshot snapshot) { + Snapshot now = snapshot(); + assertTrue(String.format("contents of %s have not changed", this), !Arrays.equals(now.hash, snapshot.hash)); + } + + public void assertContentsHaveNotChangedSince(Snapshot snapshot) { + Snapshot now = snapshot(); + assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash); + } + + public void assertHasNotChangedSince(Snapshot snapshot) { + Snapshot now = snapshot(); + assertEquals(String.format("last modified time of %s has changed", this), snapshot.modTime, now.modTime); + assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash); + } +*/ + public class Snapshot { + private final long modTime; + private final byte[] hash; + + public Snapshot(long modTime, byte[] hash) { + this.modTime = modTime; + this.hash = hash; + } + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestFileHelper.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestFileHelper.groovy new file mode 100644 index 0000000..80957a0 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestFileHelper.groovy @@ -0,0 +1,218 @@ +package org.xbib.gradle.plugin.shadow.util.file; + +import org.apache.tools.ant.Project +import org.apache.tools.ant.taskdefs.Expand +import org.apache.tools.ant.taskdefs.Tar +import org.apache.tools.ant.taskdefs.Untar +import org.apache.tools.ant.taskdefs.Zip + +import java.util.zip.ZipInputStream + +import static org.junit.Assert.assertTrue + +class TestFileHelper { + TestFile file + + TestFileHelper(TestFile file) { + this.file = file + } + + void unzipTo(File target, boolean nativeTools) { + // Check that each directory in hierarchy is present + file.withInputStream {InputStream instr -> + def dirs = [] as Set + def zipStr = new ZipInputStream(instr) + def entry + while (entry = zipStr.getNextEntry()) { + if (entry.directory) { + assertTrue("Duplicate directory '$entry.name'", dirs.add(entry.name)) + } + if (!entry.name.contains('/')) { + continue + } + def parent = substringBeforeLast(entry.name, '/') + '/' + assertTrue("Missing dir '$parent'", dirs.contains(parent)) + } + } + + if (nativeTools && isUnix()) { + def process = ['unzip', '-o', file.absolutePath, '-d', target.absolutePath].execute() + process.consumeProcessOutput(System.out, System.err) + assert process.waitFor() == 0 + return + } + + def unzip = new Expand() + unzip.src = file + unzip.dest = target + + unzip.project = new Project() + unzip.execute() + } + + void untarTo(File target, boolean nativeTools) { + if (nativeTools && isUnix()) { + target.mkdirs() + def builder = new ProcessBuilder(['tar', '-xpf', file.absolutePath]) + builder.directory(target) + def process = builder.start() + process.consumeProcessOutput() + assert process.waitFor() == 0 + return + } + + def untar = new Untar() + untar.setSrc(file) + untar.setDest(target) + + if (file.name.endsWith(".tgz")) { + def method = new Untar.UntarCompressionMethod() + method.value = "gzip" + untar.compression = method + } else if (file.name.endsWith(".tbz2")) { + def method = new Untar.UntarCompressionMethod() + method.value = "bzip2" + untar.compression = method + } + + untar.project = new Project() + untar.execute() + } + + private boolean isUnix() { + return !System.getProperty('os.name').toLowerCase().contains('windows') + } + + String getPermissions() { + if (!isUnix()) { + return "-rwxr-xr-x" + } + + def process = ["ls", "-ld", file.absolutePath].execute() + def result = process.inputStream.text + def error = process.errorStream.text + def retval = process.waitFor() + if (retval != 0) { + throw new RuntimeException("Could not list permissions for '$file': $error") + } + def perms = result.split()[0] + assert perms.matches("[d\\-][rwx\\-]{9}[@\\+]?") + return perms.substring(1, 10) + } + + void setPermissions(String permissions) { + if (!isUnix()) { + return + } + int m = toMode(permissions) + setMode(m) + } + + void setMode(int mode) { + def process = ["chmod", Integer.toOctalString(mode), file.absolutePath].execute() + def error = process.errorStream.text + def retval = process.waitFor() + if (retval != 0) { + throw new RuntimeException("Could not set permissions for '$file': $error") + } + } + + private int toMode(String permissions) { + int m = [6, 3, 0].inject(0) { mode, pos -> + mode |= permissions[9 - pos - 3] == 'r' ? 4 << pos : 0 + mode |= permissions[9 - pos - 2] == 'w' ? 2 << pos : 0 + mode |= permissions[9 - pos - 1] == 'x' ? 1 << pos : 0 + return mode + } + return m + } + + int getMode() { + return toMode(getPermissions()) + } + + void delete(boolean nativeTools) { + if (isUnix() && nativeTools) { + def process = ["rm", "-rf", file.absolutePath].execute() + def error = process.errorStream.text + def retval = process.waitFor() + if (retval != 0) { + throw new RuntimeException("Could not delete '$file': $error") + } + } else { + FileUtils.deleteQuietly(file); + } + } + + String readLink() { + def process = ["readlink", file.absolutePath].execute() + def error = process.errorStream.text + def retval = process.waitFor() + if (retval != 0) { + throw new RuntimeException("Could not read link '$file': $error") + } + return process.inputStream.text.trim() + } + + ExecOutput exec(List args) { + return execute(args, null) + } + + ExecOutput execute(List args, List env) { + def process = ([file.absolutePath] + args).execute(env, null) + String output = process.inputStream.text + String error = process.errorStream.text + if (process.waitFor() != 0) { + throw new RuntimeException("Could not execute $file. Error: $error, Output: $output") + } + return new ExecOutput(output, error) + } + + public void zipTo(TestFile zipFile, boolean nativeTools) { + if (nativeTools && isUnix()) { + def process = ['zip', zipFile.absolutePath, "-r", file.name].execute(null, zipFile.parentFile) + process.consumeProcessOutput(System.out, System.err) + assert process.waitFor() == 0 + } else { + Zip zip = new Zip(); + zip.setBasedir(file); + zip.setDestFile(zipFile); + zip.setProject(new Project()); + def whenEmpty = new Zip.WhenEmpty() + whenEmpty.setValue("create") + zip.setWhenempty(whenEmpty); + zip.execute(); + } + } + + void tarTo(TestFile tarFile, boolean nativeTools) { + if (nativeTools && isUnix()) { + def process = ['tar', "-cf", tarFile.absolutePath, file.name].execute(null, tarFile.parentFile) + process.consumeProcessOutput(System.out, System.err) + assert process.waitFor() == 0 + } else { + Tar tar = new Tar(); + tar.setBasedir(file); + tar.setDestFile(tarFile); + tar.setProject(new Project()) + tar.execute() + } + } + + static String substringBeforeLast(final String str, final String separator) { + if (isEmpty(str) || isEmpty(separator)) { + return str + } + final int pos = str.lastIndexOf(separator) + if (pos == INDEX_NOT_FOUND) { + return str + } + str.substring(0, pos) + } + + static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0 + } + + static final int INDEX_NOT_FOUND = -1 +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestNameTestDirectoryProvider.java b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestNameTestDirectoryProvider.java new file mode 100644 index 0000000..05db9a5 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestNameTestDirectoryProvider.java @@ -0,0 +1,134 @@ +package org.xbib.gradle.plugin.shadow.util.file; + +import org.junit.rules.MethodRule; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +import java.io.File; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A JUnit rule which provides a unique temporary folder for the test. + */ +public class TestNameTestDirectoryProvider implements MethodRule, TestRule, TestDirectoryProvider { + private TestFile dir; + private String prefix; + private static TestFile root; + private static AtomicInteger testCounter = new AtomicInteger(1); + + static { + // NOTE: the space in the directory name is intentional + root = new TestFile(new File("build/tmp/test files")); + } + + private String determinePrefix() { + StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); + for (StackTraceElement element : stackTrace) { + if (element.getClassName().endsWith("Test") || element.getClassName().endsWith("Spec")) { + return substringAfterLast(element.getClassName(), ".") + "/unknown-test-" + testCounter.getAndIncrement(); + } + } + return "unknown-test-class-" + testCounter.getAndIncrement(); + } + + public Statement apply(final Statement base, final FrameworkMethod method, final Object target) { + init(method.getName(), target.getClass().getSimpleName()); + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + getTestDirectory().maybeDeleteDir(); + // Don't delete on failure + } + }; + } + + public Statement apply(final Statement base, Description description) { + init(description.getMethodName(), description.getTestClass().getSimpleName()); + return new Statement() { + @Override + public void evaluate() throws Throwable { + base.evaluate(); + getTestDirectory().maybeDeleteDir(); + // Don't delete on failure + } + }; + } + + private void init(String methodName, String className) { + if (methodName == null) { + // must be a @ClassRule; use the rule's class name instead + methodName = getClass().getSimpleName(); + } + if (prefix == null) { + String safeMethodName = methodName.replaceAll("\\s", "_").replace(File.pathSeparator, "_").replace(":", "_"); + if (safeMethodName.length() > 64) { + safeMethodName = safeMethodName.substring(0, 32) + "..." + safeMethodName.substring(safeMethodName.length() - 32); + } + prefix = String.format("%s/%s", className, safeMethodName); + } + } + + public static TestNameTestDirectoryProvider newInstance() { + return new TestNameTestDirectoryProvider(); + } + + public static TestNameTestDirectoryProvider newInstance(FrameworkMethod method, Object target) { + TestNameTestDirectoryProvider testDirectoryProvider = new TestNameTestDirectoryProvider(); + testDirectoryProvider.init(method.getName(), target.getClass().getSimpleName()); + return testDirectoryProvider; + } + + public TestFile getTestDirectory() { + if (dir == null) { + if (prefix == null) { + // This can happen if this is used in a constructor or a @Before method. It also happens when using + // @RunWith(SomeRunner) when the runner does not support rules. + prefix = determinePrefix(); + } + for (int counter = 1; true; counter++) { + dir = root.file(counter == 1 ? prefix : String.format("%s%d", prefix, counter)); + if (dir.mkdirs()) { + break; + } + } + } + return dir; + } + + public TestFile file(Object... path) { + return getTestDirectory().file((Object[]) path); + } + + public TestFile createFile(Object... path) { + return file((Object[]) path).createFile(); + } + + public TestFile createDir(Object... path) { + return file((Object[]) path).createDir(); + } + + static String substringAfterLast(final String str, final String separator) { + if (isEmpty(str)) { + return str; + } + if (isEmpty(separator)) { + return EMPTY; + } + final int pos = str.lastIndexOf(separator); + if (pos == INDEX_NOT_FOUND || pos == str.length() - separator.length()) { + return EMPTY; + } + return str.substring(pos + separator.length()); + } + + static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + static final int INDEX_NOT_FOUND = -1; + + static final String EMPTY = ""; +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestWorkspaceBuilder.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestWorkspaceBuilder.groovy new file mode 100644 index 0000000..e9b4a43 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/file/TestWorkspaceBuilder.groovy @@ -0,0 +1,39 @@ +package org.xbib.gradle.plugin.shadow.util.file + +/** + * Used in TestFile.create(). + * + * Should be inner class of TestFile, but can't because Groovy has issues with inner classes as delegates. + */ +class TestWorkspaceBuilder { + def TestFile baseDir + + def TestWorkspaceBuilder(TestFile baseDir) { + this.baseDir = baseDir + } + + def apply(Closure cl) { + cl.delegate = this + cl.resolveStrategy = Closure.DELEGATE_FIRST + cl() + } + + def file(String name) { + TestFile file = baseDir.file(name) + file.write('some content') + file + } + + def setMode(int mode) { + baseDir.mode = mode + } + + def methodMissing(String name, Object args) { + if (args.length == 1 && args[0] instanceof Closure) { + baseDir.file(name).create(args[0]) + } + else { + throw new MissingMethodException(name, getClass(), args) + } + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/AbstractModule.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/AbstractModule.groovy new file mode 100644 index 0000000..0d5c918 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/AbstractModule.groovy @@ -0,0 +1,125 @@ +package org.xbib.gradle.plugin.shadow.util.repo + +import org.gradle.internal.UncheckedException +import org.xbib.gradle.plugin.shadow.util.file.TestFile + +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException + + +abstract class AbstractModule { + /** + * @param cl A closure that is passed a writer to use to generate the content. + */ + protected void publish(TestFile file, Closure cl) { + def hashBefore = file.exists() ? getHash(file, "sha1") : null + def tmpFile = file.parentFile.file("${file.name}.tmp") + + tmpFile.withWriter("utf-8") { + cl.call(it) + } + + def hashAfter = getHash(tmpFile, "sha1") + if (hashAfter == hashBefore) { + // Already published + return + } + + assert !file.exists() || file.delete() + assert tmpFile.renameTo(file) + onPublish(file) + } + + protected void publishWithStream(TestFile file, Closure cl) { + def hashBefore = file.exists() ? getHash(file, "sha1") : null + def tmpFile = file.parentFile.file("${file.name}.tmp") + + tmpFile.withOutputStream { + cl.call(it) + } + + def hashAfter = getHash(tmpFile, "sha1") + if (hashAfter == hashBefore) { + // Already published + return + } + + assert !file.exists() || file.delete() + assert tmpFile.renameTo(file) + onPublish(file) + } + + protected abstract onPublish(TestFile file) + + static TestFile getSha1File(TestFile file) { + getHashFile(file, "sha1") + } + + static TestFile sha1File(TestFile file) { + hashFile(file, "sha1", 40) + } + + static TestFile getMd5File(TestFile file) { + getHashFile(file, "md5") + } + + static TestFile md5File(TestFile file) { + hashFile(file, "md5", 32) + } + + private static TestFile hashFile(TestFile file, String algorithm, int len) { + def hashFile = getHashFile(file, algorithm) + def hash = getHash(file, algorithm) + hashFile.text = String.format("%0${len}x", hash) + return hashFile + } + + private static TestFile getHashFile(TestFile file, String algorithm) { + file.parentFile.file("${file.name}.${algorithm}") + } + + protected static BigInteger getHash(TestFile file, String algorithm) { + createHash(file, algorithm.toUpperCase()) + } + + static BigInteger createHash(File file, String algorithm) { + try { + return createHash(new FileInputStream(file), algorithm) + } catch (UncheckedIOException e) { + throw new UncheckedIOException(String.format("Failed to create %s hash for file %s.", + algorithm, file.getAbsolutePath()), e.getCause()) + } catch (FileNotFoundException e) { + throw new UncheckedIOException(e) + } + } + + static BigInteger createHash(InputStream instr, String algorithm) { + MessageDigest messageDigest + try { + messageDigest = createMessageDigest(algorithm) + byte[] buffer = new byte[4096] + try { + while (true) { + int nread = instr.read(buffer) + if (nread < 0) { + break + } + messageDigest.update(buffer, 0, nread) + } + } finally { + instr.close() + } + } catch (IOException e) { + throw new UncheckedIOException(e) + } + new BigInteger(1, messageDigest.digest()) + } + + private static MessageDigest createMessageDigest(String algorithm) { + try { + return MessageDigest.getInstance(algorithm) + } catch (NoSuchAlgorithmException e) { + throw UncheckedException.throwAsUncheckedException(e) + } + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/AbstractMavenModule.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/AbstractMavenModule.groovy new file mode 100644 index 0000000..8eb206d --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/AbstractMavenModule.groovy @@ -0,0 +1,330 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +import org.xbib.gradle.plugin.shadow.util.file.TestFile +import org.xbib.gradle.plugin.shadow.util.repo.AbstractModule +import groovy.xml.MarkupBuilder +import java.text.SimpleDateFormat + +abstract class AbstractMavenModule extends AbstractModule implements MavenModule { + protected static final String MAVEN_METADATA_FILE = "maven-metadata.xml" + final TestFile moduleDir + final String groupId + final String artifactId + final String version + String parentPomSection + String type = 'jar' + String packaging + int publishCount = 1 + private final List dependencies = [] + private final List artifacts = [] + final updateFormat = new SimpleDateFormat("yyyyMMddHHmmss") + final timestampFormat = new SimpleDateFormat("yyyyMMdd.HHmmss") + + AbstractMavenModule(TestFile moduleDir, String groupId, String artifactId, String version) { + this.moduleDir = moduleDir + this.groupId = groupId + this.artifactId = artifactId + this.version = version + } + + MavenModule parent(String group, String artifactId, String version) { + parentPomSection = """ + + ${group} + ${artifactId} + ${version} + +""" + return this + } + + TestFile getArtifactFile(Map options = [:]) { + if (version.endsWith("-SNAPSHOT") && !metaDataFile.exists() && uniqueSnapshots) { + def artifact = toArtifact(options) + return moduleDir.file("${artifactId}-${version}${artifact.classifier ? "-${artifact.classifier}" : ""}.${artifact.type}") + } + return artifactFile(options) + } + + abstract boolean getUniqueSnapshots() + + String getPublishArtifactVersion() { + if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) { + return "${version.replaceFirst('-SNAPSHOT$', '')}-${getUniqueSnapshotVersion()}" + } + return version + } + + private String getUniqueSnapshotVersion() { + assert uniqueSnapshots && version.endsWith('-SNAPSHOT') + if (metaDataFile.isFile()) { + def metaData = new XmlParser().parse(metaDataFile.assertIsFile()) + def timestamp = metaData.versioning.snapshot.timestamp[0].text().trim() + def build = metaData.versioning.snapshot.buildNumber[0].text().trim() + return "${timestamp}-${build}" + } + return "${timestampFormat.format(publishTimestamp)}-${publishCount}" + } + + MavenModule dependsOn(String... dependencyArtifactIds) { + for (String id : dependencyArtifactIds) { + dependsOn(groupId, id, '1.0') + } + return this + } + + MavenModule dependsOn(String group, String artifactId, String version, String type = null) { + this.dependencies << [groupId: group, artifactId: artifactId, version: version, type: type] + return this + } + + MavenModule hasPackaging(String packaging) { + this.packaging = packaging + return this + } + + /** + * Specifies the type of the main artifact. + */ + MavenModule hasType(String type) { + this.type = type + return this + } + + /** + * Adds an additional artifact to this module. + * @param options Can specify any of: type or classifier + */ + MavenModule artifact(Map options) { + artifacts << options + return this + } + + String getPackaging() { + return packaging + } + + List getDependencies() { + return dependencies + } + + List getArtifacts() { + return artifacts + } + + void assertNotPublished() { + pomFile.assertDoesNotExist() + } + + void assertPublished() { + assert pomFile.assertExists() + assert parsedPom.groupId == groupId + assert parsedPom.artifactId == artifactId + assert parsedPom.version == version + } + + void assertPublishedAsPomModule() { + assertPublished() + assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.pom") + assert parsedPom.packaging == "pom" + } + + void assertPublishedAsJavaModule() { + assertPublished() + assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.jar", "${artifactId}-${publishArtifactVersion}.pom") + assert parsedPom.packaging == null + } + + void assertPublishedAsWebModule() { + assertPublished() + assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.war", "${artifactId}-${publishArtifactVersion}.pom") + assert parsedPom.packaging == 'war' + } + + void assertPublishedAsEarModule() { + assertPublished() + assertArtifactsPublished("${artifactId}-${publishArtifactVersion}.ear", "${artifactId}-${publishArtifactVersion}.pom") + assert parsedPom.packaging == 'ear' + } + + /** + * Asserts that exactly the given artifacts have been deployed, along with their checksum files + */ + void assertArtifactsPublished(String... names) { + def artifactNames = names as Set + if (publishesMetaDataFile()) { + artifactNames.add(MAVEN_METADATA_FILE) + } + assert moduleDir.isDirectory() + Set actual = moduleDir.list() as Set + for (name in artifactNames) { + assert actual.remove(name) + + if(publishesHashFiles()) { + assert actual.remove("${name}.md5" as String) + assert actual.remove("${name}.sha1" as String) + } + } + assert actual.isEmpty() + } + + //abstract String getPublishArtifactVersion() + + MavenPom getParsedPom() { + return new MavenPom(pomFile) + } + + DefaultMavenMetaData getRootMetaData() { + new DefaultMavenMetaData(rootMetaDataFile) + } + + TestFile getPomFile() { + return moduleDir.file("$artifactId-${publishArtifactVersion}.pom") + } + + TestFile getMetaDataFile() { + moduleDir.file(MAVEN_METADATA_FILE) + } + + TestFile getRootMetaDataFile() { + moduleDir.parentFile.file(MAVEN_METADATA_FILE) + } + + TestFile artifactFile(Map options) { + def artifact = toArtifact(options) + def fileName = "$artifactId-${publishArtifactVersion}.${artifact.type}" + if (artifact.classifier) { + fileName = "$artifactId-$publishArtifactVersion-${artifact.classifier}.${artifact.type}" + } + return moduleDir.file(fileName) + } + + MavenModule publishWithChangedContent() { + publishCount++ + return publish() + } + + protected Map toArtifact(Map options) { + options = new HashMap(options) + def artifact = [type: options.remove('type') ?: type, classifier: options.remove('classifier') ?: null] + assert options.isEmpty(): "Unknown options : ${options.keySet()}" + return artifact + } + + Date getPublishTimestamp() { + return new Date(updateFormat.parse("20100101120000").time + publishCount * 1000) + } + + MavenModule publishPom() { + moduleDir.createDir() + def rootMavenMetaData = getRootMetaDataFile() + + updateRootMavenMetaData(rootMavenMetaData) + + if (publishesMetaDataFile()) { + publish(metaDataFile) { Writer writer -> + writer << getMetaDataFileContent() + } + } + + publish(pomFile) { Writer writer -> + def pomPackaging = packaging ?: type; + writer << """ + + + 4.0.0 + $groupId + $artifactId + $pomPackaging + $version + Published on $publishTimestamp""" + + if (parentPomSection) { + writer << "\n$parentPomSection\n" + } + + if (!dependencies.empty) { + writer << """ + """ + } + + dependencies.each { dependency -> + def typeAttribute = dependency['type'] == null ? "" : "$dependency.type" + writer << """ + + $dependency.groupId + $dependency.artifactId + $dependency.version + $typeAttribute + """ + } + + if (!dependencies.empty) { + writer << """ + """ + } + + writer << "\n" + } + return this + } + + private void updateRootMavenMetaData(TestFile rootMavenMetaData) { + def allVersions = rootMavenMetaData.exists() ? new XmlParser().parseText(rootMavenMetaData.text).versioning.versions.version*.value().flatten() : [] + allVersions << version; + publish(rootMavenMetaData) { Writer writer -> + def builder = new MarkupBuilder(writer) + builder.metadata { + groupId(groupId) + artifactId(artifactId) + version(allVersions.max()) + versioning { + if (uniqueSnapshots && version.endsWith("-SNAPSHOT")) { + snapshot { + timestamp(timestampFormat.format(publishTimestamp)) + buildNumber(publishCount) + lastUpdated(updateFormat.format(publishTimestamp)) + } + } else { + versions { + allVersions.each {currVersion -> + version(currVersion) + } + } + } + } + } + } + } + + abstract String getMetaDataFileContent() + + MavenModule publish() { + + publishPom() + artifacts.each { artifact -> + publishArtifact(artifact) + } + publishArtifact([:]) + return this + } + + File publishArtifact(Map artifact) { + def artifactFile = artifactFile(artifact) + if (type == 'pom') { + return artifactFile + } + publish(artifactFile) { Writer writer -> + writer << "${artifactFile.name} : $artifactContent" + } + return artifactFile + } + + protected String getArtifactContent() { + // Some content to include in each artifact, so that its size and content varies on each publish + return (0..publishCount).join("-") + } + + protected abstract boolean publishesMetaDataFile() + protected abstract boolean publishesHashFiles() +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/DefaultMavenMetaData.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/DefaultMavenMetaData.groovy new file mode 100644 index 0000000..b343af2 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/DefaultMavenMetaData.groovy @@ -0,0 +1,33 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +/** + * http://maven.apache.org/ref/3.0.1/maven-repository-metadata/repository-metadata.html + */ +class DefaultMavenMetaData implements MavenMetaData{ + + String text + + String groupId + String artifactId + String version + + List versions = [] + String lastUpdated + + DefaultMavenMetaData(File file) { + text = file.text + def xml = new XmlParser().parseText(text) + + groupId = xml.groupId[0]?.text() + artifactId = xml.artifactId[0]?.text() + version = xml.version[0]?.text() + + def versioning = xml.versioning[0] + + lastUpdated = versioning.lastUpdated[0]?.text() + + versioning.versions[0].version.each { + versions << it.text() + } + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenDependency.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenDependency.groovy new file mode 100644 index 0000000..b4580b7 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenDependency.groovy @@ -0,0 +1,19 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +class MavenDependency { + String groupId + String artifactId + String version + String classifier + String type + + MavenDependency hasType(def type) { + assert this.type == type + return this + } + + @Override + public String toString() { + return String.format("MavenDependency %s:%s:%s:%s@%s", groupId, artifactId, version, classifier, type) + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenFileModule.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenFileModule.groovy new file mode 100644 index 0000000..469f1b2 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenFileModule.groovy @@ -0,0 +1,55 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +import org.xbib.gradle.plugin.shadow.util.file.TestFile + +class MavenFileModule extends AbstractMavenModule { + private boolean uniqueSnapshots = true; + + MavenFileModule(TestFile moduleDir, String groupId, String artifactId, String version) { + super(moduleDir, groupId, artifactId, version) + } + + boolean getUniqueSnapshots() { + return uniqueSnapshots + } + + MavenModule withNonUniqueSnapshots() { + uniqueSnapshots = false; + return this; + } + + @Override + String getMetaDataFileContent() { + """ + + + $groupId + $artifactId + $version + + + ${timestampFormat.format(publishTimestamp)} + $publishCount + + ${updateFormat.format(publishTimestamp)} + + +""" + } + + @Override + protected onPublish(TestFile file) { + sha1File(file) + md5File(file) + } + + @Override + protected boolean publishesMetaDataFile() { + uniqueSnapshots && version.endsWith("-SNAPSHOT") + } + + @Override + protected boolean publishesHashFiles() { + true + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenFileRepository.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenFileRepository.groovy new file mode 100644 index 0000000..7216e08 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenFileRepository.groovy @@ -0,0 +1,23 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +import org.xbib.gradle.plugin.shadow.util.file.TestFile + +/** + * A fixture for dealing with file Maven repositories. + */ +class MavenFileRepository implements MavenRepository { + final TestFile rootDir + + MavenFileRepository(TestFile rootDir) { + this.rootDir = rootDir + } + + URI getUri() { + return rootDir.toURI() + } + + MavenFileModule module(String groupId, String artifactId, Object version = '1.0') { + def artifactDir = rootDir.file("${groupId.replace('.', '/')}/$artifactId/$version") + return new MavenFileModule(artifactDir, groupId, artifactId, version as String) + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenMetaData.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenMetaData.groovy new file mode 100644 index 0000000..3c8bcb1 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenMetaData.groovy @@ -0,0 +1,6 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +interface MavenMetaData { + List getVersions(); + +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenModule.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenModule.groovy new file mode 100644 index 0000000..b5ad3b8 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenModule.groovy @@ -0,0 +1,45 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +import org.xbib.gradle.plugin.shadow.util.file.TestFile + +interface MavenModule { + /** + * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module. Publishes only those artifacts whose content has + * changed since the last call to {@code #publish()}. + */ + MavenModule publish() + + /** + * Publishes the pom.xml only + */ + MavenModule publishPom() + + /** + * Publishes the pom.xml plus main artifact, plus any additional artifacts for this module, with different content (and size) to any + * previous publication. + */ + MavenModule publishWithChangedContent() + + MavenModule withNonUniqueSnapshots() + + MavenModule parent(String group, String artifactId, String version) + + MavenModule dependsOn(String group, String artifactId, String version) + + MavenModule hasPackaging(String packaging) + + /** + * Sets the type of the main artifact for this module. + */ + MavenModule hasType(String type) + + TestFile getPomFile() + + TestFile getArtifactFile() + + TestFile getMetaDataFile() + + MavenPom getParsedPom() + + MavenMetaData getRootMetaData() +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenPom.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenPom.groovy new file mode 100644 index 0000000..dcc9002 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenPom.groovy @@ -0,0 +1,42 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +class MavenPom { + String groupId + String artifactId + String version + String packaging + String description + final Map scopes = [:] + + MavenPom(File pomFile) { + if (pomFile.exists()){ + def pom = new XmlParser().parse(pomFile) + + groupId = pom.groupId[0]?.text() + artifactId = pom.artifactId[0]?.text() + version = pom.version[0]?.text() + packaging = pom.packaging[0]?.text() + description = pom.description[0]?.text() + + pom.dependencies.dependency.each { dep -> + def scopeElement = dep.scope + def scopeName = scopeElement ? scopeElement.text() : "runtime" + def scope = scopes[scopeName] + if (!scope) { + scope = new MavenScope() + scopes[scopeName] = scope + } + MavenDependency mavenDependency = new MavenDependency( + groupId: dep.groupId.text(), + artifactId: dep.artifactId.text(), + version: dep.version.text(), + classifier: dep.classifier ? dep.classifier.text() : null, + type: dep.type ? dep.type.text() : null + ) + def key = "${mavenDependency.groupId}:${mavenDependency.artifactId}:${mavenDependency.version}" + key += mavenDependency.classifier ? ":${mavenDependency.classifier}" : "" + scope.dependencies[key] = mavenDependency + } + } + } +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenRepository.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenRepository.groovy new file mode 100644 index 0000000..53bac45 --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenRepository.groovy @@ -0,0 +1,12 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +/** + * A fixture for dealing with Maven repositories. + */ +interface MavenRepository { + URI getUri() + + MavenModule module(String groupId, String artifactId) + + MavenModule module(String groupId, String artifactId, Object version) +} diff --git a/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenScope.groovy b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenScope.groovy new file mode 100644 index 0000000..ae0c1bc --- /dev/null +++ b/gradle-plugin-shadow/src/test/groovy/org/xbib/gradle/plugin/shadow/util/repo/maven/MavenScope.groovy @@ -0,0 +1,44 @@ +package org.xbib.gradle.plugin.shadow.util.repo.maven + +class MavenScope { + Map dependencies = [:] + + void assertDependsOn(String[] expected) { + assert dependencies.size() == expected.length + expected.each { + String key = substringBefore(it, "@") + def dependency = expectDependency(key) + + String type = null + if (it != key) { + type = StringUtils.substringAfter(it, "@") + } + assert dependency.hasType(type) + } + } + + MavenDependency expectDependency(String key) { + final dependency = dependencies[key] + if (dependency == null) { + throw new AssertionError("Could not find expected dependency $key. Actual: ${dependencies.values()}") + } + return dependency + } + + static String substringBeforeLast(final String str, final String separator) { + if (isEmpty(str) || isEmpty(separator)) { + return str + } + final int pos = str.lastIndexOf(separator); + if (pos == INDEX_NOT_FOUND) { + return str + } + return str.substring(0, pos) + } + + static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0 + } + + static final int INDEX_NOT_FOUND = -1 +} diff --git a/gradle-plugin-shadow/src/test/jars/plexus-utils-1.4.1.jar b/gradle-plugin-shadow/src/test/jars/plexus-utils-1.4.1.jar new file mode 100644 index 0000000000000000000000000000000000000000..690fc044352971ee7bd3926433cb94449a2fe3db GIT binary patch literal 188648 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 !IW@E6xf+p_v8~J2E7! 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 ZiPmb76=Ewli&KF45oX>c2|m`p{{iiGhC2WN literal 0 HcmV?d00001 diff --git a/gradle-plugin-shadow/src/test/resources/asm-6.0_BETA.jar b/gradle-plugin-shadow/src/test/resources/asm-6.0_BETA.jar new file mode 100644 index 0000000000000000000000000000000000000000..e83d80da821badfb9ec951064903bca147cac146 GIT binary patch literal 56452 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 z zUs}Z51!`pU&ebQEUEln5HJ^^vxlU7So7!0WHN;eQg&K<&bq4^prCqkCNVca*c4y%q zZx!#_zYozHi&-P#h*RQaq>b=wPq{AN4&VPbi+%m^squwGkgjhrbI~)C)2nlO$+*_U zi?totaU`&mSx)0S9~Rumli0WbfcW+dT?HZ#(GNz8xg|&>qT8jL&AkUqEUQVrUQb`){BZvLEk$ zZaB~^I5wmQ>DG<-^Dm$cASd?Dy7xSwhw%yW&bf!!&rkMF1bpj%?#spOCiu(;fC1Ek z2!eWWZiV^@MEP8mYifMk09?T2epCQ;(8>UP#dN4|G(Z4Q5yakGgRu?8U&;0Ly0`SQ z5{se>#?QsH9Up%i6BhM6I_jQDe85Z0v@N>Fapf5Z00sPwzeR|Ma5+yh|u$NKA$zKa7v z_DE*G=!0W}{B+$>xA{38<)+KJDVqG$b`sZP%WovE($BpV-$WmI%Y3*U<#aWDy1+6(4KzReO)k~DGf;caZ1>{}iXZjUXwR%K7u}cbJ+ptLw}+$? z`@;@{52ITZ$N}Wqqc#5$+dJ6vL+1niWZy&G^F!f-{xsW5-19@`gV@=i@}_zx-CNkb z>YovK^MlSs_{sw~-TJBmJpd;CGodT6xqqMhkZgC`h&s`?DS4n{Vde5ZYJxJ|`9xbG z=%MWbFZvhEzhL*+a%NEYP`kx}H11XQkXL@xJb@=w{W`!Od-$EA?ig2owBBqvWZKWHh?RTvj3#_8*SQarut=jvDJUl_gy;=s6q9B_;v$e_b8y~ z`^2DopnN+4vhE7NAMOI~n46g0NEgqDds_fM7(MW>4!{tA0ICP;GY>!pxPjG+xWS|L z#(wA6o7kg((F4t;=!E?EkaMSh@99_T_oI480m=o{2>MO+s|`><-2}#BcK_s41!@90 z_AYj5`&R}^V0J+G<$$8Vwtn`$66hW@-x$CY;Hb=NdJ%}1qU)JR}xBi;HehNU@&K?P16TqOqCeRMam)E-#@U4{y z-hpZf-U0u9lbMIs8*>w-8`l=52claNSOr+>f7a*IL+Zx-B-?B0>!Evh1-b?A{QXSm zzX^JV>4EK51a1MOf_A`iN#AvW!h6qq^bu7e0?i2M}< z`dDgvLU2~fK*9k{biUqN+2A)Ua^=?Cz1iL9K)DLprmz2o)+E&(TMUK;0ja?Hp8x~O z9`^qiiJ}hcovDJ(&%Tpt))P~s!Mt3Qu4GUsvE2V#<fyD}XJCagvWc2J1=w z(%W0eCNVEz<;+F?GIr`Y?5v21mA+UY)-_c+^2c!*h)v_X3{D4Q3%e{5Ft^90N17(i z!c>|ybR{`FFlS&)FW^eUr;Ul*`kN(f6CbA(mR$6r-7P8@r>zS=CKG#fI)jgZeL-!o zJ$sexYBjt2CDLo8#TI@@p?&m>n%K6G?1PoBYQQEv>ys~cM-D&xJnKYI0qok)ldP)| z-O|l5T6Fz|rn^=K!6V{U%C4dHwd0<=B*sfmAj>J>reOMSdHSDF)Npon~a#gB~DaO!JaWJm{20tsTRAf^_7T)S&cQ44#q%_^xqgQ{Ep1(JZP?GA>GZ6J9zW__aIw#y zcVene>2T+d#mh7CghfU)d2xMk%t&X?&MVX{K7O0F+RhoaK;R+f-|nue|KJ-##DEN{qw zk3WDM=!iB|FwiPX9EFNS^T*SLlqI9Z1%>H-&iFSzyl5i?Cb3wDniIT*woMMQg-kA~ zCgU=xY5vGhk9p-?cOixt&(?rdo--@loH}7G%mQ_uvS^kv3(_>KIVO1+F$px;#^HlL zytMD)D8${JB@523pvY8lER(wd0)ofa6BhOk9+93S%+>(z*qG?2PsjrKbkiS;XxCZ@ zYDMtSu_UhNzZtA`RzWwi*7<5#>l5D<8`GS$cEWct2^2uHW$H&lzsfM^2gE^#$k?Yt zS2Op)lLI@E`AmbTqwI^2`D}wKq8JQbmBXT7f02LgH3#uL3^PkdR#JSSgN^h)lC|L?1VV@@iUC=XfEA+JL zF3D`gpE&S9`-mSxEI5BPA^LX}cZqAM?HOql2!ELUW{^DmomKn?tsE^(py@|yZ#p<{if3IBo#gR&nTP}N0kxq;8r_9UHRe=~b+L%IO|*w>`rm`;;` z8l+7bfJ#6O@~44zYXM?i)BiAutc(I)DK6Jz}>7TuzG+ zVyCv3*FJ3*Iba9*J!cmYV^wbc!#-@68+?Zhh~Gy*YY*aU^S8zd5CpLzj>;p>AOo-o z2!+BYbC22;1Lr3KQufV2;Zp!P`{<#3B><#-c2GI}<9(>SSTaxGBnL$9^o~kEPTgR? zOnqL4=AiDw+Zq^E^jVbo?jM?>?M?p6BwH$faO3!D{Mm@Rs{PrBwsikw#_KBm(TcOB z{5KJ|t^S8vJXh~eDoHf-gqvga>4xwPx=+g~SpIWlx6p%?hh5S8G4Cn)hl<(v?nOZzFC3yG6Snb>|52P^-^zg-KQ zwTF0zFl6e=owr3wXq%$BxrEB4xmcbrI2lVFy`EydyX zlZ!%0vW;mH$zHDD!}Yn5XK}w3z7julqs}pc_Btc3zU)##&i3``U4i}HE6j9oyAi&T z!(7kwT5Vl`yCSWtLHVhyv+U*XBm)Uuh8A2!%WNyG6bP+rT594 zG`RZeDb=-m8RZysU=Sf`jYnDsFg*XUgleEK|AKyVOzraJAKxB49$F5)lC(}W>d*kx zpnqh(7lUjP4$ENxPMRxVhYy^LxofYAaRt=^>3(d%wnI4ob@Y%^0~NBUxvt4wyLE^j z7N-@|Bw12hg4=Snm%!z(TsR>0U-ogG1#UWmblWv zV223hz(TJZ!&srmrk zOylEI<70ENH)tD;h$+1wH?s%jn`nf1)Gf7y+^L&X$EZ8o3p96 zs1^^DZRLx_b+~u>DaY>EBOkqx?gcjLNa+cS*j+I0uMidKy1T65C-2o`^Q^1|s1rn^ znBf5JQ?YwKTJ)kV$;_M}>)xXrV~lRjYA<*nL+CA41-@yN_AC6QbOYwmhI@k+_mvff(Ytm_DmDzNY ze1WFXtNeSm&wv(>&4C@$hI2a!f!P5)vxfCR;g-;z8B-T#6f55MV73Zf(&<|O`+DVT zoci8gO{2z@pkaDw&w^=X zEBScu0wa;1pBscy@Sd65$q@qnF3WsHECS$5p~$&euA-He(Ocq??mx) zsmD&f=|GgS#d%B6BVh<*x4F!Oi7$uu-$wa-=11jY)yswojTPBmeYPvbT^9m*n&CVY^kg~$Fkg(>O}dZ3Uf55S?)ZD;U~*251oW@(p}<3y9Eow zHKQ#`+fQur$3@SasTUY;>@iU zQ^9XOSm`%lgYf?g*Z7a?yS#z3`TyY;Dapv;s-f}D9Ms)9LnWdukT>HJ+E^lj70U=F z2#Q-Dps;`2r-N#Uvx32~A+M$|H;$8!=T}^}N9RWrM~W+82opD>71A`E5}cOv|MSa# z*iO$Y3YZ<@em=;4$APBW0`|Z)AH7ehsq2rnzKn^wQmA=x0YuF$I}9_8_#8 zLDAt57aM^v?DRE*7fo>W+WN08RHnjwofQUp5OYEHZ||U|&sGlFscf=jnIl=|%(OAA z)8e1vI!kD%4AC)km^DR{YVApsSP?RnjP8>=S=z%prF3PB@`Ri&wKNm}Z87C!;i74S z#gf{XI=NAiX|p05nU)nR+ZH*5pvuIw)?>L2DpZ2+N^`~!-kcOml@_)tE(nXSY4(e# z2Z9xW=I#S_*&+5$oVi3cO8e^%?t;$@Ew)66_REk={;cB+tYn>v!^f{2~H}V`1~5-fQTZACnTFeh89K z?8tj!+U6a>D|h*p=rOc$-N5)Dew1Tj;0ANZ;xIexyfMuk+_%nwfOJmObCTf6DstnL zjkJB5mS@DsF{*aph7;#-c8$m@#%_%96&6EW`wLy9S#`u%w&H?uTm#?f+UVLxPklK_ zbG7HNw)Fh|V<)|WQ`^QH7t|5wZ|$Y=jvk|{gAj3A61#84#(@2rgR{Nah!)a^(e^6; z{ObD^{#>C)hI8q!cq=^Vsa7RTZNqO?Ye+tI*3GJ%Scy~mbYUKCb9_;oD7?+!5*<#) zwWUI!6WTgZ6W5J5B2Pu#iQL1ok@4y&xaYrhp?#G{X@|b|AMZCj?Eeq9`H%glXku#e zZ9Hc5f9mMy>!)LcBt;4 zDB;57V{yh3$KsBp9tj=^KgNVmk_!d%fnyz~IDcLJxIY+$rKW1*+tx%$f zLLRD2J_La_hjiewn>)|X1CXu$#p^NP3z|AjS8N-uX$nnbqwt*#3;I_fmSvff1}Wg< z9(%4s);KCO63p-$br~5(nl@YJ=^5vz#f<+{th(C9;Iyge4KXShBe8K3cZb_ z>=D~hXW6sSIKjp$IwG&$3%btV#`OQM%FYBF%CG(7qsYFG5Wfl8mmy10B>R?qj|gK6 zGZ`aP@9+N~o<{%of39n;YsPimpX+`+ z=Q-y-=ef`Q-K?tKz)jHBL0oE)7w2K}V~r|H>e7T9y`JH5KHkKK$;r>c^A6{|a_OOU z@Inp;IVjCvdOq`ww~f_2s$oW>k2wgKOFdHcSB57!=GfAe^I8MDRTSIPGac(EakKmJ zlM=T!bq?vQO;nWKoG)*;a#FTRO(`}tl~PxZ{pO^iUa#v^z{_9TVeW3g79Oq0M?~bY zs~c=DJ~wJ^7Ys>{vAcMkc=-^lu|W)5kg<->M>%4utWoaxZyB=4)-|5nPw@=}tv$)e#=fTT`jVZn-qg^-r>fz&70@ z^Isl@QHMIn#FR#@p9-~EZ=lkxd>--n?d!wXA3UL%*sTM3ufoNg1B9NHZ0`%!Nxde5 zIzMM^lyfo4-1@-!Rt? zxEkmuUV#n>8TBnUN$7j?tkW!92x(uZxpr$@G;&>wJA9467=i$5d>#b|D73pY%K3t;3nswTS=!0ZN19}7{y!8mS0{w)KZRT~ zEDcdSmQWS*WOwI)n`g{~J-M58*2+}!h8RuEo$)D(v+bqSB%<8kz-BwWU&o1-7xDnBY!)_pC(AE+>^wDnT>d0$jg zFIx|qWG`lJ7=P}lZ3>Ga^x9an;KuA|Z&iBLNe6|7q3>P}8AR>rd`Y}CZ!>4|5Pkm1 zbVX;zzbc6r94a$PPeKoISU0yig&Hu?-bRlA2kGOeul)!6D(%?Jwku{W@#Xc;DK|tpZ?JgPJ-#^=Uxe%FU3=~)J->OC z@BQr(o>mtD_~30fzvlzX;!$i(lxeJLl4zuW&BUzj;30<;RiTdJm=~W1bDq3k?X4S~ z?ln%Z7+ACq^Jp1J5$ZH7{TtSyF5oSw`1b&^WwYuj#s!sa>-Dw%N={#j44kXCckS(R z-KW}^CuuYI+{&jF(?>aC62v%4~BgTNCTDC_P!Hs6f;{MK1ee_wc%uAU*lRL|17{1dFXT4tQm(<2^ zP0=O@`zBsFJ+^+}n$<@y$q*`0Mp)UcS_zzaSQ?c(HO@(DH*;5A;a*qtT zGd_tuF00>l4DrNSR0SFw_|k-E*o8)lu`3j=e}}bq`uxprglvd9s$X)seqCw;+PqPt z`Hm;2?9^zJW{N#$Cvw|eyfa`uA5)-t0QWJT>0r5NVp?!iVL(fvV|{_4fPSP5@k5`l zZ{s<7EkHa5&n!3v_bMPfZaULWE!PAUa7Mli5>AEER#O)$zA&32!1@W!#1+ZK=Yq)0 z?KFkIYJzGQYsUFMF|74Yu7=4Cg@ovC@@@6!eQ_EYdwk|{Aj-6QBQsB7Endr~TfT7G zw*jR&`zloKh>ooM`5$*?siz%OQ?*(hS>_F?YYO^OL(hxmPbIo`l-L(ExalzbF!xyg zkP5TH#l^RczPVhd@d;+tvj9~py7X-z>_#Vdn{4cQy?}#LKPUTAngCAo5_~D>{Znjt z@QF_i$ZhKG8^?nn$LFV?Ah-Dq)n5D*{aHAg&&D%jOd3jX>EHBEB)rgq0b>aPNN$t; zZ-;|j`H~sw=Zx9apfQu4^aciY9u&n^Y9<9zhb!=9dbU}2X(^hAw53a3$0SF3k3F=z zk?%6i+)uAj7(cO5lqiR*JO_VD8+_EgcO$ntr)O(zV}>(4bl%hR(qK(#rnM&`T;7c$ za;|p$fr$6`Cx;q`g;an4k1PI@50n^VB0mfT;(bFz7FsFPo?3lnc~-_9(A(3In6Vp_-q}Ls(ETT;k{Dl**AZAz11eE|=R zba-4%jc!yI1A*H^R z8qpd!_#5nfC#{wSzJ_SDOvFy?U;5Jru5`sF`mXHqPCrJ`QB`gJECGXM>=~uzH;UG z@aA~gm6yH8%aRGK?{$KrvG)es%LKyGXgG?6=#D>68lItC7L2GA!Ct4V%V~rOV=WP- ze|;TUN}n_?TRngI-8;+@^ejW_s0VyV(!avi|LmbHPypMi6pq~wOg={cpUG!uPGyGW zCnI)KmOLMNDSxqI``WmD38PeJO^8f%n(S;>xcG%UoRy_wpWYq!sk6JG6QTM%QrMo& zFa&bAOVMu`gX4kgr{9)ye}4-FS4!X7TAbm8AebVZsGr)?eb&{pPcF*Tw(dQ!ZOK;!|UiaI_gJbKf zvurEt+}@UNX}+0OcWz!t2oba1G$UHR#Gb)VN+(uJ;hMV;K`q`^Q-x{psdqAl$PF6y z*9j@1Rz$;W`406l1nky}iO0Mf3Y(dgmorASx(ZI0Vp9U%!rw}aVSd&rKV-BLT@+o? zEI3XVDn1rL6Q50|_c|@bq=5ab?xXS)0Xw=A7V`oFjGx7qG|Ct;9ch93)7m=26a5wR(gCSa8`5gvVQHLiF6>r~EB(b8iqa6s*B?el^#W zQcdX*Aw(_1j1b4E}ccQR<~`|lOI(YJlB@wZ_B)8 zpJ$)%(9xuJv@}~;IhGJ62ycn($!kdEziq$uu`Ztp6E;~PDCIsl<YsfThx(q7tGs+sGC%V)?u|t7o2VBi1{C|C z5GVvfLj!39MsSZ4Jdr*?9ezN?5BMuEjPGiOS{7*k<=Km>N-SOB+w$EgQlT&;lB0qURdAIvlLW>_QuSKDv*A8le1 zlAE0zPs7{E&u`~?0t{rIt#0539Bc>X2tg7J_;x~hPv9T#OUAQuZNq~QNa!)Jiy5kW z6905d&b??W6$1JX8*p2IqLVw?5i7Dl6cXbai241|`PCW0Lh>Y9v^81SZ?f{>{~%aM zokYajkRy_0)xjjNXgP^gYfFywyM#HI2$r%Y5x)T)==bjTo3J$)2v%z)0Ue#l0?|%B zdpsIo$sQ7_7)1`XQ@{u87FZpH#EL=h&D!G`1M6~-V73@?Fp^3K*e$U90EzT0fE;Ou z7y%dsuCz@;@rIH^k*T{4MuMxjl923Cdn5n!q=HX?k%(eNWQo6@2Ln3^J_>fGdu3UVud*Bo+671Roupr5FnCjpL{j-bNMyIjz + + + + + org.apache.maven.wagon.Wagon + http + org.apache.maven.wagon.providers.http.LightweightHttpWagon + per-lookup + LightweightHttpWagon + false + + + org.apache.maven.wagon.Wagon + https + org.apache.maven.wagon.providers.http.LightweightHttpsWagon + per-lookup + LIghtweightHttpsWagon + false + + + + User-Agent + Apache Maven/${project.version} + + + + + + + \ No newline at end of file diff --git a/gradle-plugin-shadow/src/test/resources/components-2.xml b/gradle-plugin-shadow/src/test/resources/components-2.xml new file mode 100644 index 0000000..c5eff71 --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/components-2.xml @@ -0,0 +1,48 @@ + + + + + + org.apache.maven.wagon.Wagon + http + org.apache.maven.wagon.providers.http.LightweightHttpWagon + per-lookup + LightweightHttpWagon + false + + + + User-Agent + Apache Maven/${project.version} + + + + + + org.apache.maven.wagon.Wagon + https + org.apache.maven.wagon.providers.http.LightweightHttpsWagon + per-lookup + LIghtweightHttpsWagon + false + + + + \ No newline at end of file diff --git a/gradle-plugin-shadow/src/test/resources/components-expected.xml b/gradle-plugin-shadow/src/test/resources/components-expected.xml new file mode 100644 index 0000000..4b69cda --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/components-expected.xml @@ -0,0 +1,55 @@ + + + + + + org.apache.maven.wagon.Wagon + http + org.apache.maven.wagon.providers.http.LightweightHttpWagon + per-lookup + LightweightHttpWagon + false + + + + User-Agent + Apache Maven/${project.version} + + + + + + org.apache.maven.wagon.Wagon + https + org.apache.maven.wagon.providers.http.LightweightHttpsWagon + per-lookup + LIghtweightHttpsWagon + false + + + + User-Agent + Apache Maven/${project.version} + + + + + + \ No newline at end of file diff --git a/gradle-plugin-shadow/src/test/resources/jar1-missing.jar b/gradle-plugin-shadow/src/test/resources/jar1-missing.jar new file mode 100644 index 0000000000000000000000000000000000000000..e71875b0ee3edcd9ea018dff3b26a2bf11b7fb06 GIT binary patch literal 87105 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 literal 0 HcmV?d00001 diff --git a/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/LICENSE.txt b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/LICENSE.txt new file mode 100644 index 0000000..6b0b127 --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/MANIFEST.MF b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/MANIFEST.MF new file mode 100644 index 0000000..fbdfcfb --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: 1.6.0_20 (Apple Inc.) + diff --git a/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/NOTICE.txt b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/NOTICE.txt new file mode 100644 index 0000000..ce3b94a --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/NOTICE.txt @@ -0,0 +1,6 @@ +Apache Jakarta Commons IO +Copyright 2001-2007 The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). + diff --git a/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/maven/commons-io/commons-io/pom.properties b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/maven/commons-io/commons-io/pom.properties new file mode 100644 index 0000000..d8e1adb --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/maven/commons-io/commons-io/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Wed Jun 27 03:51:16 CEST 2007 +version=1.3.2 +groupId=commons-io +artifactId=commons-io diff --git a/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/maven/commons-io/commons-io/pom.xml b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/maven/commons-io/commons-io/pom.xml new file mode 100644 index 0000000..9f57f8d --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1-missing/META-INF/maven/commons-io/commons-io/pom.xml @@ -0,0 +1,353 @@ + + + + + org.apache.commons + commons-parent + 3 + + 4.0.0 + commons-io + commons-io + 1.3.2 + Commons IO + + 2002 + + Commons-IO contains utility classes, stream implementations, file filters, and endian classes. + + + http://jakarta.apache.org/commons/io/ + + + jira + http://issues.apache.org/jira/browse/IO + + + + scm:svn:scm:svn:https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk + scm:svn:scm:svn:https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk + http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk + + + + + Scott Sanders + sanders + sanders@apache.org + + + Java Developer + + + + dIon Gillard + dion + dion@apache.org + + + Java Developer + + + + Nicola Ken Barozzi + nicolaken + nicolaken@apache.org + + + Java Developer + + + + Henri Yandell + bayard + bayard@apache.org + + + Java Developer + + + + Stephen Colebourne + scolebourne + + + Java Developer + + 0 + + + Jeremias Maerki + jeremias + jeremias@apache.org + + + Java Developer + + +1 + + + Matthew Hawthorne + matth + matth@apache.org + + + Java Developer + + + + Martin Cooper + martinc + martinc@apache.org + + + Java Developer + + + + Rob Oxspring + roxspring + roxspring@apache.org + + + Java Developer + + + + Jochen Wiedmann + jochen + jochen.wiedmann@gmail.com + + + + + + Rahul Akolkar + + + Jason Anderson + + + Nathan Beyer + + + Emmanuel Bourg + + + Chris Eldredge + + + Magnus Grimsell + + + Jim Harrington + + + Thomas Ledoux + + + Andy Lehane + + + Marcelo Liberato + + + Alban Peignier + alban.peignier at free.fr + + + Niall Pemberton + + + Ian Springer + + + Masato Tezuka + + + James Urie + + + Frank W. Zammetti + + + + + + junit + junit + 3.8.1 + test + + + + + src/java + src/test + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Test* + + + **/*AbstractTestCase* + **/AllIOTestSuite* + **/PackageTestSuite* + **/testtools/** + + + **/*$* + + + + + maven-assembly-plugin + + + src/main/assembly/bin.xml + src/main/assembly/src.xml + + gnu + + + + + + + + + org.apache.maven.plugins + maven-changes-plugin + + %URL%/../%ISSUE% + + + + + changes-report + jira-report + + + + + + + + + + release + + + + maven-site-plugin + + + + site + + package + + + + + maven-antrun-plugin + + + + run + + package + + + + + + + + + + + + + + + + + + + + + + + + + + + + maven-assembly-plugin + + + + attached + + package + + + + + + + + rc + + + + maven-site-plugin + + + + site + + package + + + + + maven-assembly-plugin + + + + attached + + package + + + + + + + + diff --git a/gradle-plugin-shadow/src/test/resources/jar1.jar b/gradle-plugin-shadow/src/test/resources/jar1.jar new file mode 100644 index 0000000000000000000000000000000000000000..865c9e41cee5e3f65a734965aa1c2699b069b08e GIT binary patch literal 87776 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

A6&xXLJ)&33psjUNK4JvEVm%t&{TCAh!T;B+qd zr^8OpGrNRkiGIOf!Fh_Qe-LKOj*d~7>0JtDHoqg6sU8(ZDyvK-thKHfRVM9(dYNQG zdln}k`dwl~he`ZFxW{F*)J2b~;>2R4QrW7k6svKxLoKL@)1X2cg4o#eM;&fZn`LT8 zqUoWK;491#UhU%!aX{T(YUQ7H>=j7a3siv zoIQAt5fzzww=j|fyva_hhtY{W}!Y@;f`M>q{bcNwX3DuETpG7K_|@oMrs-XhJ`y4>2s|5(u) zHk)pfh(rNnqYdL)tinX)9K}$DTRKbLAbN}#qVZQ zHb0HC32NK@j=#rt=Cwe1lvo!)Z4(m%a^U*dg^32V0{YkmyR_*J&XOo-1-rbp>7KB0 z2qwtPkn+|c(lDvN<%X>?XnFwO;>U9!VoitB?Dh`YT94d=dj z2ooCKLpc>GmRp)XY7sP)tuX2eNn8`=_SbUmYX0Mb1>M^BfJ5om=&u&UDg^P~xf_cP zI-z|-M%)QEZ(#6YLryaxpjq30Re|>uhR7R-*fVF1SVr(m7$^bi$E`qg(Hl0I>r`P= z7tD-0!H5xlDu7Y^Dw^(@Wk9?gf_;lL9-x;)@iLFqz&L)$HZyol;!!d;d5y(+2w{eo zi9mNf!sjdT09SAV07`+fQ0}}^U|l*P#@$86-2rS4k@LbEY21!0BuZ*UA}V2$6;Bi8 zqBfZg9iNAIuOehWVuc@~aDCv_JA(3F#QD*zDwK$gBE0$?qh4oo{p2G|jDh}x1mYuo z^iDCk%jEx_Q8|DH;udIihuq^w0>VrBGmo%Gt9y@^{wlNATp`UgaBju-Z@`98#X}DJ z#pAwT;Ku$>c>HT<{;P%bAK;dxWNkYukL+^^gWL+^A3nMC{4HHE=()#=fxv2xq=8tR z^=8cBq_oV>g(9w-$fTN#No()c;%jp_O@Cl?Iz@6~I zaH#s-XX^(##dDwHS0|0O@>+XeyIYGl+0wpQVcfApNj7nWT50xy&vaT*3Bplh8;d<)m|(AT-tffJkV4`7?}o z<6#iFvEiE~RipCpQ?;=KGU93jE$c0a{hp95a>Ubkp?jUz@hQkxCfIu{ z(4Z9&whh5(WkC$+Et~ApALp7Kd-iaWcB{VH)ffZ}!VZzjN7y3>^8~YoM$7CWti8AJ zFB-xDRK_R5{F&soN&n#laV%uAui-yd&VEUb>fVjv{f z?>u!SlgZJ9*Lxw6Tf?}H)=Q7ca$7YPKz# zQ~>RAgydnq0Ody=boo1m`ke^s9Ub2YyfFedpc9Jv1x*4TbcpZ;ad(YN}KWGaTo;wu`0 zw%Ef_K8K-YQ;>#bAheqm%%Q-*XaN+n6$K6LqO%a@6@3NQz+U8g3#=drc5!~s`d_Sb z=55$^F&MM$N9NEn7Jr6s*Tk?Sq3Q|kFR zCf8ONRQdT=Yp%^7u>T&m$mF2xsb7!$=qq$N|H(uDZ)e_r#I3^r1${%0s2Rx#Fq6dYzD&NCEk~*e1@uA`G(Nx2SMYZ<7N0D0^sIHdAS61FhH_owR70HfoYA zvr7y6vaj4+ZhmA(y#F3XG#I#5FmB4AgD2!zQ2qGUBVL7#BXE?JN1_&fSaY3N={XU*C66O`Xbb8&>X?7!vBc*k9GEy6Bqv@db+jvK7DsBNX2GpJ z1|7gBx9la$>X|h`^fa5`fr~`uyHbiH6mT5BJXHVQRbZC7X&(CC9eTFcANB9- z{I}4e4H45ky{=GOL=ITzueU}_Ce2I;5vEhX7x#+mDXDPO0?nk-CB3+|ZjI0QdB#ju+jky&##QJ*E5~kd=E) z8L?7)(3H7jM8ORsC{_;;2A{$R7(>n-em|P!>$kY@uq9%lRqjw8NK2d?KORPWi77tm zrL&_=*|hmigtwp{1=T1OcE>3W!uK(T_u9kb{ZJC&)`bRsi>Vu{^H>r=05S}VrS}&= z874>fI{nNgbh5=a&V>n`)){yM7&du@qQ&Igk6Q?#p7x6JF)Vmv3}lH-tv=XBBCj4? zMc8D0Wn5lk|+*gqGVZt*Q#M9cH1x0_+!u5PJ5cDgTh2k!p zm(k8oxM724Y8D>0!>Ahp_jgbXEJ(Wg^%AJzuUzqlv~qi6T0DwIUQ&guR3Ag+ny-jY zdksQqrC7W}iCMEsxIvC*5FMWEoQSC7-YTe3uhh(Hu_I9aeH@2i1DDVd>H%uyk*!$B zEM)roZuqD3Lu|EV+}W3F?E~oFktGP5y7#YaQu?0;ny9{&f&VsHu&Nu$On2Vq)m`pwJn{cT5^uKvYD78-H1Lu`^FH^fRzdT6^olI#%r#TX2#Zdlc{d+th`Oj|8VJ96eQn28N<+WucROt`HkHkQfq?}jF z;G91dtWhAiiyIb*W7U-_hpCYgMEf$Fw1mp|W3e-P{08Vqox^W(=sL3-zOk^~IbTENV^2fHYRxzSCdnZ}hZ^9ja=5npKH6 z^Hc3*=&<|~hWUS3^H(VRcSj;gK}V`r9^EHesmRgX!u%0u z!_1GUogxvRPypRo0m|K#yF$A-f#X$WLp)Y~`unk%$W^M)on-6st8-S2>yg)|mlyDt zh(i$G9t?-9Y-J%I8>6(WSTg}%$u>^)R|P>9@V)t1J=o5Jr-+Z)YRIj=4b?mA05d9` zlf}8Kgi+YiW$SSgBhjElf-_|@U?7(9qSKk@w1MU+x~YwM1jzL4mf z%s_Os&iTy5o1+L0r2!zRpg!Vp+Og&OP#NO=YV*G8(N1$3r^${#2DJdqm|XmODRSUJ zEwhBYTFyARSTyMmLrTGE3X4QPMgh2T`2slsal(U$3PK#H429(7;TtZ&Dki^TIrfK< z77Bd)G0Fhy%%N)5wY}*fpfEY|C#1kFc#KzB#~Q=|=PmrFzvT!D4BY#4U%g%6FZS<0>-ffk$ zbMntX$?M^@$kY+vGQU`H;CU{|B{y6mI=#k=gT@wPCb&6cTvL*mLn{M^ zRSo0jGa=A~4A57MVoO_cDp+3?xC=|Be`1vFTOO_udEl7pmf;u0aQ`|KtrlIJV#r|3 z%NCb$K)Gxm4D1MWTR#&Zro@KWSIkIJywxzK$HZVKvkXs-5+0g3=gh)sb!?Z- zPe7AKtLz^RkCWDoI03Ieu|kvhj;9UHRh{r{zZQqvqRqjr?@4uzUeUoEY^)KZNt;x+ zoo)vdto@ICuNV~leIf|}c?(YaYzV+L)|ghEF>#O=iH5hyJxZqQMY+RN`H&Vk)w1|e zn@B)XLTpB)si0?fcToD0R?YGro?g}R9Gq_3@*JIhrN;CJVJtbW8V(rxwK{u~qaKyy zyvGI_(oVh?KbY9+DAVa-x-C>+tsn+t6?N=;R9GrgeKLk-#wO!^_o|K$@t2RXciPO& z&CY?4yCZEgW?2`bGHzy?KH$xlk2d!Ww=`Gb8CPc2#(l}i&B9H^(byNRSqwrD!D99B z4S>&*Nwu~879@>ELt^8jrMRl>v8G^N*Y_?waCXl2Er}&r8(zVx@TfEUL)zllG(eTs zJIpIgu@yHG&&9}-d`XTXZyrY->{Y@Rem>K37~&Yugv(_n#fUuz%{vih-pUnNzHAA) zNuEh2cJ&NARIIAG3JPET(uc>p8+v;es0qhz8pLfH?A#v2jlJ6vGEx)BQwGS|7-_m- z38)gQc+XlxVU6CMcYmK^nMW>`3bF(=sZvAW44)T7^@cmscImWu9d02izGsFp;cK^&&5r+VGt|*_5-(RN20J zAZeknG7;K>5t{P^sfDy$%yt|K(1c5O3@g959NvH%@DBh3ux23}b?(BqW^-Wa?P6%R za(XeF+22zLz^$k=CS-3GsYC(YUA>|57^X7GkA*=`g&WZ?8-yk>3QFU91+c+DnGwg; zNFUXq3{bB9^qJ09;0$LM#e6gdh4$;njs_$QnJ&G0s0f;hc=BK-JPANDKO(NqI zj%l)-GD<1DGFVn%r0V?{l+trZhs-=-bEH&aG~9_cGa9r87tS|RGP&AgOg5blT-rD` zV~0;Sf67rF%Hb}`bz%mmG_Cw(>4UHe?TC;sLk*jS3D%(=(wA7U)?3fKxGCV+Bae`^ zslz#4%0pMQe@s^NBTE-<_eUZ)CjYXBBn>F_%zplxG@0V$aa#A4L@0bElmGj`x5>Y% z7IAYUBV(I?5z+r0Y)DdElkNQyCaI^a6W8nW!y|Avzn~Bp16;x{MTQE3pX!0h7$|j^ z$o>DNOA3q_#l*-t0D@1N*&wPM&K6mzi?m%RS%laJ8^~AgJsT~JresGr9x`ViD@W%*G!>^?n=g}22 z&rF5C_O$qz%QqXZsKn^w>u=sb(qb_FU~?BqB}vJ7dV&^Ww@ym)Z*V-?Y%t=5!G6~I zA%p0A|66UqKU##Eq5M;;@EhC)z!E2nW609<;fF>DZAnS=e1PDDW6Q#z>GaxKVWmh# z&Nv>D&XnkGHj&&RLQx1(4%uQ*@p*W8aiTanzq~H8+2wx0mn>;k@a=IQ>E$n3k_TpE z2uQ<&2r2-b&xi z^WjbV2C!WuI(IN{-%ovUyP(9TmNTq zrLtwari}by6Tc{NYOS*d&bq4K8kcWhvhv-BMn{a|B$-CaPZ=eMY{+?TjpNZSL(rWc z6wXc1KuB=c9=|4~2-%R}8BJ+Ex zjn%KTo%yrqjp^n*loZtAq-emTL%OFUAfz^iP_}};A2ov|;kVQ}>)3?Hf>X*vsYr5M zyy5X;_@zV5_eciY0Hh8i&p@4{WC^}0rv^r7jr@z;E)r4(0-5e$C@vJi%bQWK2W3`b zsokEFYRLB+QBYKw7%j~(HtsFXbxY#s81_dg;5%KRX7 zE0kJ|rIWd+8X=ZB%P$d4W4GTB##G~EHeT&v60oaTUAXruCT&quO7OS7D51bH8wLBJ z2cCATllIuaT=GJIbdEz!wGl(R}`;D|kdj??LIE$|kYpyox- z6jcehfexBXw61isuV-wgC2XB87S}7rJ`HKkfXV(ASG2Q_|9tX+yzTvJYle$QxwFuu z-A{i#AN4snZ5P7N_e^O7pv9%tOB(p_2)bpaLGN=tpozD z9jwrA&_8~;8@Zh?H4`MWJtP~4@!y(%2SgkicK&@OTVAE^*4r1LzI@@yKbn}4wf&Eg z!6Zd3%fI}ICawuGa~CucG7Y+mi({nbZMoZ0B2Wee@RZAn@-K`ULldLAqb90vG#|iv zUI>Yb;NHk@3cINr@KI=z>g=GXCw-V{X%Tw+NBX7dzZYMIX}q0H->r8g@U?g z?=&)zG&v9d;1aX} z4#U7OpX{$yZ5N<_f#%Ex2%74%IW>(7J?LAqIL8meQ2h?kOIv9wwj#f3Pk7N;y+;j5 znv{W68BepLo}ZB~9I0m6kYZTpNSMC(ZamrnoqSoG-E#i67ewC0v7kGxt7>KmED9^d zMkq(N)r-C29v)4;c&u%n{nNQq|B|V`h~*t@aLMI}Nb;(rP~Y#IBXO9MewuKC)nb_+ zDP4WsvIQcUJYrp`1Ks=HBTqI4Do_655i*gph}_Z*bj}S{$d}DVk(A!WYhF?vv zfv*cd|A?aH9R5zx1<4yWSR&}bTQzLDLz{0c=J+HsQBplMnZMWz5?N)^3*iDeU8Dq8 z6SFO>t1>GV+ZH@wQWu`FT4F*98s)x!S4Up67Gx(3G?>11i zH)9SkZXOJHGOFgH55BfHlM>Mxs)+g#XfD86Yp`KY*w6!xu1eP|8c&WzS*zGG?f^ON z8}pgq!9N@<0=_0zC^7rwb=evO>vU(&og0kg<*03wR1Ae0oHt##NKB>3R;)H_Ej1pp zG&g5!jmvbK#zj?|YS-H++v`L;S3n7th(>G>Z7NB>qiRyT1b)MqgQ*auqVo;yUF#a?jh*#=3VzIJxw$ys{J!~4Om^naI95cSi>Z9My zf`h&WLcl3`c_Y^G67{yDn$P1z-69SPiT5dj>Kmb5Q5}3{FOfmc;4b72`ajpvi;0QTLmy8)*k&N?M&ICEBSvwv+$tK3~ zhBWk!=01`OM{|cCw)Gqx`F(+-%Wl75(ry%T-;#3bX1h#+^oJE@J2;*WU%wE*bMgZiaCW zd(EA%)|GvlSgwqlw`dGj0s$y;=~kSAUtSluHWlYn)B~!L=MYEerY7E}59E-RmolH| zv6r4rIm@(rus}xpeI-$u;uDRKeMkfdQ}uxK#z8ea&U45r)a6Hqo$m|;%aI9XAUM*z z!PJ95Dzsm^Cm?Vbj--DFbvWdJ$Reo6NN|P!m$MqjD-Zq`T>987i{BZ}w{lKdRfK~%w$ zo(~A~pf|sYMANR3r`)iQtO!~-p_j>49iD@67Za0$C*7!p-v^zoTv6tzCDFZV$C-0sveJ-9{j2h z<4e4JCt*Mp^fs2COB#Um#KWFBY+4q&e$jYMDBXF+?Sgp|(cMLqz5N~U-!bb|y@8zg zi~TgdRL*}?UdlUs&Ct939k-Ga{>x7symhjatk}s{j;|iUZ%#$^8^oU+QNch22_D3w zPA9qc%gfTJ1hRgbeZ7cf+svQiZx$!&3_P8HYLd!)}wGr7_C_#lPrEa{vT?ByQg&@m1qS|`mi7^NC69G3c&UX}M$X6tzF2Q}e>WxQ+zFo;3RDc> zA&HCJhLR)vDMP%%Ago^yR<%b}#5i%*rFLE*4oQ@05Z%D{_6Q&}MmboYNwd$Ku(gI@ z;yh+_ti3*+I}n33fpW`jp~;;#lWWpLA1Di(uS(TueIBZ$0sP3Qq`GNhVhWUfD>Ua@SRE z39wfPZcBKP@u_kZcZrTpP97R9PN{p;^6WQ zKSwC@UxhvR^EqbS#79pU74wC|*#}T0(6nchr6g5i{Y4aSp;!RQ+X#ZVUIH}lrPJAn z-eJdM`ECBhs$<6`h&&?qv>}@^ENCZmp_h3zeC|K5iLR8@c5y(4st_c?B$lp$Pj!2) z)_hNlZGC*6E>pU3pqjlkh%}-Cmn59vI;L^l{EbLJL`s@;fyq~n#Q;2kPjhamPTbKs zx=YhpmgFuFbOSNn3K1X-jS>k+u1))WN{2+Mw+N2jzsJS0^Hx6pR>lhTzG<+2HHM16 z9^XGIW0lN3{Pa!+w|J?Pr_NPP0Aw;LPxi z;ZZ6cl!sCrySIfwL@y1B3wmMNhBw8p&kdwLtGcHQJO>RjBrcj$*%SbPsi=z{Juep0 zFQeytXs=&o9^hD{vS^ePgh+X7#z0M#_ERjYPUZmj`y!Gpb~yQl7e)H@UT97u6S8wl zod-K^!GmA1n0-B_g3Bo$%*cGB9Yk`F`8c|IjU7%IVA8kp=guJ?)gojb^NtOtMghyg z;$fX}iN=fhu9&Xa?&5x~S&Lt2yHjy#t9ovm{w(}uXCq@^K3=S?g4_wl|L5*sp zhb33muTak}7f^$Eap1X-ti{K`&^CzALfZXA)CO8%iz>E{Va1tYl5(;{rW`S_sqNVl zv+B>0g0D!o^FMRCLMk^3hTQ@bA1Xj#F#Q(3`%b(BzI8ya42D?+q-o;EF%G1Nvx@ns zs4V&XO;~c-@X`8<0o48}11LG0{C^ohOKFZDdFXQumZD&tP8Ggf4kNevs&cYq46SQU=@YD2$vi*0K1WvG zzjh800AV=l*Ao#CUlexk#= zrh)^fNNa6j>2X6+4OADytFgCq*1DP_I!i8wy$(I^y4>wsl~ z_T%8qY8!yF;KDf^8BNO)Xd;lNG-h_5!2&&Qfz%h%U-I5lEWgXBm_NRckSlP_uyj%|uc!v@o!%3*tK%VmI*jKkz=TD)ryU zTe_Vrd`kD@&!Mjh_)}ikAWg4h{6vDCpD>4L^+m4|F%<&2XvYIA|H2^pE;^}KY?i6S z&=Dr)4dB8(cz5Vp@frG?=5;7~jKmzlrS3vV)u34WDbeLKe^iWun&bK3R=JGGRcEw( zi4WrclmV0-oc})zFeCYwVX;nwjdY`QW_7Vq(^^+~x&bOEH&lcQioici@Re0#h`n|t z$$3xhVc-Q{`LFr2lhl>pQ7|IOnM|&i9j241sp)HcOF$sXW7qT={mK~MNTslO)AUiVE^h0WIyWfffu{;A5UVm_SS zZR3)HbrJjR0Dd$?0)uAf+t<^6rCfsnvvAkyGw9Q?~z^|)m?vphimwhiK^KOac@ko}xjGnJ4;bc|m1 zUHjBs=al`&+iS`z5OOeIyd0tmw5gK%bjxw}pHlJ7yQ$==mI3@6iBE>CJ=F zpUHc8hZ?U?q~FjD`WfBCPPTuH5C$;g3g;-Hlv#k;H;pxb@=#j(t5iinveHmJiMr25 zrs9NTqZFgke!*@D{Tr%vQEqWrX=6E6noh;4stkiw0X?6Ll)2OuMIYljMR3Q7P5+Gu zg#N1TEOe`x`ipjl=3*vzgO#FDwV7pUOED)z*HeIhn-zRV!dkxf8ol#NOKYC?J~|>wMC#2SIBNm=msQhmuBtY zPIXmnR0&>rf$!*heN+HLEpC{?5;bL}Bjr<-4Trlc`@>*shDciKv#<#h($a^t^lB?M zg+(OQ4Q7s6;8OJ>LTFC}G<#h^gcynCp^$fW!3MJcB^)swSTGrcj>ZB~ln%>+Pz`9u zUnyUURz%t1`TjK zH*d(#52o46JpCL7&$T>qvqzt`Ud*DaI>uaK#upuco18Rvc+$?*cFKQcv*avmU1b#F zJtpTp)hEP~tQaH)N7=}{m)2fah%$Ts&74sp zK}6e{xMnq%VnI6VHsNk1xsVK$MItk;Qla*#X(92RrNFCt%3EfIRpuQ|=9cH&;CJCh z-zEG!T z=u=nEFj#gZZXUx6)1y}q?#`o_e&ffK-p-l~s=Wig9g_DS|I4m?E4Gp#Jx>5*AUa`{ zNhsP;yp@B5fj{?P`wB5fX^+TFi}q8?@U0wrhPSX_RIQg(y^TK58Q2F7#yu+Zd#8k? z6#)sG#E9D}(ZZbIX>o|>x!%9m`!&$4fQ&D~L4o{-Wyk*jnt#otB`P=~seI*nZ0Rm@ zPFlZdNF=pDL?qH}bQ#J?(MpD)dE0Bj8jO>Tk2Kv}X-c(~_U%Mswh#fW7cKu9sSaF9dgAPuvDS zlo+=rt1+qdb>CRjfpnl@PeC7neLLdBlhZ*HpL|uJu={=P=1`zfHN2gsV&B53{AfMa zVFMnWHWV>pw?S|wz4G0nu)%!gY_|U0W8(?4a4#K?t8yP@wEGQTwGmyWgt(zhrQmA| zFY{6XP=Ou0>LvhZiJB{lUX!QUH?Bdmk%mpkZ-m`HJT#?F=XMhw{#z9ZaK$MYNWcXF z^+Mfg`Jd@dZA!+HQ)Ka~mx=D=h#8~ij_q8S+%f-kK)!WQt;IGyEo_Ecq49{b@q~kv zK!rw%uG@iQ!R8H*-;L!NV_TcviboxS2@l`6s?nllYvm~OD9*B8lAnHL^-JRqDyZ@` z9E#$+iLrxkF6<}d7;d0<5U6${-OFv?CHr#y*lv=eNkG+Z8vPiP!N)&WFhGtzSaOxk z1O1FcVA`nDz_UL%NzH`RP{^Xvd-=dZEWSb2|JSWi9{_WvS`>xVqfN0 z^qv?PEVg?WNv({9iVZaprB82>R7Wbv0m(%Z*V-8nm!L?_aFQzV2s#e z7(m*Bh$(S`VIxNjJtl6n9;gMqckD32i|B*Uirr!8Zm^pBN#>xZ2=rS)%B1G0`6%(5 z#=%dIv5Yi|q?DHwC+gCxkrF|yil-D5zTdq;sIPHnp}7V5K6Q@S#JeP3XlD6@aq%v4 z&@}HsPF6yFu*nQwnj)odc@sWyh@SMz4`8{_|6l}c<%q2<9u5}y3SR|W#ZizOzMa#t zcax1=(8#QNzcn>(ATk1H?m-}ASF^|9;}vOSNW60Qe#!F+V#gd|3DjxR$@LiRKhY>8 zG>Y8N!^DkfQCmc85A?>H0(Gz>f^ZI21&m{O=8}Imc5&8wkK9n*?|h5dwxnDpy!pm< zkS?y39O%QC#M)pzklaXENN5dxs)lhFxpM{m@6VmTsqbCr>z14vDG(6F|2j4Bmzrnu z)g}KQGmB9gux`o=4IfV&o>?3c!GX|$}{Uk_b zNbwsMHd(D3cs(pDFumXhB&|z8RznRg+E!I9tu3>k9v>T*Iu|t8)|M*PeJ?mt0s6&0 z-5i!x9Z#B{x>X&M*HKG&zKHw*7T8%6ICz~{!W5l^F;^pd;O<^}sBiqZe2FT2@k22OH%L40V(_0~*$_8+ ze^LRTTEToND|*A%zJVSJxLt`uRe?{IIX*-&Tcdlfz;C#8&=0lpRbbD50swkR1HRO~ zmVXksKePis2lV!D)KcHm%U|2%Kd*N_{nZJpQ?|2j-8Mpqe6XLs&gW8!B-aMk9^@SC z+)&uU9~s9;Qe_r-{`V<-dk7-h7O|xLL64&(XjlOD;Os>u|QrvQGBa!&&1Tx!N3a+V3(3 z$uA97UM{jc=Sp$4(OGp6XYc7`oy(b|A)lY7J;1b*Y@~0uJ{tMv8aCTn8dP(2vWUoP zc(#&dY2x8Yk1iXZjz8|l_O?IXZ==B7__<9z3Jb}#R>dr1#h(Pj#+L~Dw$s(nCP>54 z%M1G$;PawS9!jXaKppBXVTirjDj~>50{yZEq!kX zxw4t%wTa07^9w_dZOWZ8+hbaJ$%gPTGiGBN`>l6Upc>EfRQ zHIZ}wke<{reqJx93i}KfHmTrH_sso-S;3~=r$`%O`#M8qaTA}bynDRTIn0elDP5G- zpOm?_Y98ZN6zXlXw#rP9I6Mq)gihe9c9xVX{3*{UlOt>3&q@`pL| zNy9|)+q^#wV+kV6t@!<%nNbkg!UI7(B}jrcjiq6qjEP!q~yQsQ86d~BndgJ34FhYFTTe(NP-qH^ z$4lI_KD?`~xmq4x@!6D|%vFy9YP(T*zlxiz%v1qh_0HRGi%A4hlg!Mqk|vz5ZF5{m z??q5MY5ovPxsg@M4xv?cS?4AtBNs-FNv>ztjn)QfsbnMS#wPi_v{w=>uBO#gJlx9( zv6y3o?!667{EuAAKc#TWg+c-@#5qfqgRt8n)wnFuC>^VDad=fOi%%%afeNRT3y^

Qec{whSY*D8iO@-R4zz6D5gX~pgb*}o3|3ntemv7- zw5V)9;;`uC98*}RZ55rczxDw}S8DiX(;fBAhSWiu?A4%jvS&zS{NnZxe&u6{hZs$_ z4`d#?qcSK(1H32BIw`E^0%5l1d2wBdE{%fiO^K*jhYG||u_F$5XkWA@B`n?$1H>`1 zFPHe7wnZvuO4EttM+EfkL9}L08aCtPo_s88nTm8B$T#O@1saF>=h6iQwBm*6R{4hK z`88~31Wx2hFQkbvXxfwY)_{=#e2JDPoHlfb=4|k#C^KJYtuNJz)x7vt=cG`jd;R09 zO`M#s?z3$IPG?Eg%~4FptikBwiDPvf>n)4I~kF? zdZI+t9s*=Rp^;WUY7>)LI{%NSfdU5roGp$+j50wQi~y7x+M(Eo3Ps*It@&cx+G5k} z?#f{resi;_X_;qAFzg>C$?bc=0gZFE`BgT2*95+kWCuSTrRM&W%<+QIi<_KiE15I4 zDkPi}1Ne-S51p_c=|}QMeh+?TlvC5yd{}JURo;SwEOr@!9RZ&*fXVV~7#^(DhEWDN zt@R{pS8J>2svZ|DoZ=7ixKuMdl0@`9*1Hp8wFBlCMsqb;=Z8ZOa@gx-B#f-|b%zLP zW+f0<(`(r3%xqcQ@m@@)>Tj+NM*1{EXh%_WJIz$K>k|x8TqhceBhOhw9I9TtYz|JW zTax>OxIvs&jaIu%!))q?ETYw^i3vJ^W7jU7?}}wQz;_dpzxScar;QD-Pe7+11%_1! z;@eG*q7sKKdp7DaJOi~@sphM{e|4NL>uw)6vWhwYS`)w^4hzlksIBqs4Bp6(ITUSO zX-#VpV2!C8YRJphXv^=Kej5TUgmrBf0uJx%yD>cAF`UR)A-V`FQc}3#I6*}e9z}LU zSu}EP$)~?)=8{%%LzZmr19_5OMm!Np=JluIiLRq^#KqI>HbTX4*gqUMhEWhU#w0B9 z1oOqHN#@qE7ql=t^lkTbGx}t7H5h~I0>>-y`lvCCFygA4p2**b7H<&fJ{2G55?QrthLYS-U5_^z+~AliHyA~F&X8cf3G*Pq@N$)GcXER_wfP5i z`i*iUjkZ_TZUl3XLX0b6J-&EgdF$)l<2pL%eT;xd$=TY^T%$ZbJXXT1<~;#TV&32! zjgYB|lBJx`C{!|$GHla@uY=oaY|9h3;uQ?R=ZBpS=aF+FKSt)ZTigpDf>n4LCq`sv z?_u-mnLc8NsMR!*!xoGVG9L&8-5Mm{66E@`E1~2FB>gC%6e)wzhdZG~8T3%`tFVAj zv>;uJfZ8m`ZBF&>_hG?$Dx13ja{pf9?xsC4M5n@{tIFc0vOrmJikyyvqI{<;o;g8l z8Fs1|Wl`r*=E2347^qp=SjRL{OzIrPDYr4+XhesV!0?FwI^xS;p&C;I{2XtgBxnCw zXA$M0>s)+Q(r_wvYVbf<&{5vHu(jZkfCWn4Ymi)svpljI+rud2egkzDn4`?cL?SXe zq=e&an#y3q3cV2G6B_gp#%xNndK8n`hQwNNN4Oa#plQz|Uwo|eG*h$yz+Yfzj zkG^A^r}J>0_Bd>FN7{vAG5_3q<8^~>76k#9c;&LRjs=gx9Zq)iYMZrF4r3TS3 zi973zI+19_TW8Z&3Z2X4#76LvgUY3#RW_(;rpGYRmC6H_ll_9(viu~LlY{}o-ybkroCH6qQ0(C2ivQu~|kL*Pl zHXtel2@Cz>I&ZJ-T3t2yaavg|Zg5tGTlD;BO1cWe1kW8_hkpRt2Q}f1kM$(hdx6$v z;WaxY-C>#>b0i~&cA+lx;PptocI0UQJJ#VgM#}0eOHS>Kc`7cf{gBF*9ZO{ ztUx8Pq&eCm&9fxo?qS$?x}rhGk()|1P|HIsw|VOn)N3#Yj=a{wSvn3cHuu*rCJ6-f zrENc?U&(XjpIORB>yas0k+hY*4M9M{R=uB@lGC=}%wBO0AxvSTRjV zafh1Bnc6t24aEwd+0GwWwQ7OGHjkXO>Vcyck4QbCufJ7$6Zp@$b}Xs&Y_&Vbn{SzA zO$(0(L^H4=v#*eJh5c4sfaW#Wvs&a{12n_38qf_|=3d)0n!_t@?0{;fzP_-I3!9ZR zTRF^H^D=Kn>Uc}+U#r;X5Vb6PNcF?{FPz6wLx*}D-RM;^slqXo)Fba)w#+6wf+);A zd6-<`)sbKUF*>|?DXcfe)fmR z?Z8`$vhduU0M6eDVx_J)H6S~StBT3FFuJ}gEFuh%%vaM>t{Dq7_4`D5aEbBa7ZVVc z6A%^?j3^uP$8fJGPqh$oQkiJWqh%6S<;Ky+IF;*O(lVhDF&h*1|G{dX#Zgs!2@QFA zwK9IsYki-ExN^N0bB#UOnryowd&gb`vQ$lI+;B&lXs6Dr4P+6Z(2Cbk&`e{lF!9zZ zGUa#i;&g2sXI9&njIB_$@n>1QD_Oszw1H=tVj0)#hv5E3fAoNsy<}nAJo5Im)Sbz* zf6;1>6iT(7!7Z0q$(x2>)s?G*9&;4A-tXR0qnahSZarege0I*`hG~)Ov0y#2FYnQp z&2sln)-Q;)`=d;r>~E_uVDemz20m^6Q?99RJI~TTaK^La=;^r=q{ap0gt5WIn6rrs zM2FoQ zvc9ytuabpfdPO};MLk)C1G=xid#$B~I+KPi(SWw3GXD`^wvSj*1KG2)94dvRQ)pc%nP}IvKY)i9fsHiJAks?fGOQ zL%H4BPJuCnu%q{j`X#ETL|~+youg`g$^KmzxH-$rR4@wv&KtG1JB;%Lmt`4P82tq3 z(P?9fv?z|1sPxUU$SA%Z4p6PRp3HyRzHY-z1!+cF2|y4AoAYIsHyNMs-*r{{U7n!o z{!cc9@@T8@9f2}@4EW-=*ko)0=}GaN9d>~s0#L%2cWrqJQcwQm-XnQUv#lw|M-9A6 z4R>CXpVtY9h`g{ViH){GC-iq3I@a8~xzj1d<_F6qa^(;_ZWXC&M_$HtZG~dK8b-Ym zB|SUh$!gitTe&AF2a(2?+jAJCyaiF75hg==uF%^;a;EFlJaOV}ODr~EhKDsi*#Zks zcpLruXI)ZRL(--=1pn|w_RVMqbV|L9wwN8Ej+AOuIj`8#v`-+ve^w~AAXI~&5vs3X zPcS|d^5Y!;W%3HkUU>#AgR^526xT=YM!&K~}kb+fE#mvZC@!&5T z*r@IY1Tp?7z%}Q9&i!!KCj~T<{iy+Gu+?6-U3qQQp?~`e3C#HLm0WQP@1=x2r_ZIt z4Ktmw;6p}PpUf^7?#+mu_n${NuE@a5H`>pq5DCXJLq@9ysl%%}OR8;Eq9+Ch-qjze ztOv?z?jS^LQ)Lm{i~2NwtRE)jH^yf8QC3Q<62_aE-apOnz?0S9!8_N?WyTFxPLbYk zgv85c@5P)-2%bC~mW%^WxH25M`fKb=o@08Ooh{Z#;r}Humh;D>@sr~B*fN8J^L!Lf zOp?DZUe3I4?|#}?ACZ0dK!xyxvusD6@yMToN1X~oej!)8Lq7Uo!#y(O)p@ce)DOvg zI@GS*2zT`1=032m{o0Ks5sGnaw^zkQkSm4~}_(X|2(*?r#(;{AM z0;%GGoqgbn7reK%eQ<({??5;Y+`zf_+ng`5gbS#&G9;Qm0;Lw`0Go^ev1wyl;`zpS z>5X`m9yCd<(*lbhf>RIoC;kY~>G8Z06x-IM*_#)$W6>{%Za8Ks(lGJ&CacMl_w_`uj}R?$aQG?^lmRH z-OjBECK_71@#j1^V~KcJ&_T<-%F2{swXWypjK5zINOOpt{55(~(Hz@RFS!^#z&2hy z@5*nCGQmI1dA@0Dy%gtf6)+$Pm)dck;CEvm&)bcfryHz0F#}&!cnYK zLczYQ_hFy(u6Qq7{DDov2=f35Dp+}YZ9xI&A9(%|_iH%xGkO)mQQ#nNcB)o26`W(o(Zl$1UB zkUjF)P}PObs_hYl1v2?=F5Sg)*>eYqZ+}NN+-Oi!f$e{-;FE&SJm2e=1Kxil>O!fd zA5w?rL{EFRHl2GXz?g`Jix*cxzRt?yU10y$ECTyG9K!C>Z8Z@j!}*J>U|;y3J5wG5 zRltSKi!DN6$>Ao?&K$6ui1oHP;imG&a~a6L>dUC#T}t(7Pu`bERJk8%^X7>gPSX}~kDuR|tbTND- znd`N*SLX1mO1NU`Ks73WUG3Z?sZDOdyxFi^DxE>~6E(@&CaXTe<{yPGHM@K2&a~pk zCyVZJrExvQeHsYk1UL$8o8rC_p^6Ar}WpsH2gtC-}bTy(-C_QQ*7UOz8`{GiHJu$L-I2X3_smUh zwUCv9DeY`IfoTwG@@rfcq$;M#QmY+n@)zAM6La-{HnmiECCgau)dIN_S2LSz(Q`i< zMXV{RYmXz>!}jr1Q@(8K?l7qY+S)aa$+&fiYPqsHv*41V_PhXhbMr+_Nwjdehw|q9 z<0N(TDQI3Z_fGLj`crh5WCv;B_5rhC0(I4?{BBwAV~Lt=xtOlb7k z!5>NK`1h#vjp@<2xD%GH#bz{E=Pe>DN<5xf=;l?yP|kmX75AkFLMEe_V_Y?0%DOgF z*Zp|%C+1X%H^@zoD+8%)Cuy{F#1O=%Kl+Ur+??iQB1|y1dBs$b-4BT6GjN*qG_adh zG;mx}x9p_EtdgsvH7uyjOgMESt%_qPC^qBnw8a3U2iZ!hqHJbkHeJI>?9D6vIDBQ} zbrG#p92cd_|IDGLS{R}gHDa^5aJTp>fq7PWc;sqoq3fMTy^K@1bv zFg12~dW$c*mD1^?$7bO~|W3CJ> zm*ewsk1vnUt|w1rMyf%rfL}4+gXpOo*AR8cw2yF;O(fnl-?X|k&~T9GW$H`g_Kn#? zWWQuH!y?u3S#H!t@Eju)eX8#7Q+>6Z=c>aOr*hWxjKWAXkz7Zve9@uD(;mRutQLcl zWLvi!(>H;2z?VF-Nfz{IpWT!16h&ZWS5kqyL7V#HQ-wu`GfK;u)r6QLUWwm~+G7YY z5kwq{%4sHOP&%=?6xtM4+8i&fVIq%+osxtRQWcIt*{tI29&HVk-#(Z`EX|{7Q^1+b znX4F~MtVS8)ThIdZuBlT#+%ljv&3Fo)p=|C#8N_r zp@Q{n)Ge&+nQddLll^Y2$9#FdISbMwqB-Jm(@m=@Ql;G(%2=gBJaDO@`LyiVYO?y5 zJQ{TQuBpjv04M=9Ee9v=yxP9nMEKtP+Gkg+Hd|@$$fyXAyXPtM&d@_!H8CwcQ7KvN zf7obFqfB&UrqWo^m}NIwKgOj@G#x&6esU<|p>WOBt{iT$pRkwdSn1xLh}8(`(6SXi zrJdkWXalRLDKa$8mEP5mz$xVZ&9EuZipZU+d}RNTkj9cS>u`my^%_}GVX#sP z#E(~1u~q|UdxksMz$(k~QgO(WccS^wihK6baF}+QkJEIO$J=A%Ftk;>3a%J)KBjl6 zNuC#_TeG)3!I7g8shpw4%8!L8tKMkf*p0Z-J+pfj{4DlAFoq@=9~}=r@aB09?pOW`5_K~iVMJfrD54y_wqAe z+)jTfid?{WL^)L@)rrU{zTwXsEM^?TK~z783m^-}K3Mzyh1-j`ZlsT7cZfYkX)6kz zPY^>*V{zhvX-iu;*&)JSxrK@hxD5d;5Y4U*#BUl zS=9Y|l^|<=`+_?$yO%Tz0~*nk18JIYB=4I3Q5MWKlDVN}AOD!O&y;q1`C8IhRJ83K zA5%>!{tQJi>2G-eYWkxv(rmZ_k+UI>`kDZ7H>ur-?pl1I5s08Tte|PnpJD8;GW$|?H75}D7t@@P(5uQ}QMP!Nt=nJw z30Ux}GBiY^lwZi zgfK;#<;IC>riD{Ng;PU8QwOfb0)!b5et-xdA2<+Wa5wG3-te)-Z)(JiFJ|<#jlH!C zoWRtEpkkG6^S#8tck!ueE$Pk>;@cmukgOnf$D1H=GoT>MaqkB)(&u^zrBlj-jQ_-H z>)~RI`cp$^lE=}EX&H{ArKFMc5dSk(2n6Sk?Fk5k1UQmwZ!}L_NzWy>>UZfW8Z%|I z7OGPV%<$ie#S^UU*w+bOS=ep^JyD#eZHr^BsBL9^fwNXVTVulBw${WTAv;ZMi!-L$ zj!JNJL<2EVgiDc4D!kEzcus{vokE-MKSYEZD!isFiS98H@5`1?Sc2Nuc^~XfLTNTMrcA!6e^n#ny7CC!;{Zyg?a@vQ`rm2l(zm81OD4aa!bDVoCjHzEQV~Pt zuYd~6SiDO8SzV4mNLxOtv+NKlN`n|%^xEonox)s4F*$)lbtNz6yY;GiFYOraX$p%U2ePj{u!`?Dp^M=9Rl9v&Lb*Z*}CN_ z6_Glm652-f;S238fed2APt!_j{+273)+oVK?%3;}vaX6ss!0Kuo1xcma~O{V!rwUQ zG^T7G)P3k(n%?IweaRD^#hS0Ey(mM^w}>9q4Pn;zo}~j{JOTm8tBdDE5rzVTU>_jW ze@n6`h)fCjql);#lKk*vkI)`o0b0a~dmP*oemoJqcT}<0rc#s6Tcngug?zzCV->)- zJd@i`_&(Z-@f+Ktu41COkY3ZOY|I1Nt5GRq(zFD+iD zV2*8m9FlsJKYSsR>4i1#MNE^plzHmf9IJ=sfn@}5<^`F>e2Z5?vIe;=Qyj|3Dx39) zcAlwP4b821z4eka#0+c6gR}E1*gGlZs>bFpQIWYPE65EOYu`yKTze}kexAwF1;qG> z)w!2->wx)@^ZJV+eF2S~_I}Wj(AB=sAMiQ^C`f!OaW1A)p?#;H*Fx<3|GK0(IEeU8Mbi61 zzxu~SCGG*i#Rn42jr&Itqr(H6%JY2hIGqK*WFX^x3bvC?GvuMy$_5fSuI=_NIdUvX zV*wO&B2YPNdkhu-LO_O{o?bc-&pCcy>5@dX>tD2TxPt#0Iu^McVx|yUGJni6lTXHv z?&)%3E*iIEsxc2}+g1DC{u|6z<~#7@ix31vR|W)x{C^yn`M(b6)4ni%YRg?O&-uPL zQ)Zno+EOA?KLI&RsmRc|Vfi4aVw0c@|7o3gQ(PIBd^Ioqe47SpJI4lb}$&Y(?TrF0|un)47enuy#fAXe6<|I{efNs zt{6?9TYy(OR>O$xOLd|baD&*3z~E-YpD$nup~CCawC5Sppld#!(;IkbP-z+G@U%zS z!$i<=vPbV#gC!VUcra?u?ZvxuKEm6>6e(4a&ifjocn|S?Ff!}q6o^Pz1m60h%^5HQ zCkwp)aLI(avG98S_oaZXQRto7M5rDoZh_NaM<(m%+euOG*5X1#q`O1o0KZ_OM6X+9 zLJJ3hi3Y>JvF#^wpOSoDY5VxDho(I*eEP>H_4Fet^ll+tI~o`8)|!^geDkFPMO zSudb1q2EHN;fdp}QCz{@9w+}xD^%e=|Lomc2j7c5;U56)?&IDw_Uz65glo@T1K~$7 zRN(0V(d|MaeXvj_*1sRw*2^zE!viG7`>O`vB_*G4I#I*z#RZ=)AYSk&Gw?8z)BA~) z@r(B>&+>b~_Itd=&)4jypR^m@?s z?JMND8gwH4N6=mI^HO{$abznH0RM=`Ci7(Wt~82QS)GklH#?n;r5>}zLG>C;kDu_{ zYd!p(tXfzxEA$BA@$0R=3RI{vQ=&@Ml=Y%YcqRI7>vP9SLsnN8>n1}hKRUv!%Nn^i zaNrx@Mnpaw=&Pgci@63*kFD%%^&2_Yx~3~H;+`ImEu@ic(Tbh&_Tb~6aFffW91Ts} zNi2T&B2#Eo9I`N9EjOhr)NB(+WM)S9dX8T!lyIVu1c@<*T3yI;8NJNH0J<`@=kelj zl7EPdXYj+6FmGT<*G4lS4*LQI_-jNpkv|U3k5&-&jm&Hb^5MnX7siS8XY|9@+bf-4 za*c{+&E%}6AAG=KTSZ+#^N7x3<^u3Ha9D{TesUBpRv;5Pjzbcl-pNKm>O%ct#P zjhGDZL#t!P37`t=+ss}@gQp3ZK8@I~WkW^X7E299u}2=D5?<+Xj%}ZDBK^XGkN+Z% z)A}bzanz$RBtv5o`_4gj=@wz`X2m5H(L1&X>yGV7q9$ebn0Mbtzh2e+PHgPa`UQDd z<>2PYh&Y^~)9U^Eik0GV+?(M__xzG2qECyjHMJI)65BH-Y|XGv zP@JcASKMcmSghwjAL3qjek?)m0~JZaw40ZELDox-|{PB$lOXUjh!q@vEUJ_6#32i^@@OO)e@BMmV@oq(J)G~v z&6W(QViqKwoeKEpAe-&x0d9c{WyEy7AWZ9_Vl9V+-5rC@E*Rwh?o(Fyv2X_jcVJ$| zDEG56OIN=<-2ci8hn{ix9TNR>aP3}IE;mxTzr8=DF_y)2lw^la%gexfFlDC}Zpc~4 zL!!gl_&%Y!*3*o)>3}FKBj1e?V2H~6$j3{JIpnpjf>7uZ`as`wDU$CJ`iwHa5b7c1zpQ5pF!%I7EdB@}DGRMa? zd$33^u6xbih1|Gt*||2abA$;8=wWFmZ4T9m^NnkMJk)164=mmD{r1TnvUGoe&07iA z^>RxlG@+gkHu?nI_`5cs7GXl#9MvR5Em%EvcRZ8e(5(GLXC-r_YJ00Qji% z^BmEA;`GY2_dePAWy>TDaThK9?@9jH=^&-V1yGe)A~JK&Tt5!ukjjr+gdzKG#mLLp z&ejm=q=Q;OqXWt`jVDF+Nqrrx+9a6y6|q>^Ca1fhjS=4=F@h_J_Zs)8lU5y=l{pXE zf!z=b1MJpKDp!k*?gqvN=`JM+Iz=>%A(q63W;57`2StShE)J6^OOnj03CaXgb`6^m z1b{jc%W*q4nQAasg+!`5!HRm}8T*+ssiglLe|9SAr{Fakvh|yn@=8?HnD4H%9G5pY z&!!kT1J-BFsN@&NW|uA;pax8ZJiE>Rw74)y$eN_2ac5SX@{EP()}q2}3TC^TFd)g^@fQsP&y(oRJbVJDBdI zM!E#llN4B@H8U`zXGFWYPaVn1$~Pn*2_!y-n{~kg>lhRH;;9VlxXoK+}ir+EFhirYVl?fk2n;}t-^*M-)TVgmm8+y`h0Q&XS zdx9yXr0RbT7{e^8Wy6r9!f43^jpgKw$CrUPrxHVuZd@&j$675nrR2-v`6k?@(dP!w z3e!Nef+E&X(E`nZdAh9^CS_Xb7Ov|H$E6htvzhK*sC_dE9&HKiS~aokCUps_!#qBF zYy_53k7`%&1+(*rc~!BKVvz|5T$}V9z)1-LtOHb^D5O&^)PfQq#dZLXI)a$>8Hj*e zE^gdpwkA=jRw%7ig#3pOcZ))*CUYv?d?N#jC$bF-Hrse+_x2L*Xa^gPSpfM9{hExf z&iLvRZvJ0G>1L0KJ=V=6z=|C!3#N8>M9yu5f7p=HD2JXXxAca7o{<-~>FU5{3}Jxx%Xr<<;q^V&&EU>n84Du9oVpXg!jEGO(s>5oQH%35L;gncMF2JJYB_J0 z{_cvr0|W1K&IaAiS^mD&mex8J3#ze+yfzJ-+y(DgeFXR_|81CXGxh0ZEVG0y{wtNU zAGhVS#INkTNX%@3Mp5+|^Xqfyrm7)>0-Kb4;u;5e#~bqOeDin`3!mehOJc-RGU|c) zdiVm$8}gp=d`_E9+`pHpJ~=v?4ccoPoj;84yd9+oT00A61%|yhkI!ST(+unycrx*G z(Eiq8!QFjxM_~1j8gV(4y4RF6u@#41_K8YA{-j^&z8BF(yO0G}NohNr%CB!kS)bNN%Zc)kp#FAV%v~;t#YhQkT$@f1sd2C|L7R$xwvX0zs0mwbCZLs?g>088 zN4w-o=l!@)!TdwP$k;Fw5^=>8Thk)CXjZYhe~VvdM$)H#>}uWPh}6ubrZA37kso<~ zmQWw9t7)B3Q{+>`czf3nG!CtP4jjU~Yyl6F&)!MO#ibN8f4P^*JvorL88fnP_E zr=hv{OL`EyK^0aXJv^y{=>}jnL`4CjW+}=Tf zi%LF5Nt=

&hd46Qq(aD3w9C6#ohc{S}b<`-Ij76}=^C1da~|jwp&v7@jL_oArRo z(*b(EuT7Gl?PQ5b;Rn}eZ#}Aw+9h&WA%PHjSCGeh9WsyU4?f0HM_=3-Qt|t;Usxoeq-5rQ%N=H#|BE#KcpyVq-w07315jVK$2K< zO5bacM8XtGl*Un-^nve|rP4pu&K96#1Nm8SidYAACWCJPt{Df-e$qSrjuYw6}C++18nlb<9 z7wv}->^Z8e65wnSXw+MX zKdm1Q_^b8vU)j89XGF&Qpj2=LV;?Q9Z+o)NDxXq{UEZL)1`o7v(qXJKsmxo+3mgAT zqCBZEiV1pLtyv3vdM&|e_DMAQ&M)dg0!R%=pjZjQs%$(Pb9CEjAEnUz5&2NY=?=%4 zHE2WkMC_5!Lvve&KiHxkWR1*q#oQ6BaAm+{ctzUEs?HlS0zY&e!A9_u-KDEom>ZCgola;BnX|rG z9j^3WyUrc2+Cliy1!=MSS!NPS?oRNQi_T8tSmQD^%A`j*m$JR!7R=3pIeesV>5B5xz`mx1EXGi29H$rT7_} zt^G+9WqsEKb1@}>Zn9SVyM#4-4Mi3JK?)I zh|m?N0+(D*P2f4Y1I339b7`U&P(rFm**Vt;Qn6F!f@+;puhE(SgDvH7Xz~;Yj zUG_H!qZfwhvjt?0lxqGw*!RS2c^U|mHY`$Z{q8l>fb$*~>?ew@y85g5CC17=E&GM@ zWfFb*{4mp@zaN;>v+8U`Z^w&xH6+O=;z)ackuXqw*qGDGpCs6Kz*T#e^{(C7DzYip zqwBl7-T{trehj;MeArmu#v%AkR{O_3!e(7msQB|@3^!)^&`De9`kX1gr0e_Aw4yjd zyN7r&`~&*#c2p^fG3nh4MdDC)s3Oytq7N(+yxh8D2rZ6pMYPBeHCP}(T z%%;KpIvsmEU3xq3a#?VF}5c#S5p&MXu4l)kQpJH(S!)1!eylEw&BTmch1L;)X48EMFP!}a=Z3sM0&%T7E zZh2%0cq!7G0QknkrKdgt8jqq3B7+V2EnZ14Ox4IZQ<(C@YUbeuq^^LZO>(0 zU2wM%)*L&rUtaz565yuEB4wj1jz&eMY#?j6D{v0Hux>DE&QE&@-oVwhcu zWn4Ql*)UY319fpE37Ugs=J0ZwBZE5Td~Q@|MzJg?A~8e`YBu|r=BQicO9RH{uz5=V zwiD(wA4=Jfj#R9B^g8eHER*#L1!bj9T;oC|IwpPkxPNSAPxW<0Zgfz)A=<~n$hYOw z_Ms=W2s4jZ`_T`E3NWinDOy*Snj`Dd5j7|iC%@GBRZU+t<0=0-uJihg-=r_>t5Btu zNYJQ?n_|eNl>j!C~W`$U$5n{j2VAE<4O^O_N>t@3JXI~D~P)1(;3V7VYB z&5tKqU}-lSh+RKSC7PKRt{3!dy@PPdxvM2VDXAv~5q**RUrHYu{pS(o;^M%c(D|(b=^xgi*k;%Xh0xJ?YUkq8~ETJ;)|j=%7234hc@ zP)V-IrZ2fSPnxef%Y<;vK^;vfq%0FcQ)%}5nKRO<)`wso*=SYifg2z3L{Y5*6wHY( z)de6|sW=DRClDGHyHFg89(B_c`>!VGoht66$J^A?R5LgAavf)%k_+$ltx|vnW9i!r z7(UX7wdb{Ph3%c2VDcIVM8P)rqLd5n#&iiSHhCv!aGKtW>0J|isTaY?V(xvJ)hgx& z5E$S`NKWc}@b%mkA#haxvBP2=2b>W4%AiXa8 z8b`ShQ9phv{o(^DNrRGZB8q^SC}C251!KNcz5|Jr8bAW59YxtsgQ`T`as?w7$eC#x zx3wOhX{07va3zmPgm7VeHh(CM{{TzD;Qp{=%jmvom8$K>RC(~HXEz%mvNz|g<6kI} z@m23q!x~b#lYMB=N}wReUll68nMfr&ZV;`%O9Q-XuC1x|-B8)2FKD{}Cm2Fc0ue9KI!gGu}U{WK#Bf?JxCexYR# zG4deY{yY3T?B;F*l-DNm-&cg_QT${rDtT>S()EZwX_HGxe^%B+f3=By^Zfc|p;F?F zdYUvU?H)ih>|vEYq{=f5k5Eq0>H96x{*Vq&cY-Kq6uqU(imKHHr&ql%XJoJB0GD>Z z;mx(%{X}Y=QYe%nh*(&g-0G_hRByvP>MolB;Gf&65K>OON8RNY)%+$l+y)nCOHgWX zs%%&7c6AD-)|YndcEdU#fonR_u(-N~vWePLG)MJg6?R)HMCVCC=F9j3)}k2TgBUkK z7zaPL#E&Kh+aV(&ht2Y6orm>5n~e0z*81Kw5598QDP>pu#jF$}kL|WM3ZCdWs&~nD z6FzcryL(cO;gtL-$uEQZh@DnxNFwV4KT>Vz!6q=e?iJrcO=PJ&(^A%qQQ2=b?#K>) zAqxe>d$kM!-Fks^KCBXcv3Cizlr3Z6C8ldZYlNZ?XkhoHF*umM`kXV1*j1i6KSi{x zCV?yItxBr^&Md9ky=H&VOZx}U`6qA9P5uoq2p&JLVN&%+(5Q6m$94SfB>P<7CttKnJa>}<^^p&N!$1JpI}&7T1F3BULFZan zoGmqETRpE$iG zj@A+^%-qpTwx&9HItdHHSN&}(-vhfJJ)8g}txPR#MBRBTq^6fr+Da*|q!iSW@}C7A zZAe%x3V_tKAr0!-quDKRgcjQe6V{=5*g~F7QoK}RHV1}Gm*YiX46=1j-M-%=V2);} zo`3PbO0wHCP14l4HX;PrF5|80)H@ceV^`l$^+kZzHu)oOh($+GoIT@nLwxF=mS1lhA-xy*3Ti%7^b;!3t!%wX@Ae?$IP;si~+Zwbghlps4S2ngx_w}CEY?j`1K@ARK;l6VbS zCslRqFNM^Hoh?WoI(c&Yru}?OD{t*0zO)Py>U1Ctdr;cuUkZfsF6ORaOmuXj5?-UN zu3tQWL4a?8Z$`V38Q)mS45jCcwU9XNF>>hr^RACy$2 z%qEyIqUdDaP73R}3026RXifPt81#`L0uC|tT|$7CZP*uxjgSj zy_mS2rm%ER7O}eEfgBS5y6=45U2j(1<}{6GFr5&gNBw9!@~#o)-)d4?@2oVo!7>IL z>Fyx*T9nA%f2|#hjS?g=taivU&f8_X%$x~H9N#XB=yWJ@+ZHT1E_XU<@%!<{XnrjK zL`M;GC;siw!do;LuHux)p>&0foHh9F@bcYTJCYdUb+}KO_Tph!Gh6b3;UXhVgAZA> z<}EGkN)9{lMrhsYQz0bcZQG;&@)~Py4J#)ws{Nh_IM6s_zMxnwtbTIy4%8Yjtx27Wd*?tJkXoSCE@dwq0D0K4`r9)Y{eX_YVh!&pmn1cE3hr6U19^-vspX@UDCDngXYpV>RH{VCeF*mzk z(VkV9fBg*Ut?UCoCoTUgX=U$s=mtwEeCF*|QN>TtPC|~Md>m=3de484DJ_a~WUo;Ru&>ZaXzTSa~b~V|q&=aUHl`GbJ z_nWx1UPGcWE&{=vk7$9b8)uu zhk8Z1lthhuVHq+ygsulZnBoK2@kcGWf*FChgLq#tGexkJ*&xXLJ@UDrE2&SN;geT=T=d0rvp{Bh)WZMSDHdM`(ZPXqT9x5~q{T zURzWt7KLw0v-L4WmmfdL5{++6>TZi^GS4GX{pK(1gMN1z2lEi}Q1_|ho0&9$5MI&keBnn)iGYq&eF$X&Vt3ngtHAku#=AV1U5Lw^1PBZvRPAqxC|3y8IYle-(E zsGYH^tDK{;nYoLiiH*6boAiIYR&{eRH@5%J+LEt2>F^Ir%b%KAWh#~1-dfJ5QZN&m zRS^c=7`*|Du2QU_-v_#>d7YhY z_~*|zh|ys(A_6C7l8C}6g~NGg_@?l6Ld0*}Yv$5?j+;lD7|!s~Mh)JB(Jcqp4s*>> zIE;uTxHFk=j8OaODxA`?X2ajxeSc1TLp*6-!6U@f*&#&BDfE?ZJaGp%PvSYuo7s8}CW>;Amd|9i%L5D~k$o#FKyw4i zi-ml*RQ+dBHdz`i?*CzPL3e@@eg(s6%_*@s5t$>j2Sa&T?Xj$27koCx<%EeoOtq3+K3}xXi+jjc4p+w7C~^yD zQaJc4_TdjY1HS~SBJ^F_v5YG3L-Iht@cNZ zR}T=U$x*6@{!^^kO^2$0*qaA+1{JwdrANG!SL~I7#35lwzIJi6a;6`{PA3IKV?K!$TxLG?`{y$5uOikVqPaU0~5hu`$M?Mzs1e|iM zT*#hsi8@Urm#q|$UfyOyL}Y1hc_@1g{3(tdM-%2}lZZ%=RukMgCd`I7c@#O#Kmn0B zmnR?B|6=SNgKG_dEzz8qC$??dwryv}wsT_Jwr$(C?c~Hh@ypDcshWFl&AYGa`?G6* z|90EW~>phA0kwA(1N2cNJ}YVW;6PH ziX!H83_tBGC9klEw(*lSuCLl7Q++DkV`kGek1L8|JR3~tVyi~E1c7|BZi(>;T?(PrIMlQN0Emv&Fwg1VASj5r9L-->GJkE}b> z=2emI!ga#c%5A9kp&)Xw2=ZYg|Af1#8zr1j6g;<++4Yvw46m2p=hrjHpI``ZSeo-T z3xPqU--m4g`}XP>HR5N03nt6nm@MTQ0jTz zVS;(_HH6LNPGfdV6Hf2yLwC>dTgt^1oX|!3CET+W)mB7O{E~Tx9yK47igdjkT2zk~ z--8+faz6A_PU7s_w%XwJZwmTD=TAPj!`B|-7opS!=dEIEfWVSNbWAhAod(e3j8|EI z3=b^cW3Rf|SgNh1C`v{ahfeV|>5NQ9m)7QA5RR%rMn`OSfK`*(7WxjnXhxAI{cU_l zF?Let;kuPN(Y`QdV9n-Mj9gRUmup#hIaLo)XhCCt3oBQ=SEtBV2y4tX<&se9QiSm< z@t8R5-p!m_T*626jroQ}#zEB>x+Pr<5lqbV18*@c+}?~a)u8)u66J-bp2==PKduJH zsOrrEk`=LdGD#sn$r4mON*aj+b^4I#7c)uEkc6>JVLNq%?DYntsICg15P7I?L5o0k zA8`slSs$`ejVOzggQienpK%5?PjS2c@u+_KT$ikQNeGMPf(d+6pt8u1L}7$MEmSw7 z5DO~_(>Kw=5+rE~?@`Sqj=C)_(F^iOxUR9HrP+ zKH2_~WZB!GEN&9Bq__kOxRX`4@j0PX4KRo>5_7I$^9C`XC+Cjz4-<6#6)SF5?R^OaUd;+?a^C_IUys5T4Vf6h%NJA zK2Fp^&P{1fp}&m!yp`$<3)zx)PU+DA>tt#H<8{+~btwcBFP3p6 zEGC$f5Urdg_1Rwz`LsfJtj@M|^x!BFl+1R?0^W$oQ^W=%-PajIz z5RMh)41#o|Loo?RB-?dG3v4k4qVpj^eOQ}_sEn+8Tq=H9V@XL-#Tf||rKH5f0E#P$ zkUH)N<1L(_eF|^~oxJkhxRfF)SxOHJ9aWcbu(63sdNU~rR-cB-xF6xH@#nJcLT_i5 z2w*w~C(S%#nT7kLmL%3k$%%u6gyjXL&Lrh~4=xkgawK$}2D?BODJhJ|1Lzv1KEjHv z`>pVM(z0z3Ub-rqZ{a1|o~@M*5q;7rIWz%*@nZ@I|7vO*4O1#5VfNB+lb4%H!bu7( zNM)nf9=WR07c=B;eL;5(A(s0(*GoOuH?~r8&UK&AMe-|OR?u)0?B-327JOg#a`-B*rXx`g^4U2Kyuw`0w>GyU|V zrwDP{CkROw-cFFo#4JPtSD62PP60Wj)3b$fQCJMOVkj1&U#Uc`U5W0)>fNsp3m@X2 zIC*YN;z_={Ch2#@|LIZc^s(i%>WW8V2+h%iyWKM1vQ#q|2Mr3{acT)MW6JecAkF5rJ*|pt!v*%*U?DSyl2$VhyHmU zOPp7Rk+U#O##?=IpEC&edQxZ9nr^qKYL|%Kt`PjDt!@bC`~5t(Jt}_2r0XnCD$y@C zkjfoKRpQs3TWpKJ|1tjMN z<&Xs`K?dnnMu>bG^^g$dkfL0$4fh*tTsl6Hi_vhSD>Qk}h-QFu(f`U2ZkJTfU^ z13}Y;Jq=VOnp4VO_{yw&{ophU(VlzIJvNwDSz@h;BX~dK!n&;!8!wL7=41{&w|PXm zc|bhnMLT>H2;Kf~=Eh#q?%$*)eD@Rpbm}`k{|1?FIAK}ZFbt-bD1w~0>1hib+<(c|pLj4> zfAi!-4M!eg;YqXPjC#KF)G{M_M^0$%h>-sBCH(D6`rDWI=ZZP{U*PR0l;>AGDlmHR zXfccvm@bZJZ{Ruczz{54hjd$hL69Ncezcb>+@a2ZdunBX)nPGog|76Iw!UwbNyh;~ zB_axI8KTu3k>xzq-}4J2+5{t9E>V!*;bO31rrb8;zD?lLq}W4$KF3JP*Tl__Tm>?oC+Vy-Dr6iJANTjfkVnTR6jt}fN zPkDBp8e;Va-XXVzmWi|^fwA#DCL}j%^UnhGaRgLwM5@mmNOklTd)40f4Rd*4{}UmA z*9&5f`GZ{}FhD>w|39!x3GhGofd5S`U)cS?*7vs0jjQze0$ps2E0A>RxiJ`3glOLi zaCLDpZJrG+!*=%7Z)>-dbX^iMzMeRY=(pe@fv8;;to^|FmT=bFZF82MPY@QY&%Cc+ ze>vSs8>O|8GcMatbDVy<;F<2l*{|25-+xG?5HI^QK)D&OzgK@?%RFG5lB_g6%kZ8h z(JUYh){rUyOXlHgoxoIx16vTcWPO#B>@?k6##C4WEI!77c|>^5W11lyh7dcT|1ubX z4m>_mpx+(g;eVi2%Uu^8K?tZ2*dkj*$oPWCbO!}Oi!*ID^GS5UA9Z$SXIrVfp0wtv zQ}C&&zT_l53hbqZevxtohU$VGG)Nqld$BtN4=LCyK4IzJ^b!V_K`}ji%IVp^E0i{D z;t6YY=?ji#ER%cP#E+}YqG&$l5B*NkEM3LwQR|>3np0wh~A3 zGB1;?$;&icSLxT}y(Bm|CJud5Cf3;s&_|CSI*il^&5u9wSn%B1y~Wbqvpwc06J-iU z%^pz0<|VzC%_Roul-n&H(sUKBE!fdkGMON+kLD+3{RX0BQwhlnlbIdRRNLrHlUgXc zXNHah5w1<5cLa%YWs1%W>%*Jd3Sb+r?o6e}rwOGG&r;;#FdfB=LaN;{*?D@@9*t#f zqZ|R4qQEx5nMK5wi+kqDidl(DO)`qDD7?99Q&9=|QH2=&`xH4wj*y>7hRPdKGgGsQ zXsOLv@oF7O0j8TKDpP-<-E`s~YGf8ErbG+_GthGLN~))I^9+lZ{O1c*o3A^1>rvP0 zjCzU8WngRRYErrJ=AgkMXy)2?{8}JVy-}p5`h?-Nu^U>OV!CJY{R|EJq_vp%D#5e8J z6*ug@SJa`n%s#oMf;^{SYaV7P!ATAYOzaVO*h4}r9S-S?+w2jp6ZyL`1eMj*(RK*G z_mvHVzdI^04>LAPg%f>cO1+08l(vyJdVO?qqMPt-D?)D=L=o==CS;FjvgVE` z?Jr+4SYJX|-{SRu(Rx9<=O^ES?1%D4x9D{HHx=!5E9sGk7|xb&X_iJoBNf_f9TmZb zw)@N4KN~+=11mihzin+@xXWIuAML!3*A>nqoX7+PcZK#vBX12$A@ig`<5377BN$@B zA;m;hN~*`iZKiD2F<6^r^@eema@ZEsq<%1FqrD`1hV{u^eY#~$2`qH3+<22oj;$!3 z2k4&(sXXThp4CZl^q2XAeG3Y8(C2!qoT6H&UMFkO#$n@&aivl9huQ6mIqT^5`ljJv<;^ST28n>}Nw>as8P2jHdn{L|7kC+{ zStDSRFDo>;S#*aV2+BOjD+ucp8n)fQ78xta8x)!^TsAEkbQrtpZZ^#DJ6{mPIB}3M zIMpE>EO^kuOdCY~k7hJgxpIigV&FVEotmca%!doRiN!a8xq>4^e~V$|{MoB&3d>`a zOBNP3W$%<8&k&V#L~5$6XtoMdwQ?1H%XIj3S>!!NkKl={IAqq^p9mqb&Tu*|Ib5qD z-@M;R=xW$@uDlO6T9as71EZVkBZb9U8!C&T1zBYnF%6h*^3QAo7{nO6#WTi<&h+tk zz0?sfeDhbMVtg+uEF4OHIGN8P&u>@CdWV zy@K9^^a0dgR52%r#K*!%(JktR80tP!IK(K0$a}vPVl<_{a4^PvM2mvYN`KQ{kD~h| z%p)8qCyU$NlrX3ns zL&eXuqQ5Xu{*}^=xpm7fYnakyKC9;Z5BF2i6Zn&vc8EWeZR4`=o9#O2arVio77wl0 zNzm(D47})AZ$aSSnp^(iF~3ElcXJ?h)3T2SGA+qK8!zzX3nhoVE=o}54LKcV32t25 zDteMsZ;OCVnMJw%;!c(@(&P73<@JA_$RD0TIfVTLcl=Lq)BZR0)BgWGcbF%e@T@863dy|G}Sd zms1}zXCsFzJ-vT)`HYrr0Lu$Ybs(un2n-qKz}sicB)68b&S~%%IpNGiw@z83 zsnk%cr$W$W%Pf(WqtbpY@>_{4QT3*NB0i@%eg;dDy^w&8Qkg38UbMxLGLz@8QZ}tJ z!u0gfcaw?vEIL@3N)f0r;>3^asK#d1WT_Gxv}{SUV!LY5-g2p$3EveWFmj?`z`B%U z3FYy(XH!QDvvYy9=p@TmshW3M50sq=jm{uO;95gbpj4%EVp?(p162nkLP}k%HJzty z&)6u}?-UVnBA(>B!|^GYELEpG679GNyT!c5Gl?5MlpR*vNdl9%tJ!R}9JeLh-c>BS z!oE%%Ax7n_@9@}34l;$i3vq#4Y-i}4s)*rQv7k+gGlRu0-EvCn(a3<}8#kYBK>w|&ELHzjV&fbu*Ez5U z8er>L)FiKUr#hUbawY^-AY~>p{8nI;HiWVak-x~-B*htg4F%7|xhzT&rh8xax(M*G z9vE$457gFM1T`Ipfp02k-Co5nnOx;x6n(VHn|}j)yE)Jk;Lpx^7_9^!w$o+O;|ky_ zXOX~y?s>K;bN`gzpveW~wm3WkiaW!=9JoQ(x(x>5%_EF}c--Qr+~YdMnRay>EZ*{8 z$$1%iMC`q9W(lF*U`|xwzyFidI%z?kaq17H(Ds0A2Zx*Fca=O3 zp}PfNVSOeu@^`G4 zU6)2G8VVBu_Ukm1ZygJB|2mCPsw!4Y()8G{ldF`}rBqq9nXFhB%^llD_SX+mrMhcU z&{3rCu165+S1}X(EisTDX=c+)YN)`4Ig)ow;?QoAmpY2)e-pJ}8nw*k6mR@?iZSe| zV6h(L5w#x-SZ@GJwD2dKk@eVCq1jBh(a`GX5pPh^c5<(}4hC)=ah?F# z&GkneYd2|u9T1t`-Y%IS@32&?KENY9c5m73!%A11h2((vGaKva6kH zBdI-Jb~w&sqB`d=XZW}|%Wao0iZp0zmzG1ajo3r>o2+n{Wvr&2x_MS40aVlDteD_3 zm@eLQh5auaPXiKCu<+IaOsr4em1+B1kpZ@>Q(j)P?eNY@2LTu{?Z>?boDLGqUDA4C zeaXyOusfdf6jl-SrWT%yL`~HMLQUeBFfE`sLk`d zIRy#j-zj{jj~*OXcxT|6o_Y<+_uF0N79qSlJz|LNUbizEQuO;IK9-?^0`f6Prk5H8s>f*FJgO|%=s_JHN)tjn%~6}zC<8dn*_U_02e3Rq`4B;>_r z3-_mf3BeOUzatFrcTSacXEb^^ZZ7U;x2OxdXFmhFXmJlRpMMMRJa7*{AY8v2-nbjw z39M-r;jDo{ppm-7-+UH1`T$A_xF^zoAU5{9RV?Z``98FEjr~oyzm`5ukr}`J{AS~{^$(mEEaMSQho%5 zBNs~McV$PKj3mYv+5d5;!;%uiC%D5{7$HQGPZVO(nt^)%{&1^DPHGi;s`e&n#=>MARB;|z- zodExf^-#5PLI$AtwzOMkl%NwOp}|CpT5O;YJq759@HjJRj+?df79QQTRF2R%?1spy9C4~$?%bIjcTvBXgDMH-XSHLMqNz@@6{gzsD8 z=z23lMTctQN%179hGCqnH6RUo!+PSZ+Uo}A7gXdmGvv@8l{hXQLc7XpxRO@VniqS- z(`qhP0Jt=_Cr)OfEMhpS@@P}-7-G>Pg3Ip~p*wI4FESWut;JiX9@>$ila!%1M#pxS zW!$uwEr}W)Us8=Olie}M0)veix*+^>?FUfq z;PWjk!3<)>=p)TTIzo5V@#$~vCi;y(|KLtx>hYn0QUvrv;HtpE;^A#kOO>w(lY{1D#Twh_l>s+CjPGxWtr_C7pb) zAf=DUmdqZe6}c@C(jH0+k7yi&m@cs;!{n-~sgdfS}pAF-LOfKFkQs zr1iqBE9cy@2F_|M^}6LT(x<;8%n}Ah$%dHz>0YlHo_$!Gy|ne_9>@uGu^~9sK95ou zd!S09y);x}|LWP);ibe3xG8ILyv4a1>Y}8M_s@1enAvAN&`c+n`X8+=>MO8q8f|u5 zsxQ|!&qVMu;ho8eBK!comBK8Dqb`unJFfLE!3(rY!GvMyiyIAL7V^0XA)-j1rgt<$ ztzWO93ciAqx6nc_c?+hmgagqKhFoDRcp@pQ2(&eVv^7L2*1CdOH3!t@+M);LLo*9) zk-lz!l+DR$?fNY?PQy<%JGetz75Xw0;!XA*tPR6oFC&^@=C?SzBA)lBhy!lCf?^<^ zZY2xgbqem7ie12P+o*&~c-RB>5^f^`sU3#_=J~y?cQNpT_TK2&C7}3)PVIR7RI^Xu z8I1fT0d5X`FSBo7h|hMeu2@;bE~hW{41Mi~p=76>{xhNWYi>14Ia%ihC+ua!vvWNm zHAaq;R0;B&Tf9vGv9taZgu~b5=v&-{D{84zE2;x_I8p2>bQ=Wmj{>*@AFbxOH*;Li z42lLH`H=W)VK{n19O&pENb?Km7S_^x9}qEl+(HQae;B;~=0-WxM%(0iKKed!9?r<} zV6!i2VuM_510oh-KO~ypBnRNUu3g%`xODhqq}w1_r!eFYXu%FS&O!8_{MpG@TiKh~ zIoiqL%=^cEL@;O_pBS?p5FMA$@HR-My;rk6Lc$s@yR$Mh3%*gE?I0(-ns%X%IrqN^ z89RArxlCJQ%Wq>xC(j;cfx?Fw1iL%r=x9J@*OB!^Z)O#aOvNQ^2OJ;%8b4N} zOW}#*Nz~gS%+9G3NFSSOd5j-S_5MfJ02p3=)&Kzr=#cvVYt>m&{{K)4E7pK<$60pa zb2C%x)T-XG(`=yG6uQ5jfcB*@m;{$hxf_sTO0XUkIH)OZZmQcrU4XVnhGpRp045}5 zu~=N;F>>2Bz+)@Nr^si;z(ytrYOz3C=-Rl4CA5GfAr<|cz1*I^ysV1r^?mpGAI{EB zUk~3eKeAz$PWXIxB#6z>y^(B-FS?I*k021AbE5nO_-$vXJs)XaU)!R;@vaMV?Jr8V z-Z1rjuM4E!f&;$>MZw0QzQ?P-4^}F@J*EX~JIkU*C(hs?y2t6ehEw&q&TP<^=W5+I zxV>Ll`M*X4zpn~B->L(5TogfiHRO9I3t!HBAU=0Qqj~Nj&{;{VKj6Lo{o?)*XS?g& z`jH_sy`>1FA0V}U1aD2!(OKd#MCbGvApF@_2Aph^Mbdk?o(Zx~PwjoG2Vt*>^1Yx7 zU@Jmi6pRr@=q?dT{ZpDqWeTNmQ)FlM5LLMyiqKJ{tiY(o>Jp6>rmCQ`l`})EK+0NvC;|70!bF)5w@)L?XLrU63jg%V+od36CU_w=74kL1W-q`x;#r|l6 zz1LrPE4GoBxA3R8m3z=ta_{e+OaF=l1J?opR3%D4FP%+_Z;re>Uv3abR=C}8jp~3j^ z=725X_%)NW`*M2c&LfU zQ7*SpVO(V7{%s4g1%=v6lqRak5J{x!4pihn(y5Ra3MrJ-nSAtx+{`si;WG-(6l_uA z43;xL)Gr|mA0ig?i0~j*cg_6Xkof!{TD?P-%EjTE_FIH|wSLQe!DI$yp^P3p+MR}D zMIJztXQIhcXV4_|dVRyg%M{>hQBs<6Hm&Ze^?Oa@(KKoXnI3%x?%_IkN7iW}cVNdF zE{Oclo!U(3L3R$flKnB-b)QqpFi0ay7V@x&%17>WsYW+He6B90T8u$-cWNY+0ZH`0 z{$V&^>*Pa1SA$%ImnEzU#!9;Lxt;bC zfP1~|PGfaz`kTdG)}CQ>!J701I4v#8Unm z$IR%l6KM+U^hcZl1siIx(1FsCOA8{TTvX7^GJ@XBdbubNjp!>w`n%0x44m;-#_M#e zn!RMo#^9gB+|?@SMLDO3swjJ9Bc0b}Ex9_b^-~seSz12|wCK=+yKAe`fOyFnU8jpA zS5W<5=%Hfj1{O^X&mSrvye;|joT!r7rVRVm_z^`DZZ!4v?%S~Zhg{050@jvB6itj% zs-4;^$CDm4nE_3UglL_^NE_iw8wLMd!-{{;pCOz=X*pbrR%FV}A=_pk$!>8?r#9No z(`10Eno>e|%t*3dAx^eEvFOVY%4;l}*=|ttltE8XgK7;-qT^4@RFT3Z_O8gf&GUM{ z63yBPj?~u(x;t_d$E6^$q_#skL>>2c9?EYKrB& zM5eaShiGd~(A61_h4|+yWQ;h-M|)nTeM#zWr$9 zxj?%T{$zbb^(W7mN2m#gsKoVQD zo%KP61vSi_D2nl7zKfR<+?q}8UKA{Kp@mDF=2x06UYvCv{S>S1iv9$0OwoIRnP!Hs<4PEMhTbR5>tn{G;YlnVx6ci(1i{d?&1e(+*FpoRJ?QR)UUKi+HSSXbTN z5p!ul;imqIrvdHo3@?R#{9<@P=OCH}Ml5uOHPgVxxiBXT1~GZx6k2VB+ zng)(10UlTkrE}jjVse9z>s8hVSW_svp^PEtgV}~srQ`LV7@Qz@Hx3iqC%jTMqmRXO z1Yfncp+Nr-H)_vD?lK;;bR*`U{+Z`2-mluAP)mdQb#(5`!uZhLF_RV-4| zf-Gnjd|!$L;Lbq>Nym$YXRy^Li&7Y)RG~s}g)>#WXN4N9mbZe5D*!P6@&$JTw_a`? zQJlyP9^02w%xR$|+K|g+bt(i;lJ=?#W@4fG^AemPCOTKPcInNWr7!k6kXSN5?MSLo zs4atamCrx9Bq1zmsdCWxryYXLebrh36ft?TU<2T-4&a!~X* zWG8Fjs@|JUi6`I> zK*i`EHsn3r^uN;!3rmwgA}c6vAh*(*%acb>9++d)4yPQ`*(DQKV-Kn)20LbuCps9P z0RV=ZV(#MpUr}`+$vezPE76v4H;ff*?=d`-F$S_d7Ps9>1NPLEYGedd0LmpXTGD|X zmSu*u0HC0X?BV$s>N}d_h(_=zVBUv~bK5NT^S3QgKf3B05qx?B)H4p=(ei6XIB*rT z=YI?KQVfYh@_nbJeQ%t}5&CDp6sQz2+NI!wD_~utykkX71r6i97gc7v(7;N{f5q3e zK`_=8wH~u1ziG+MDPh|zav?F_{4?#=472nf>byO2Q=!=@gudfj4%=Ncx`Vh6gJ+rL zk$is(?*QfV#q>p41nx-v0#2d2eUCtU7sKzB@+((|^n7G#h&@d&7*)$e<8_nHt6k8S zOh{DP5t|3`hf$VZOU(Pw%wBJ!pKf#gw@yctd1W+F@1hW19cgK^9RjynR=Gu?+G6oB zB7=Qx^|+c4i6@@5kpCm1^oKq3U~EO+b@LBp3`g&#;e{PSvryw}Yr>8ylhu;DymAi2 zWD`w(_W3KX^{$zrUs{9>AxJEiMXt6a-Y^nn_9!0;Tlr_43=Z8Ayc~TWPh)Xr%c=Eh zyk%#OqfI9qj}Apdg4awjD$Ib%=P!$HaoZas4f@BD=NHBM48d!lvD5M~*V11_8W5B# zf2oPyt-~k;39yR&kmO+%wxXuwA3gLR> zi=3drSf<=vteaRfGV}cILXLCTAUX8a9gzuvD0-S!8e!6)mmF`y@i(m3p$9q@rE2ar z>7!h9n4n3$xFL~~xvmA@TPjVmmQ^T^2}icp#4=Z!QvdaaHRO$J)FT;bZHGN%J=c#- z!qHkaBx8ciOsvBPw1ZE05=zF{I!kyWXt7OS?KGS0xhW;F;<;~0d(*3$6Ffp zTMsy@q=gZvLW#QqP3wUz{9+a=$PV0l}{bAO(V7RU#s5RH@4OMpJ)8xy0g&!{DW(tct>1`ZVH@ z97KUNgYFv6IDq>U&oDw_Ru!s|SkE*{K~~SXRy5E4LGn`R{zJR|SYA5+@hvpO;fNt_ zV%p;rtOR{JpwXqZXJE~rCo_-MswNu#F@i08yeq=uv%Akpd;O})om4c4cVzzP3cj2$ zqf~ECzE7T2%yz_PrD{zSZx7r^qM!3Tmiw!4z3`zAMvrxe_=V~TtCyk(!-pPgp2c?G(A5&WW3xI#Jl9 z=@A8A5{4{fbRQh9oCBh_lt%JaWsAm5j>2X;yT)B{fL^Xr z%$UZig6%H$rOZ^<#w|nfa_U6rEg&Szo?0GNrF^4IPqj?5`2yG>IsJaJ@Vz@* z3lid4!T;mwQN9Vpt_a3i>8(T6G?u7a$rWxGC{&BQlBg=}eZ0c6rZkh9;hu#$DIqFRN2Fy+4?v6!`KyZq zbWKI=3WEt1?}+3O2;^fqe+7reg_|UN))1)a;Mg?Hi?cF1=R9M2h_gKUC`U6Wo|H^1z2<5oX4ySxzI;4XL~Guv7ExU{^F_kR~tH5!&= zgZ!0zR z>3I&;j_i3Z+63Bg&h$nnX0rr#m-w*Y(PPrzsCStBA!eFWZbt9N95_#weM$y0E1Pgy z!s*@<@wM9b0Cy{bSk(iU(+fQ7*7vZw_lkY{LF?WFlXK&L`mX5_bIvo$mr$D6Q~C{t zACF%w9UJ%U(2?{F4~ddmX%uBhQ0%`No!ZD z?#B^q+$&mN-3@HI4aG2G<^pz?5&XSkeo>zpQ*ud4X09Kb%^jzmhpyT8uA3bGudi2h zAkD7Y0|%WbW0X`BotXWI2nJKssqyMn6%j^ybC0WdYjD=TM++N@Tl*?hnOd-IqX*I@qrDln`9nL3DJ zIxW;9e-r<}=rx;gusJo>$0)wCbEMixpem!Y`KCBluJ6sX!v>>E}n2Kb!2H<>k0Mso>AYf$mw-!a8UU+sxhE01 z`pZ3{%=>owFEWG8d}ti;Z~Si(L1~y8C`ATda_ec##WNt;Ipn0(pgAM2pg99=eeRiN zGYwI!+Z|_B9;yx2=fn&PNm`YjF6x!*HEK*v8D^SDXC-~N`?x867&%FF$WSH{+4`hB^-!&-j zF9sL459Vfb&)iK=fb`?gxy!sXD|UF$k>Bvy+B#&G*~NIrRwTEeyLm-4Fu7%MG!gub zh#aHtGg|X}au>=XZNMc9aAD5w5cyL#XY0akX;Cb5&AG2|^R$m8%)LY%AjfoX3tnMp z%y2{IJ1-8o`y6sQd(iWy2%|Kf45R2)Ik1jtL#kQ*tXX-Z_RLsHTfx0j#KK~;j<`QY zFS*Aj6ziXo+HbR^ej*>gI*2xPf?DQKQ(yq-mO0ML&Ou%6$?KvoXwy@_58|&nzdw@Y zZzfDUBYDoP^89vJ0l;Rj*Lb6petgr4Gt)eDd9lWb-X`gzvX70KiU0m|{GBQmIiBd! zBoq&%?U~rT=(0J$VM}`r^^L(QjJi)fmbxv%pFIStNfVEA6Y<3pj8V=4#N>~;N>7*F zd@6<-<);|s1F17SrvnjRYzm-1;I_E|ogm|r%{xOH1bx&6OnMUBW6+cLCV0Z3C5mHV zY@}A4E(TbdZsZMu{^BbX$~6YkpZiMSbt?_L&_FOr<2wbe`8Wx*e|U^nN$_1Z!Soxb z!!O#Fm81goo|f2+`_~%vpev3vu7AmoM{VF`$ECZijN zKn4P9WD!1ZtjA*UoRq^pw&=QX+V2b~qR}uzMenX#{}cT`rYRdDT|LO36y^N;|7)6W z=i>Z7PJV?voB@K4j)orc|MT5y;L|MEhrP8HlGem zq8H=WDgfDe_Xb)rnbWi8--?{ZiJ7|}({vkj;R;zzHSaa9?&oYTGhVVA&vv&1huoPB z4?GK-QHTQ3PfX@X_+?FM3#OH&tN<)-)h=HW*_UKYvD}>Oej`F-osbLYL-8;!aVisqH6#KG$ zY-#ic#tS;_Bc3`7uFfDNTwM-=kh*|sK!cQFzXtA&XlB4RAW*Tvc}C{Z>=arup%Ii- zaN}%Q>)H+WL2QQ)zzTC?%{))@Z*jZokl9@YHh0+p1$iKP_WW$*7hLY@9mdSLd1yXy zfFLfe(Q>#NF8)w8R!`nxq`S^&s=Z!6E$TJY8{<^``mYA+9O~Cn7hq!i`y{Tn9abtj6=O5%ZoOUZvXr+KlkT!%1G((FOa zn*mjN^jt1DZFI_{N<~NT^m{172x%;vfckN}_l;e7#B!~nCon$1sUYd&A~||0tI4RK z+lDe1m+gU^B8TM^;1(7E?UH`?4_td@`PnPDDqAZzg1Nm*w5vk)mkmv}VKio@PK>Q1 z33hlUt>_dthaDQe*4oC)?f4x5L0F$^vO!nYI4`Fghg0c;PwkeSKhZ?U;d@);t z>t9tG;dUC*-P!q3%(hJUCBn-?Df)?hkhNLOGmgD8cc6--Z9AI62@QWMN6f#dZGp1} zr_el!)N}3!Hf4GN^ld>Iq-T?5-L{~q^~KioeHsGy6>X?47{h8&^CO^F+r(FP^c(h< zk*Yy=x93PM$L>sH*>M%@dL^%wWt9VPTSI>IDO2ty7^cA!ZwV8g-eZdE-Dp_@m-p}- z;>k)^sw{PeBe|i5GCfh%JR2=sKEW{8e^qP#!U>Pj4f=s?qA^mD=n31@joK?(BX%C6 zy6}y8Sbd;c-*~8t=3D9gMNk|5k)2fx_L>M2RhYcvV<8APEjsrM`E@iO3Jh)!jX%N@ zbLhL~j=;$i)aafzeU6CPA(#b`Hoe(aQLvx4EZYr!c5_W}b6vLcmc+kHU@vDN+Z{{M8+Ej< zFIPkVFt(x=c_fnLAL+mgoX`smt9K&OtR#Xs)E;BX_EpfhG;JXg1G`H!0~C0>EBW_Z z`vRy5sR25qPvy}*@U^{xeqNDaNi)J_^ML2n{!R~)DK8<^ic=Xf4oul1a5SC+8Z;jx zT^cgN6jYLAgYw1~TVjg@Xip%ScWN(KuXwHg-pkD-M@FQY)Yny^AJH9O!4mza6{21G zp{j-wa6+O#IvdK0G-~&N?q5{5JEi2VEy9;FYnI_ib}Blmnc>b?{o`wdD{*OBtAHKo z^0>0nKQvT~Rn(mg&sWYM|DZ-!F=pl0f1J4q^lIzS9IKk^4EPS(JOW)sx&II#8us`6s+<%I>{ilmDOjETUn;!x4W$ap^LvusTGBJUmY73Q$D0JQU(+tr??|qCDXx>ML|lWTMd`8M z|EapD&&P3c|Iu_f_&FhB|Gzdt|EszX{$HXF|FyU%R@IfqR!87n-L+;DuojYrYPQ%I zN&`*wC8eY!Eg?+fLVyYgx^H4bZ;bD@qP0TGc~fcT|M?w1!LEdh(WIl3dS1knb2ROu z2`LnpQrjDy%w{;58?C-SXLIR+Q0h$$KyzUU0_BH0DNj<;Ho=W^htq>mJ2jVgLR4)S5^CC5E9)&hT6jYH(|WCLD7{r2 zx;^JDHJf#HtJ;jR?)_#jLFZs=dm5N6~a*bFRsU4i4nadmSi zS`>rmI{)=tx3>5^4D*jJI34XBTHmkcYA zQ)8I28Dxc`(LX0wADVVy`(S4SKTu@iTwj>Ob}@5sRKuYrIc)EWg2sW^q=3T0KN`lO zncFc)X@ckcrP3z`Jn7nAQ*(1O9%ZctS+OWL&CJha$z*2oYVN(*%P;(=Eum{lnpP?M z^XB|oMAz1Mj5O&2wLv28l7yF6r~7?)pL5r3imP;7lMOgJ7-bT_aDNq~^>#1}d!)*Z zGxji_WoD>@SiE;ld`Ck!=;g<*#k3d23~Cjle#IYD8*AFc`2gX+`>M2g32%qa>40=5Er-@arW_wG zJ@Hvqgs{z5C!Z7H?RvFK8S{F%Y)D+rJ<4i>Dy5F|Ke9T)vwMgFkrr%p@%KP`E=&*@ zknXp_zZf*SfcT;W>Um}=~UH0n$Nr>U>UZ+`wRJ`i_borjNZ%agUxsx0D8p*pM z;u`$Nqk@Bblmii9(eMI}3+#7IW_~?=J!@+{-J7K9|DDij@@f{?0;pHr3yv{E-9c&0 zMzXwP{vplviKAj<60j0;-XSsXNF+8jxz!AkP4!%|kFWykzJe_jATCqWK6-*2G$v^L zArhou1B_ST65~zM^ia3G4x(XhQd*Kt%R$;sYSQy7=Tj(d7<}XPu4(4OMQZw{;^kBb z@EC1+V*je(11wc59@EzBk>SQ=TEa`3LdQ}x6?hr(kQhv~O04CMV>fl=PCYamidD>I z0nMtk@#)}3S*tbmfD{4$6_r^P(IiTJ_oV!N`*OpCe)0jy=~0R&^&9&(;W`seY5MEK zN;m-tbIxVyTN($1a&;_;CU|q;MQxZ_UUAA}Mnuoc23(`OH1fNA6)NT``B|b0Sj1`Q z$mEU*cx?5HkuwTVEo=-Hny9%~RM;)h(Zzd^O+4hR5{el*PoJ$Nt~#(1SCV-%?V8&p z;A(2tzs0=RTyZmf?NRe~>Xy^fO z+!aT@9tE6nEBGGU)Hh^yTj5gr%s4IW@qO@GOp^aCZxUwML~w{Aw@8@&0{d@(Ov z?bLJW&9!l<$3e;mBkZCo`JH?shAh?cHwP!#vlNi-1QIP|=f(5YU|N{R7w+JdRj~x( z9SJR9E@ORznpVZ2M}E<@O1*BTET(1_OrI$lm>N)zWxJNmwG`%3IKRfgvWo90(Q*dC z@McXNVjv&IGaiMf7DO4EgJ(0~=IuchQ$USD!eK%dy%2N+d#s}irlo@|S`r)&&kwgE z#Lt{rA{O}=>jLTJ9+s*l5|;IPj`#5j$-RXRF;(jG{*U@Hpd0C42&_~GIX#ixNTKI+PB2(mXYZI*ZSJF$WKmPTLa^Xv z^e4aPBg5RNa)|h7m)y(4-|aM&|H7N-Toz($FZ0s#X#+ip(-(jJtRXj~p099ELnW#{ z_!Pe8f%f{qApOxGn4bm$;> zJ<#}Nij$VvCC|2AQliw(E7^GKTjgOYh>E?eQMSk|7vSaK#>Sg?Wb{@cFbJ}%XO8HZ z-zpOl%sgKYb5^~h0h&gHNbS^Q;1_ulc)r=d3xTb4Jj{iTmLfv6sa6Ox!NKf^NoYoa z0!iT#gO6L~jDg_0j6sK#efz%VopLkRVXr+%FAP}yp7ow%e*S9k?lrlwr<|i4Iwx+G z8CL#hh;fYh5Svwt9~U2reiRK~;6)l3z!{h6?!^a)^zz?T=dVyNGKicG7vQsTid+=I zFS-y!)l@FILlHIQ#dWF?9znR#&2G=o7&YcJDqJD%B$E*(Kg{?l({1BWn@e!tZ7ONH z-eEY&Mc@7E{2bRaTn|lTkT_EA@qB=<~>ytX5>Pvr3o~>4DjSR z;1vcEe4T8ehIRg%SYMXY{ee)NgTgl8D)3@;v~=#Wgt5EVB#-DOI>{Bz`z@i>LkcB7 zLNMpc&Xr;YvN`xSl}Nx}S3pfOuvhU4wvh z=Dkk3iW)lF&k2!-B!I3HTD8d4O~16FZ~6F*b6*P5IcmA~+k*@wYMe)!ms0^)dl3hL zFWdBkxzw4-pqF%+fzD_XOSFOQghD=;laRms{2xHb5n$9bHRldb$X z=5=#Y-=OY3ZwCmp*L_GhRu$n)3Ns%8I!|BU`x45asJQx?(WEg>R;yt|6yx!tC#u^N z^RHEPiWH=KbMoR8<#}?ty8z;PyzIu%^vu&_@7_x{X@3OpU9CCF z%7|0T4Ez&F>97@dS0>Mhr4{Q%6fRc=PppfMlW6SQaLivy5IKgI;n7x zVL#tpd3?r2)k)~?Dr1j#q$4|phq9b-d0K%VF>nZt--6s`%<;KX?w;zKoj#C>WqA;( z6--P?*jY^cJpSuYxG@G-1%Cs*#Hb|`2G{w3HCNW6w)wH?;zv_9VjpXST{xm2n(wg0 zIBPpLd-qEcMlldV+c*%VPxKn+NPv$s7nxV>h-~D=6J?b0%aM?}Oue{ILul|=n@g9GslRd=GY3zXBoy>3bUyT*NWtUGA%^7lpEBkr+?$Wep)mm6v~wzg)<|AR5~izxRq+e}f}8GcQp4f}hbs*Hm2W4Jz3M zs&9xDT5T_>`NHcEn+}Y|=?aHl7;ZX?f#|73DJq)9eU6%Cb$4o{C)(=d$_{;C#6$|L zc~c^CTE^s+1`~DMOa%hs5)*^Cp;6oaMXf*nZM=et$%H4TngL<2pAva8TW4SFR{9K2dUwp*VXJ2+pCFxC_! z%{#6uI9ph+zI&2FIjVKUBvCEQmLPr;cJdUfJNtt$X?8Mh^GrvfA<85CmY2&Zb$mSQ zvUM==L*<89=wte3z51dgVL8ETR+3%zwgcQ^<8!EpGwzrV`YnaKU>#+;#2iNg4k%ju znkibs640o~kln!HL6JnmjAYz|C$%4Qq6PbEJIjV73r2gTljhIZW)I=u5>5vw$-6>+ zP_SY=W;j2)YNp|2%8^;x3F-hlD>8;0WsdxF3&0Kjf+mWu?eD#?W z)`S_u9F%m>tC|%{aV!JB^cjwXnyu6(%|aX7q!-uP_bK>AM<0$wlT0gWWbx@z z@9rgj~m~^(<)!4c0 zK6W`YOvy@rmx44!>Om$dSF3x0%ohiqD|E~57Qtb>66XCTF~?-JIZ=oOIrQ!a;rBw^ zIiEYuz8g>p8w%jss5}cIRhQOP!dUhMvCDL*Fq@MbY&#Hb@h0iHIoX5|n8SPwsS5zMb+1rQ27Wb zVvWZQW$M%UsDe_Z^J-W^2ar<@WnRHyk(sJ6h8g!RRxm;hB~^O`fpG{6;Abr($W04L zhX|F!#XzlCT?IOWDlLgjY|6bJjA8nSit>rcTmZsBd&as8Vm9+NWw^eE)q5+yK_XCd z6B49dL@O|Jg6AsX+3NeUmfBXOF%|PnhG)=J7!j-_5K8ZuqiHjq&^yK#b#vs!fs5Pd zPfAl+hEJb?DlT)Vo*hqzD_K)yvYSY}ff@N|>X8F!KgFHID5+{JvI z?3)nVp1zhMm8}IMLHaJX2vDdB2maO<1F6x>)zzQoh%VVxhpS~xFwiTPA<6VS8{l+{ zm9^QfKpmcU!L%^d>=C~Um|VY_q!yE>gu1{Hvp!lg>F*N?V|wNg9+Wca6dTTv+d1ou z2WwE~EIro~YqYs5nWY7>UFrJ-cd({ZdZL(`|n`>T!uW{91SqQ$SJ-&eS-<87YZjJ0{olah8lQ~~A@6u!21c_o< zW-$D?AEl<>nL6@hTAnq2XiH~$kZV=9CLxKlBPoEjmam6eoE}Yx%!2X6I)#TLg@aHX zx|_k7h{=xycxwasz*&EdS`pMU`Tk>;70}I;m~K&GS5dS=AjsaC*^7`Q*sBWfMaupA zwjV>l6_rrW#PWCJo80r?x0SwMl@~B?Wu%LDpxsqbflEYkH{sfq1)WR)k!0XHYs&*` z>Zxa^q*`=#enp7?)Yr3uV~zJ!FRv2oI|ea1mxL#cVT=yxbzhL}U%$nuiMD@Dq16-R ztptHTL%dx_g5Qv2Ulm)n5@>UM?Z8e_5mXaSxmi+;vTb%&F_Xk=Vm;dZJd)Y{Y57s6=U^j$JT}$(Bq$>V$ zbt?#x6ydbi^Lrp5@xb|m@vdFkO$5K696e+PBs+jm$Y9h8+ydOLB;8>=$XkL+SQrl_ zk`a|(OqF37N70)F#43KJRwALf1OcK;8ocUu(2iMf`LdGgVz7GlX%E^nteB%hxw6b$ z%jdBJ6)#c-o$jWK8^nsdrZb$F8Ip4v(L?7EaQyRF^4i>iuUrwn!DBf{5nZwE<^${f zt0-T>IcMW|9(5K_=TOHuM!&Hq-TPYN-^{x2X!+*Ff*=9Oy&^6{ws5RLf4C%`kmJZ~ zB)=zFhfTyzkK;YgIm07_i5~sokC~*|OC|oZ+2e2*+FQ<%)2-Rt@Tv6Y^b-8BR1F zKWb_+}e;*h*nz+RiA?Oaxk~SXuuDNQ1cy;xN4Zo+DN$@so^w66f)0d?{0^% zQHlq5hb;v02=&65)zs?xIfJfJ94sc~Bs#WF)Usfl zYv->=UR4iwh`F|Hh{C&ymEOUUvc)=;THc?ivr2<*dok+kF4%VFSzzb|*4b`h-N&Tn z&l=}RE@Qy=keLxudYia>OhEg7cEKBE3Yj&9-smYtWHsnhAzp)(EmBOnc9n%*$zyB# zsjg|%JUIi#=2TnKNu>%9l zNhZK3d8bX_ujm>-nV8WQ7NgRr-5F{yxw#E}B>mmCDc22Iwt_lL8^|i^yu7J>gRbRD zx$*_~M9+oxbdny51Cbwg`7qy-<6`25AVu4}F;W(-JE5=4!2Om6zc@fz%5ShHL zr+u-kv;CvAlI8PuFs}|+i**4PP=)NTeOIPx=@z)%BV^&>S#8yjZymzHKf1MU!cbK= zzq}y9vz=Q^oBu4AJ9muR=wS+pw=b~ z?GynN0m=e^rzK2M8qS^J!=_8V2&v9i~i(j$cNs5MrOsDygF17Dh~2l3Yoe#D9@cE<&YLHs07hpo)u! zCd4EXdJ|4EZAo4|H@WRezn*F->EZ@~r5h+4tULc=4~FMtnfn8EvG$z;k}*}$w(2nG zeU(Ew)eK@oo2O6t5@eYSunR1qS;z*4`bLBz`#D8u;K(ZG?)$x-5Fn+`aI;?{gzgh1AdlSuFYyMxl^}`|(tzm(D6Om}i1okyG z`b=S!LRAfF)ofu`M6#;SllI7ooV@1saeEal_^oI?nM)NC2o&1Odsk#DY@NK8#B*0GSr_E@hL4VS!`ji*~@St$~+Ae#$?HW;bc~;h7zro>QgRR9mU#**}Pp((4?YdcE!6u z(ScVqi>TP*pP-$TmuQ1+f}UMBi+T<2f)=n5`+q*9(_q8Xz)nks0NZ!<9=_Tfzc?=l5doe7f+G+I|7 zvRNV4z}dW2cAdIYD2yItbHpu@E+i}q5zAu+%kl_e#3r%DQPf=qMUu86uG3I!B&-OXm_=^z?!|IV!amKFoHu_%%*%)nhPVk?% z-lHpz;YRjjoAYgdmB<|wK~(e5jGls#f{_6;DU>3V!U}Mx2;zRdq^8kG99hs@xuBM} znRLYS4*Pz-*N$?UM*oyeqj?$ai;YS-eIeL2&?t-rY@rt~SMAeQc=DG{4HUB_qU47v zi;wF+HEv0HM`Z$`i29$r^)ON89583g+qQeES^IV?U6G5J>3 z7iJ@f(lih5BL^hZjKQjB1+5ftry>_BNXj(p#~>H^&PXO;z0+fzF-179LQ+UbpFci- zlfrrg(Xx$GKWF^(y+D#urWVpH7P_TaC&QPnz0hqK{7o!8ZN5$t=KG@8jQHCtrJqS# z9M1NP+eb@JUQOg@!%{szaOTi)o`9cNHz59;KcekO#!KH3B0N&hzhFWp-8YQgh2$%9 z4*ChD9%j^*c_EvPRuGA)B#su{OCSW})PYoSlH1YG|B|O1YC2YC@RLxWXqdWtpt7Udw#zW3UV@J&PO@^v6I({)a<;ec9nc{P(@t%)AmL+%_atFjSMM^Qp_?_ zq!H|+p-8LBF9P`!uZhQTN54c6wK5N9>|PHyu*KN94;K2VL===rz9zkX;YPH1nDL`> zt_~KGI0KumL*Tmq&Kp8`Lu)-7Lkm+~Adka;pUY`~Cvtd;H(SjvGUuvg$xDB4L)=h< zYX($gHu9akU|yUdYFr+*c&Z4;3l{>H!Jc3Qaxvv{P_N7sbJdrkVNnge=s_9MGJ8^p zYg|$!B`f9)fF<){fpcw+UFqP&1Ht|tswKT2(}JirltsSb0um!-JzHNI+D>ZXf%fVr zdoC{rV&zE4kZ31bFIZ?Qa-y%IokogH8x7tR6htkCbuByZF`G**iJF>s^`@;RaGmTV z9&@ctXk4dyN6|`_?kXQn;QGCcR&$`KHa3yenbs^~U{v4lx3}vuuS13u=`5|2@M9z{ zEmtY4-Lmc?k8M^RDq?{xQQB3#QXHVA+L38I8X9IQ|gaA5c0#!E4bihGn zFywN>dp+niO&J=505$D~ndv*l+~O(0>haff{R0xO)sK?-Me9=RN*`~B#c@?Xs}&I1 zvxbt8v>Dq=I5Ftj*CwssjoYDeE^a7|!I=r1Rp(-5Ua+$C*{Z%&!H|4CM=pU}FqXkf z@(ee4{WAxydn5vG5;l^oQj=o#7F9jNOdqMlwl7GsBSeolPJ3CzB4$>YmtRXq<73~x+YjTb^fN;)JUkjVJ>(TKM^ z1a7-BG(It6S6sz|S7jQP|I6pvw<8ab!O7a&AcdEaD?+>3OXvD;+EPB@{I8WyRsGW#KGR466fRN z^sz0ejZrsNgI(h?55G*GL_7Q__LrA+l2;?doak+2I6XmStjOp+JIAQc*45P8`Z7;n zD}sdzmzP;wpHd=2T!bHQoEE_v6L~UxghYfJ)+h2AK+<0%6^GKcpIrcZ!q&qxFxAez zBgyfx`;C% zmIzv50XHcjKs<<>K*hJ(`LBBa`f6!mM&oE^x{Wn!J) z(C^)X>^P;L)XGY}+&I{EO^ z(z1Oj$5=W?cDGO$!rv_(5}vy`p6f%(1Y(h8g~(&!w@K?0h<0+c>j}gOCR)?)P~_fA zDFbO*kr5;aCb)O>R-!sbo~&TK%mxDlf8+-;1u05yT>ofAoBZr#id z+O%*l+NUGerf2N!rKuW*)mUc>yyd}NM3z^h$gNZJZsFH1)>~i>yyu~mX2yBC3Z_yj zgXK`Kqa36|m}Qi?uQ$f=8$Gt3!lE5>WOqqC9#D6w7i5Vh5DRz-2}evDYYn<&Wfepz z9Q59l7Zg6yJ11kn+6rC)5yK$zWVGv3_IS9-#yr0-6V1%q%Hg`YdzJFJn7Ohf$$55| z`i8243M8Bknons}^{t|%cODrCb~0k9aW5#+tmF-N9TO6d`k;b3py>DU7J~Lsy(@Bl zh{k)-$>?IryUDj?gwEK^&m)d%%V3)kA4^oTyp5TRu`cP}+vb5~I#2&3lnh}hmYgk( zBTr%oJ-%;>5@scO@DXZ`CYC@V9m-Uff7PLUL1;SS1Gm`s!Ti*5c|i_+L{?UNvYE!t zRF#EA^l*+;UFtHX-pM8m=%thumT2#SZ6y$xsJwWl}``c_>XMe`NxXS6W*@pitA?a>On-uO#O!8N7!?GB}pp-yE~y_Yh@ zPO4}|nqS=?RS9y@2V@muWoEv3nJ)?lt7OSqsH~plu7SC|j3b8x8fs~820LqGwNgzG z4f>^2bw}CrBwQWoFt2K+3tf3{@u?hjM{Pa!gQey3urB&gble617s?4m5LXgsPW_-r zD2MEy)}KTzpC{Wn4p5JT+Bc{_ZY6L1AWMUJZb-ox&aRFZlRI?T(8{)&Vm&d*l*a3i z;OR!i&}XCs2KmHvew%jXL6b@RJCoDI-Np3M^quW3b_%18KweVceibFQ=jf;!)6`ZD zL$vOhh?kpZ%=D~}u~sM8oW)k1DH;ya9wwI{D!+NQblr^O`*{e}mZqwc%?F`jzVcP+ zT)PdupaEZu2e8g?H4V-%YHXkQcxQTH_y^KU(1E@g7~aTDeCSdjHXsW2;&xs zl2i6JSXYP1U( zyF6Rl2({8=L_yb!u9^_*Ey-V|GF9KOt2^1kbY}*Y%9t*W1AlUAl@F3Y)6j)(Noh}~ zsOnuTczI6-RX%ooNZp4OFCFv2Go5pDsvQwJLZoK=n7$Hp0>x7mpO&sYzSid@(nBpU zf}x1Ro8r^+V&zTyw8IDy{%ZkWl}aP$5iLS{W8m`?-gR&JHI8j8D^}ade^7m#^vD(sVAIuIhU&o%=Uql^h6z$3zy#ibru z;0+?bo1k872GeXQTvn%e`{+V{!fh3d@O)WF07QqMe;l3EZ@K}Gkqdi-C@d&%VJzfe zC4b6lckJcPa~v*e=1TbF*-j;auKb3$Oqhj|K%5bpDww6X;nh+anzoc)iOmIZHE%VD zWg1u$X}d{+f=NHW9NEOLZg$`pJ40_du;f@-Qt-KtC`%QksXHeN2YDxVFj#3UN*)df zVKDV>S>R6asV_}P=T(6RO*&D;8XWRDrSv>7Zwi{1$VbGW9YSyqtaj62gtw?D_BSGJ zBD8dXCt1>;ypr-qdH+nA$%2D}0$Jky>sj7`44NqP$1xYpq#41_v!B*vrj@0ZbLq&T zCg!|;{@%mFha=KZEzsCjDe5JZJc3090yY@S<5HF2#k>*0Phkz|uBv)hHrIXm+N)pZ zSSraLE~+DcwtU*!PW?{78DjFo#0<0ZoV=dT`k}P3{&1K3&PPk8bp)vhX6%M!!l*Ok z=^Zo=yTx_C7M{;oYF7!5YAx#>^)|cVK>JA42}-s~#jf+V%t@jd)X+gRxX@y8 z_+q#r)Km>0KGaY+xIw4-e%X*<#)AV>_@mIHP{;bwkbN;&4ms190~?!zgJ3(x1E+fA z5K)FP2( zfqMkNIp?2}<3$R5|EHP+kEDp8fSf#ygy44s5D<-9J7>UH^LM!+W&|)TtPOxo@b{+P zt^ows{Z|czziVh&YUvp2{k9^?w|XEqRPH}k{Cn^FTLAPQ07`!j@Yfdp*R6m21IEfP zG4ugfCg20OGX1tmI6uG<0Oqe7@%=4~Z+!xqWb{{J(%*>{_Xi~PUn2c&K>H2_1)>g& zX~qEUb64OPL;+kY{iqcQ0MtKPAuhrvASow6W9w*p8zFv~mox-uL<8{oK2#teIbH(D z|3Z+Ik_Rwu1KcP{LaA87BA`!ZfQH&Bl&T0RY9Ip!CxcKw7-y-@sFz>qLb_?K^z6A`*&HVgUa=erQRs1=y zfVr-bmiceQVwDwDV+V-k2)F|H5mb%hSD-h(d3>gNTIS!J^KOfvVA(_y1Tc|h0-*ms zT<3V90Xp^bmi_|@SjyzBwRCRe4aA8Dmeik110Ai=lniLN2{4{FZ{2(=IbKF|{~wOO zxqeOpegu;;0T7e{OD*j;2HcsaG(tYMcSR5c~lg8VEW5YjA!& zQ$1Tf;JsumTRj6OB0&ploxj{AfBgN=IUh79n{WUlBL*nw&5h5ulH+y4_UD{G#~vWD z5o!baqYdB#VE>dHFAvUNVgGAw%O~a%>6%pnF4wc4^VM@>k05P z$E#oLPiQQFHR@(L)iS+{N&V@V&hgGJ(fn`;wErBS`Eh-$)%z0)i#yl& zv)*e$9bl4o0f(6%r37yH2MkiycS-3&xtfd!Xub-dQ$LP%z3CqiNLvFtddIs+_&P3C zn1MO*3LyG%C>SjNfJDy7`OcxRpLbWg>4(yP`q60pClq%2f6$LUsj}31V7!I@(+?*5 zKOm8}w!5<*ObBEu$iRT}0t5cz%FgZh2NX(1rn)*>)^|P;%b}U!Qh{EeLEavb0@wc^ z%`dkn+_jxw`G7-;5(x+h`M22({LJx6^!ydYjUt%?6Yn>R6G2UZnkI0fcmW*lemqqW z#QqLe&dJ7B&+IqAqSqd4S9l;GYNEG~T7B8S1O5#pMVL!A2%wNpfc+EU45wi_3tDr2R)ldIs*``uo-XJ1K7GbN|+(9Iwl_zo+GjGT5`i{y8{wvhKja$Eo=5?Fpb{voIEGh7Ju>3lz$8D0^@ddt<7$V(4^8085 zw~21QOZ=Va6R`LExpm=hnff~?{$7>Xnc4F!>``*yiIZYrG)PkS_F4c z{QMP#+XT10=)V&n5&Z$dzujSPL*I7X{tkUcdw#}B5y3;(x1b>E5pdo=>=pZ+d4*9IE&_r6=Rzj^w6H}~JZ zR{@N-|Ee7P-^%~;x%piqh`+)7b$8M6Tb!Rl{@fSn8)}XhIKzMX^)H-%ef`@r=zC+K o{<=)MeFOF#*@*rBf&BeGOj-;a*bM?kN8n!xAVwSx;4lI5f9ZeOod5s; literal 0 HcmV?d00001 diff --git a/gradle-plugin-shadow/src/test/resources/jar1/META-INF/LICENSE.txt b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/LICENSE.txt new file mode 100644 index 0000000..6b0b127 --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/LICENSE.txt @@ -0,0 +1,203 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/gradle-plugin-shadow/src/test/resources/jar1/META-INF/MANIFEST.MF b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/MANIFEST.MF new file mode 100644 index 0000000..df85c4a --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/MANIFEST.MF @@ -0,0 +1,15 @@ +Manifest-Version: 1.0 +Archiver-Version: Plexus Archiver +Created-By: Apache Maven +Built-By: jwi +Build-Jdk: 1.6.0_01 +Implementation-Title: Commons IO +Implementation-Vendor: The Apache Software Foundation +Implementation-Vendor-Id: org.apache +Implementation-Version: 1.3.2 +Specification-Title: Commons IO +Specification-Vendor: The Apache Software Foundation +Specification-Version: 1.3.2 +X-Compile-Source-JDK: 1.3 +X-Compile-Target-JDK: 1.3 + diff --git a/gradle-plugin-shadow/src/test/resources/jar1/META-INF/NOTICE.txt b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/NOTICE.txt new file mode 100644 index 0000000..ce3b94a --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/NOTICE.txt @@ -0,0 +1,6 @@ +Apache Jakarta Commons IO +Copyright 2001-2007 The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). + diff --git a/gradle-plugin-shadow/src/test/resources/jar1/META-INF/maven/commons-io/commons-io/pom.properties b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/maven/commons-io/commons-io/pom.properties new file mode 100644 index 0000000..d8e1adb --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/maven/commons-io/commons-io/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Wed Jun 27 03:51:16 CEST 2007 +version=1.3.2 +groupId=commons-io +artifactId=commons-io diff --git a/gradle-plugin-shadow/src/test/resources/jar1/META-INF/maven/commons-io/commons-io/pom.xml b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/maven/commons-io/commons-io/pom.xml new file mode 100644 index 0000000..9f57f8d --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar1/META-INF/maven/commons-io/commons-io/pom.xml @@ -0,0 +1,353 @@ + + + + + org.apache.commons + commons-parent + 3 + + 4.0.0 + commons-io + commons-io + 1.3.2 + Commons IO + + 2002 + + Commons-IO contains utility classes, stream implementations, file filters, and endian classes. + + + http://jakarta.apache.org/commons/io/ + + + jira + http://issues.apache.org/jira/browse/IO + + + + scm:svn:scm:svn:https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk + scm:svn:scm:svn:https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk + http://svn.apache.org/viewvc/jakarta/commons/proper/io/trunk + + + + + Scott Sanders + sanders + sanders@apache.org + + + Java Developer + + + + dIon Gillard + dion + dion@apache.org + + + Java Developer + + + + Nicola Ken Barozzi + nicolaken + nicolaken@apache.org + + + Java Developer + + + + Henri Yandell + bayard + bayard@apache.org + + + Java Developer + + + + Stephen Colebourne + scolebourne + + + Java Developer + + 0 + + + Jeremias Maerki + jeremias + jeremias@apache.org + + + Java Developer + + +1 + + + Matthew Hawthorne + matth + matth@apache.org + + + Java Developer + + + + Martin Cooper + martinc + martinc@apache.org + + + Java Developer + + + + Rob Oxspring + roxspring + roxspring@apache.org + + + Java Developer + + + + Jochen Wiedmann + jochen + jochen.wiedmann@gmail.com + + + + + + Rahul Akolkar + + + Jason Anderson + + + Nathan Beyer + + + Emmanuel Bourg + + + Chris Eldredge + + + Magnus Grimsell + + + Jim Harrington + + + Thomas Ledoux + + + Andy Lehane + + + Marcelo Liberato + + + Alban Peignier + alban.peignier at free.fr + + + Niall Pemberton + + + Ian Springer + + + Masato Tezuka + + + James Urie + + + Frank W. Zammetti + + + + + + junit + junit + 3.8.1 + test + + + + + src/java + src/test + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Test* + + + **/*AbstractTestCase* + **/AllIOTestSuite* + **/PackageTestSuite* + **/testtools/** + + + **/*$* + + + + + maven-assembly-plugin + + + src/main/assembly/bin.xml + src/main/assembly/src.xml + + gnu + + + + + + + + + org.apache.maven.plugins + maven-changes-plugin + + %URL%/../%ISSUE% + + + + + changes-report + jira-report + + + + + + + + + + release + + + + maven-site-plugin + + + + site + + package + + + + + maven-antrun-plugin + + + + run + + package + + + + + + + + + + + + + + + + + + + + + + + + + + + + maven-assembly-plugin + + + + attached + + package + + + + + + + + rc + + + + maven-site-plugin + + + + site + + package + + + + + maven-assembly-plugin + + + + attached + + package + + + + + + + + diff --git a/gradle-plugin-shadow/src/test/resources/jar2.jar b/gradle-plugin-shadow/src/test/resources/jar2.jar new file mode 100644 index 0000000000000000000000000000000000000000..78d832c11c42023d4bc12077a1d9b7b5025217bc GIT binary patch literal 143847 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%0DVX+mo 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 M2*@cQ6a?h|0Hb=UtpET3 literal 0 HcmV?d00001 diff --git a/gradle-plugin-shadow/src/test/resources/jar2/META-INF/LICENSE.txt b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/gradle-plugin-shadow/src/test/resources/jar2/META-INF/MANIFEST.MF b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/MANIFEST.MF new file mode 100644 index 0000000..7dea9af --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/MANIFEST.MF @@ -0,0 +1,44 @@ +Manifest-Version: 1.0 +Archiver-Version: Plexus Archiver +Created-By: 1.5.0_16 (Apple Computer, Inc.) +Built-By: Cy +Build-Jdk: 1.5.0_16 +Implementation-Title: Commons Compress +Implementation-Vendor: The Apache Software Foundation +Implementation-Vendor-Id: org.apache +Implementation-Version: 1.0 +Specification-Title: Commons Compress +Specification-Vendor: The Apache Software Foundation +Specification-Version: 1.0 +X-Compile-Source-JDK: 1.4 +X-Compile-Target-JDK: 1.4 +Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt +Import-Package: org.apache.commons.compress.archivers;version="1.0",or + g.apache.commons.compress.archivers.ar;version="1.0",org.apache.commo + ns.compress.archivers.cpio;version="1.0",org.apache.commons.compress. + archivers.jar;version="1.0",org.apache.commons.compress.archivers.tar + ;version="1.0",org.apache.commons.compress.archivers.zip;version="1.0 + ",org.apache.commons.compress.changes;version="1.0",org.apache.common + s.compress.compressors;version="1.0",org.apache.commons.compress.comp + ressors.bzip2;version="1.0",org.apache.commons.compress.compressors.g + zip;version="1.0",org.apache.commons.compress.utils;version="1.0" +Bnd-LastModified: 1242279163045 +Export-Package: org.apache.commons.compress.archivers;version="1.0",or + g.apache.commons.compress.changes;version="1.0",org.apache.commons.co + mpress.utils;version="1.0",org.apache.commons.compress.archivers.ar;v + ersion="1.0",org.apache.commons.compress.compressors.gzip;version="1. + 0",org.apache.commons.compress.archivers.jar;version="1.0",org.apache + .commons.compress.archivers.tar;version="1.0",org.apache.commons.comp + ress.archivers.zip;version="1.0",org.apache.commons.compress.compress + ors;version="1.0",org.apache.commons.compress.archivers.cpio;version= + "1.0",org.apache.commons.compress.compressors.bzip2;version="1.0" +Bundle-Version: 1.0 +Bundle-Description: Commons Compress is a component that contains Ar, + Cpio, Jar, Tar, Zip and BZip2 packages +Bundle-Name: Commons Compress +Bundle-DocURL: http://commons.apache.org/compress/ +Bundle-ManifestVersion: 2 +Bundle-Vendor: The Apache Software Foundation +Bundle-SymbolicName: org.apache.commons.compress +Tool: Bnd-0.0.238 + diff --git a/gradle-plugin-shadow/src/test/resources/jar2/META-INF/NOTICE.txt b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/NOTICE.txt new file mode 100644 index 0000000..4237bf9 --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/NOTICE.txt @@ -0,0 +1,15 @@ +Apache Commons Compress +Copyright 2002-2009 The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). + +Original BZip2 classes contributed by Keiron Liddle +, Aftex Software to the Apache Ant project + +Original Tar classes from contributors of the Apache Ant project + +Original Zip classes from contributors of the Apache Ant project + +Original CPIO classes contributed by Markus Kuss and the jRPM project +(jrpm.sourceforge.net) diff --git a/gradle-plugin-shadow/src/test/resources/jar2/META-INF/maven/org.apache.commons/commons-compress/pom.properties b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/maven/org.apache.commons/commons-compress/pom.properties new file mode 100644 index 0000000..598ba41 --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/maven/org.apache.commons/commons-compress/pom.properties @@ -0,0 +1,5 @@ +#Generated by Maven +#Thu May 14 07:32:46 CEST 2009 +version=1.0 +groupId=org.apache.commons +artifactId=commons-compress diff --git a/gradle-plugin-shadow/src/test/resources/jar2/META-INF/maven/org.apache.commons/commons-compress/pom.xml b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/maven/org.apache.commons/commons-compress/pom.xml new file mode 100644 index 0000000..88c8e68 --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar2/META-INF/maven/org.apache.commons/commons-compress/pom.xml @@ -0,0 +1,229 @@ + + + + 4.0.0 + + org.apache.commons + commons-parent + 11 + + + commons-compress + 1.0 + Commons Compress + http://commons.apache.org/compress/ + Commons Compress is a component that contains Ar, Cpio, Jar, Tar, Zip and BZip2 packages + + + jira + http://issues.apache.org/jira/browse/COMPRESS + + + + + junit + junit + 3.8.2 + test + + + + + + Torsten Curdt + tcurdt + tcurdt at apache.org + + + Stefan Bodewig + bodewig + bodewig at apache.org + + + Sebastian Bazley + sebb + sebb at apache.org + + + Christian Grobmeier + grobmeier at apache.org + + + + + + Wolfgang Glas + wolfgang.glas at ev-i.at + + + Christian Kohlschütte + ck@newsclub.de + + + + + scm:svn:http://svn.apache.org/repos/asf/commons/proper/compress/tags/commons-compress-1.0 + scm:svn:https://svn.apache.org/repos/asf/commons/proper/compress/tags/commons-compress-1.0 + http://svn.apache.org/repos/asf/commons/proper/compress/tags/commons-compress-1.0 + + + + + website + Apache Website + scp://people.apache.org/www/commons.apache.org/compress/ + + + + + UTF-8 + UTF-8 + 1.4 + 1.4 + compress + COMPRESS + 12310904 + + 1.0 + RC2 + + + + + + + + org.apache.maven.plugins + maven-source-plugin + 2.1 + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.5 + + + + + + + maven-assembly-plugin + + + src/assembly/bin.xml + src/assembly/src.xml + + gnu + + + + org.apache.maven.plugins + maven-source-plugin + + + + true + true + + + + + + + + + + + + org.apache.maven.plugins + maven-changes-plugin + 2.0 + + %URL%/%ISSUE% + + + + + changes-report + + + + + + + org.codehaus.mojo + cobertura-maven-plugin + 2.2 + + + + org.apache.maven.plugins + maven-pmd-plugin + 2.4 + + 200 + ${maven.compile.source} + + /rulesets/basic.xml + /rulesets/finalizers.xml + /rulesets/imports.xml + /rulesets/unusedcode.xml + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + true + true + + + false + ${maven.compile.source} + ${commons.encoding} + ${commons.docEncoding} + true + + http://java.sun.com/javase/6/docs/api/ + + + + + + + + + rc + + + + apache.website + Apache Commons Release Candidate Staging Site + ${commons.deployment.protocol}://people.apache.org/www/people.apache.org/builds/commons/${commons.componentid}/${commons.release.version}/${commons.rc.version}/site + + + + + + diff --git a/gradle-plugin-shadow/src/test/resources/jar3using1.jar b/gradle-plugin-shadow/src/test/resources/jar3using1.jar new file mode 100644 index 0000000000000000000000000000000000000000..c7bfc0635f62793abd4fdea87351dc8145c2fcc3 GIT binary patch literal 1189 zcmWIWW@Zs#-~hr;ysWkiNPv@pg~8V~#8KDN&rSc|DFy~+h5&DN4v-2asImaK=Fe!F zMSv>cntdJpJl$M_L-c&zKKq_I?c=ShcahgySL@uF^P7VVt{6XfTBPUYtK;c=v2+y+ z=Zu$HVkUFU9|lp?*r?F14K5XJ+p^S8beGU$=iB zV+$DMCecx1NRAva@DVEJFb}Xp+TkZ7Is4l4GScLm4HSXtHd#8RDHGUfKs^h{- z+rH_aT)U?Bh$W>3$?CR+y@=n-re$c=cTMiw51a1ICIQ!Pb*&Z_<6pC`k1K}%cWFt% zfpwGbY>kZYd+seN;k0{!*}0ayIeINVDIeCj2(&Af3tNdTemVEzYSZ7Wg`eCczWi(7 z5*|27a$fMsf*p#>XKLQ~$KhV^Bk_$^hAEJN9r7Uljka1sGsAwUHN>jJt#pt@42=vH@O5w+&Is7*<|(7<$GGs`+<>hs zI`&so-2bNh+LGhW26xvrW#`l_Ju)tYvHsm;b&~1a6zA0!7Qd5PdNZo5a8Iy&tIrwJ z`Fd?88O?@=LEig)Rmyt>(D4dj?=dooFra2P zSY85UH&g)6KcLJP;Em7<%52CP0hHMg0F;U0T9L9IvI(FJgPgX(xf*W7ZXgSmeUNp5 z(mir)fzmw!XaPNl$OXvyKyis244}9~fIeUdfc1f5Ho%*e4J5@1gi1gIbD2Rr0D2x| A*Z=?k literal 0 HcmV?d00001 diff --git a/gradle-plugin-shadow/src/test/resources/jar3using1/META-INF/MANIFEST.MF b/gradle-plugin-shadow/src/test/resources/jar3using1/META-INF/MANIFEST.MF new file mode 100644 index 0000000..fbdfcfb --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar3using1/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: 1.6.0_20 (Apple Inc.) + diff --git a/gradle-plugin-shadow/src/test/resources/jar3using1/Main.java b/gradle-plugin-shadow/src/test/resources/jar3using1/Main.java new file mode 100644 index 0000000..dc27b89 --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar3using1/Main.java @@ -0,0 +1,13 @@ +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.commons.io.IOUtils; + +public class Main { + + public static void main(String[] argv) throws Exception { + InputStream is = null; + OutputStream os = null; + IOUtils.copy(is, os); + } +} \ No newline at end of file diff --git a/gradle-plugin-shadow/src/test/resources/jar3using1/build.sh b/gradle-plugin-shadow/src/test/resources/jar3using1/build.sh new file mode 100644 index 0000000..6777c0e --- /dev/null +++ b/gradle-plugin-shadow/src/test/resources/jar3using1/build.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +javac -cp ../jar1.jar Main.java +jar cvf ../jar3using1.jar . \ No newline at end of file diff --git a/gradle-plugin-shadow/src/test/resources/junit-3.8.2.jar b/gradle-plugin-shadow/src/test/resources/junit-3.8.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..c8f711d050eff209321f799d85ebb3bbe305d481 GIT binary patch literal 120640 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 A>i_@% literal 0 HcmV?d00001 diff --git a/gradle-plugin-shadow/src/test/resources/test-artifact-1.0-SNAPSHOT.jar b/gradle-plugin-shadow/src/test/resources/test-artifact-1.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..009abad49de5cbf4857e8c429cb1f81c75c2dd56 GIT binary patch literal 3115 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 ZiPmb76=Ewli&KF45oX>c2|m`p{{iiGhC2WN literal 0 HcmV?d00001 diff --git a/gradle.properties b/gradle.properties index bcf7e2b..6b0770b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,13 +3,3 @@ name = 'gradle-plugins' version = 0.0.1 org.gradle.warning.mode = ALL -gradle.wrapper.version = 7.3.2 -groovy.version = 3.0.9 -groovy-git.version = 2.1.0 -rpm.version = 2.1.0 -spock.version = 2.0-groovy-3.0 -junit.version = 5.8.2 -junit4.version = 4.13.2 -asciidoctorj.version = 2.5.2 -jruby.version = 9.3.2.0 -jsoup.version = 1.11.2 diff --git a/gradle/compile/groovy.gradle b/gradle/compile/groovy.gradle index b48d5cc..7e52480 100644 --- a/gradle/compile/groovy.gradle +++ b/gradle/compile/groovy.gradle @@ -1,7 +1,7 @@ apply plugin: 'groovy' dependencies { - implementation "org.codehaus.groovy:groovy:${project.property('groovy.version')}" + implementation libs.groovy.core } compileGroovy { @@ -17,7 +17,7 @@ compileTestGroovy { tasks.withType(GroovyCompile) { options.compilerArgs if (!options.compilerArgs.contains("-processor")) { - options.compilerArgs << '-proc:none' + options.compilerArgs.add('-proc:none') } } @@ -27,7 +27,3 @@ task groovydocJar(type: Jar) { archiveClassifier.set('groovydoc') } tasks.assemble.dependsOn(tasks.groovydocJar) - -artifacts { - archives groovydocJar -} diff --git a/gradle/compile/java.gradle b/gradle/compile/java.gradle index 7c834af..019f777 100644 --- a/gradle/compile/java.gradle +++ b/gradle/compile/java.gradle @@ -21,24 +21,8 @@ jar { } } -task sourcesJar(type: Jar) { - dependsOn classes - classifier 'sources' - from sourceSets.main.allSource -} - -task javadocJar(type: Jar) { - dependsOn javadoc - classifier 'javadoc' - from javadoc.destinationDir -} - -artifacts { - archives sourcesJar, javadocJar -} - tasks.withType(JavaCompile) { - options.compilerArgs << '-Xlint:all,-fallthrough' + options.compilerArgs.add('-Xlint:all,-fallthrough') } javadoc { diff --git a/gradle/documentation/asciidoc.gradle b/gradle/documentation/asciidoc.gradle index 87ba22e..5743f39 100644 --- a/gradle/documentation/asciidoc.gradle +++ b/gradle/documentation/asciidoc.gradle @@ -8,7 +8,6 @@ dependencies { asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}" } - asciidoctor { backends 'html5' outputDir = file("${rootProject.projectDir}/docs") diff --git a/gradle/ide/idea.gradle b/gradle/ide/idea.gradle index 64e2167..5bd2095 100644 --- a/gradle/ide/idea.gradle +++ b/gradle/ide/idea.gradle @@ -6,8 +6,3 @@ idea { testOutputDir file('build/classes/java/test') } } - -if (project.convention.findPlugin(JavaPluginConvention)) { - //sourceSets.main.output.classesDirs = file("build/classes/java/main") - //sourceSets.test.output.classesDirs = file("build/classes/java/test") -} diff --git a/gradle/test/junit5.gradle b/gradle/test/junit5.gradle index 4191db1..88c0791 100644 --- a/gradle/test/junit5.gradle +++ b/gradle/test/junit5.gradle @@ -1,12 +1,9 @@ -def junitVersion = project.hasProperty('junit.version')?project.property('junit.version'):'5.7.2' -def hamcrestVersion = project.hasProperty('hamcrest.version')?project.property('hamcrest.version'):'2.2' - dependencies { - testImplementation "org.junit.jupiter:junit-jupiter-api:${junitVersion}" - testImplementation "org.junit.jupiter:junit-jupiter-params:${junitVersion}" - testImplementation "org.hamcrest:hamcrest-library:${hamcrestVersion}" - testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" + testImplementation libs.junit.jupiter.api + testImplementation libs.junit.jupiter.params + testImplementation libs.hamcrest + testRuntimeOnly libs.junit.jupiter.engine } test { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac0b842..b1159fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 210eeaa..25b2822 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,39 @@ + +dependencyResolutionManagement { + versionCatalogs { + libs { + version('gradle', '7.4') + version('groovy', '3.0.9') + version('spock', '2.0-groovy-3.0') + version('asm', '9.2') + version('junit', '5.8.2') + library('groovy-core', 'org.codehaus.groovy', 'groovy').versionRef('groovy') + library('groovy-git', 'org.xbib.groovy:groovy-git:2.1.0') + library('rpm', 'org.xbib:rpm-core:2.1.0') + library('spock-core', 'org.spockframework', 'spock-core').versionRef('spock') + library('spock-junit4', 'org.spockframework', 'spock-junit4').versionRef('spock') + library('asm', 'org.ow2.asm', 'asm').versionRef('asm') + library('asm-commons', 'org.ow2.asm', 'asm-commons').versionRef('asm') + library('asm-util', 'org.ow2.asm', 'asm-util').versionRef('asm') + library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit') + library('junit-jupiter-params', 'org.junit.jupiter', 'junit-jupiter-params').versionRef('junit') + library('junit-jupiter-engine', 'org.junit.jupiter', 'junit-jupiter-engine').versionRef('junit') + library('hamcrest', 'org.hamcrest:hamcrest-library:2.2') + library('junit4', 'junit:junit:4.13.2') + library('asciidoctorj', 'org.asciidoctor:asciidoctorj:2.5.2') + library('jruby', 'org.jruby:jruby:9.3.2.0') + library('jsoup', 'org.jsoup:jsoup:1.11.2') + library('jflex', 'de.jflex:jflex:1.8.2') + library('jacc', 'org.xbib:jacc:2.2.0') + plugin('publish', 'com.gradle.plugin-publish').version('0.18.0') + } + } +} + include 'gradle-plugin-git' include 'gradle-plugin-rpm' include 'gradle-plugin-docker' include 'gradle-plugin-asciidoctor' +include 'gradle-plugin-jacc' +include 'gradle-plugin-jflex' +include 'gradle-plugin-shadow'

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