diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ShadowedSourcesJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ShadowedSourcesJar.kt index be056d953..5d0b69b9d 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ShadowedSourcesJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ShadowedSourcesJar.kt @@ -20,7 +20,6 @@ internal fun generateSourcesJar( dependencies: Iterable, relocators: Iterable, unusedClasses: Set, - preserveFileTimestamps: Boolean, ) = try { zipOutStream.use { zos -> @@ -36,11 +35,7 @@ internal fun generateSourcesJar( val manifestEntry = "META-INF/MANIFEST.MF" visitedFiles.add(manifestEntry) - zos.writeEntry( - name = manifestEntry, - preserveLastModified = preserveFileTimestamps, - unixMode = UnixMode.file(), - ) { + zos.writeEntry(name = manifestEntry, unixMode = UnixMode.file()) { write("Manifest-Version: 1.0\n\n".toByteArray()) } @@ -54,19 +49,12 @@ internal fun generateSourcesJar( runBlocking { val parallelWriter = OrderedParallelWriter(this) - fun writeToZip(entryName: String, lastModified: Long, bytes: ByteArray) { - zos.writeEntry( - name = entryName, - preserveLastModified = preserveFileTimestamps, - lastModified = lastModified, - unixMode = UnixMode.file(), - ) { - write(bytes) - } + fun writeToZip(entryName: String, bytes: ByteArray) { + zos.writeEntry(name = entryName, unixMode = UnixMode.file()) { write(bytes) } } // Relocates and writes a project or dependency source file, or a resource. - fun addEntry(name: String, lastModified: Long, readBytes: () -> ByteArray) { + fun addEntry(name: String, readBytes: () -> ByteArray) { if (name.isModuleInfo) return val isSource = name.isSourceFile() if (isSource && isUnused(name, unusedClasses, sourceToClasses)) return @@ -76,7 +64,7 @@ internal fun generateSourcesJar( if (!isSource) { // Resources gain nothing from parallelism, write them directly to avoid buffering. parallelWriter.flush() - writeToZip(relocatedPath, lastModified, readBytes()) + writeToZip(relocatedPath, readBytes()) return } val rawBytes = readBytes() @@ -86,14 +74,14 @@ internal fun generateSourcesJar( .remapSourceFile(rawBytes.decodeToString(), name, relocatedPath) .toByteArray() }, - write = { writeToZip(relocatedPath, lastModified, it) }, + write = { writeToZip(relocatedPath, it) }, ) } filesWithRelPaths .sortedBy { it.second } .forEach { (file, relPath) -> - addEntry(relPath, file.lastModified()) { file.readBytes() } + addEntry(relPath) { file.readBytes() } } sourcesJars.forEach { jarFile -> @@ -111,7 +99,7 @@ internal fun generateSourcesJar( } .sortedBy { it.name } .forEach { entry -> - addEntry(entry.name, entry.time) { getInputStream(entry).use { it.readBytes() } } + addEntry(entry.name) { getInputStream(entry).use { it.readBytes() } } } } } @@ -119,18 +107,7 @@ internal fun generateSourcesJar( parallelWriter.flush() } - val entries = zos.entries.map { it.name } - val added = entries.toMutableSet() - entries.forEach { name -> - name.parentDirectoryEntries().forEach { entryName -> - if (!added.add(entryName)) return@forEach - zos.writeEntry( - name = entryName, - preserveLastModified = preserveFileTimestamps, - unixMode = UnixMode.directory(), - ) - } - } + zos.writeParentDirectoryEntries() } } catch (e: Exception) { sourcesJarFile.delete() diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt index a9f483e17..ba2b8f6a2 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/Zip.kt @@ -8,6 +8,7 @@ import org.apache.tools.zip.UnixStat import org.apache.tools.zip.Zip64Mode import org.apache.tools.zip.ZipEntry import org.apache.tools.zip.ZipOutputStream +import org.gradle.api.file.FileCopyDetails import org.gradle.api.tasks.bundling.ZipEntryCompression @JvmInline @@ -103,6 +104,38 @@ internal inline fun ZipOutputStream.writeEntry( } } +/** + * Writes the missing parent directory entries of all written entries, taking the timestamp and + * permissions from [visitedDirs] when available. + */ +internal fun ZipOutputStream.writeParentDirectoryEntries( + preserveLastModified: Boolean = false, + visitedDirs: Map = emptyMap(), +) { + val entries = entries.map { it.name } + val added = entries.toMutableSet() + val currentTimeMillis = System.currentTimeMillis() + + entries.forEach { name -> + name.parentDirectoryEntries().asReversed().forEach { entryName -> + if (!added.add(entryName)) return@forEach + val details = visitedDirs[entryName.removeSuffix("/")] + val (lastModified, unixMode) = + if (details == null) { + currentTimeMillis to UnixMode.directory() + } else { + details.lastModified to UnixMode.directory(details.permissions.toUnixNumeric()) + } + writeEntry( + name = entryName, + preserveLastModified = preserveLastModified, + lastModified = lastModified, + unixMode = unixMode, + ) + } + } +} + internal fun String.parentDirectoryEntries(): List { val parents = mutableListOf() var parent = substringBeforeLast('/', "") diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt index e9bb57e72..360cf2497 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt @@ -9,10 +9,10 @@ import com.github.jengelman.gradle.plugins.shadow.internal.UnixMode import com.github.jengelman.gradle.plugins.shadow.internal.entries import com.github.jengelman.gradle.plugins.shadow.internal.gradleError import com.github.jengelman.gradle.plugins.shadow.internal.inputStream -import com.github.jengelman.gradle.plugins.shadow.internal.parentDirectoryEntries import com.github.jengelman.gradle.plugins.shadow.internal.readBytes import com.github.jengelman.gradle.plugins.shadow.internal.remapClass import com.github.jengelman.gradle.plugins.shadow.internal.writeEntry +import com.github.jengelman.gradle.plugins.shadow.internal.writeParentDirectoryEntries import com.github.jengelman.gradle.plugins.shadow.relocation.Relocator import com.github.jengelman.gradle.plugins.shadow.relocation.relocatePath import com.github.jengelman.gradle.plugins.shadow.transformers.ResourceTransformer @@ -83,7 +83,7 @@ internal constructor( } processTransformers(zos) - addDirs(zos) + zos.writeParentDirectoryEntries(isPreserveFileTimestamps, visitedDirs) checkDuplicateEntries(zos) } } catch (e: Exception) { @@ -118,31 +118,6 @@ internal constructor( } } - private fun addDirs(zos: ZipOutputStream) { - val entries = zos.entries.map { it.name } - val added = entries.toMutableSet() - val currentTimeMillis = System.currentTimeMillis() - - entries.forEach { name -> - name.parentDirectoryEntries().asReversed().forEach { entryName -> - if (!added.add(entryName)) return@forEach - val details = visitedDirs[entryName.removeSuffix("/")] - val (lastModified, unixMode) = - if (details == null) { - currentTimeMillis to UnixMode.directory() - } else { - details.lastModified to UnixMode.directory(details.permissions.toUnixNumeric()) - } - zos.writeEntry( - name = entryName, - preserveLastModified = isPreserveFileTimestamps, - lastModified = lastModified, - unixMode = unixMode, - ) - } - } - } - private fun checkDuplicateEntries(zos: ZipOutputStream) { val entries = zos.entries.map { it.name } val duplicates = entries.groupingBy { it }.eachCount().filter { it.value > 1 } diff --git a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt index 7f36db284..506766240 100644 --- a/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt +++ b/src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowJar.kt @@ -210,6 +210,9 @@ public abstract class ShadowJar : Jar() { * `java.withSourcesJar()` is enabled, and `false` otherwise. In projects applying the `shadow` * plugin for Kotlin Multiplatform, this convention defaults to `true` when sources publishing is * enabled on the JVM target, and `false` otherwise. + * + * Entries in the shadowed sources JAR always use [CONSTANT_TIME_FOR_ZIP_ENTRIES] as their + * timestamp, regardless of [isPreserveFileTimestamps]. */ @get:Input @get:Option( @@ -846,7 +849,6 @@ public abstract class ShadowJar : Jar() { dependencies = includedDependencies.files, relocators = relocators.get() + packageRelocators, unusedClasses = unusedClasses, - preserveFileTimestamps = isPreserveFileTimestamps, ) } diff --git a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ShadowedSourcesJarTest.kt b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ShadowedSourcesJarTest.kt index b1084526e..1521f3fcf 100644 --- a/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ShadowedSourcesJarTest.kt +++ b/src/test/kotlin/com/github/jengelman/gradle/plugins/shadow/internal/ShadowedSourcesJarTest.kt @@ -120,8 +120,8 @@ class ShadowedSourcesJarTest { "META-INF/", "a/", "m/", - "z/", "z/sub/", + "z/", ) } } @@ -269,10 +269,10 @@ class ShadowedSourcesJarTest { "shaded/dep/Dep.java", "shaded/dep/resource.txt", "META-INF/", - "com/", "com/example/", - "shaded/", + "com/", "shaded/dep/", + "shaded/", ) getContent("shaded/dep/Dep.java").isEqualTo("package shaded.dep;\npublic class Dep {}") getContent("shaded/dep/resource.txt").isEqualTo("dep resource") @@ -306,8 +306,8 @@ class ShadowedSourcesJarTest { "META-INF/MANIFEST.MF", *expectedEntries.toTypedArray(), "META-INF/", - "shaded/", "shaded/example/", + "shaded/", ) names.forEach { name -> getContent("shaded/example/$name.java") @@ -339,5 +339,4 @@ private fun generateSourcesJar( dependencies = emptyList(), relocators = relocators, unusedClasses = emptySet(), - preserveFileTimestamps = true, )