Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ internal fun generateSourcesJar(
dependencies: Iterable<File>,
relocators: Iterable<Relocator>,
unusedClasses: Set<String>,
preserveFileTimestamps: Boolean,
) =
try {
zipOutStream.use { zos ->
Expand All @@ -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())
}

Expand All @@ -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
Expand All @@ -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()
Expand All @@ -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 ->
Expand All @@ -111,26 +99,15 @@ 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() } }
}
}
}

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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String, FileCopyDetails> = 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<String> {
val parents = mutableListOf<String>()
var parent = substringBeforeLast('/', "")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -83,7 +83,7 @@ internal constructor(
}

processTransformers(zos)
addDirs(zos)
zos.writeParentDirectoryEntries(isPreserveFileTimestamps, visitedDirs)
checkDuplicateEntries(zos)
}
} catch (e: Exception) {
Expand Down Expand Up @@ -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 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -846,7 +849,6 @@ public abstract class ShadowJar : Jar() {
dependencies = includedDependencies.files,
relocators = relocators.get() + packageRelocators,
unusedClasses = unusedClasses,
preserveFileTimestamps = isPreserveFileTimestamps,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ class ShadowedSourcesJarTest {
"META-INF/",
"a/",
"m/",
"z/",
"z/sub/",
"z/",
)
}
}
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -339,5 +339,4 @@ private fun generateSourcesJar(
dependencies = emptyList(),
relocators = relocators,
unusedClasses = emptySet(),
preserveFileTimestamps = true,
)