Skip to content

Commit 208bad1

Browse files
authored
Fix the last modified time of shadowed directories (#1277)
* Add `preserveLastModifiedCorrectly` * Use parent as the key in addDirs.addParent * Fxi things * Use ParameterizedTest * Update baseline * Inject `enableRelocation` as test param as well * Update relocated dir time * Minus 3 sec instead of 1 min in test * Update changelog * Check them in time range
1 parent 667417b commit 208bad1

File tree

5 files changed

+115
-11
lines changed

5 files changed

+115
-11
lines changed

lint-baseline.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
4242
<location
4343
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt"
44-
line="17"
44+
line="18"
4545
column="1"/>
4646
</issue>
4747

@@ -52,7 +52,7 @@
5252
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
5353
<location
5454
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt"
55-
line="18"
55+
line="19"
5656
column="1"/>
5757
</issue>
5858

@@ -63,7 +63,7 @@
6363
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
6464
<location
6565
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt"
66-
line="19"
66+
line="20"
6767
column="1"/>
6868
</issue>
6969

@@ -74,7 +74,7 @@
7474
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
7575
<location
7676
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt"
77-
line="20"
77+
line="21"
7878
column="1"/>
7979
</issue>
8080

@@ -85,7 +85,7 @@
8585
errorLine2="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
8686
<location
8787
file="src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt"
88-
line="21"
88+
line="22"
8989
column="1"/>
9090
</issue>
9191

src/docs/changes/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
## [Unreleased]
55

6+
**Fixed**
7+
8+
- Fix the last modified time of shadowed directories. ([#1277](https://github.com/GradleUp/shadow/pull/1277))
9+
610

711
## [v9.0.0-beta9] (2025-02-24)
812

src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/BasePluginTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.github.jengelman.gradle.plugins.shadow.util.JarPath
1616
import java.io.Closeable
1717
import java.nio.file.Path
1818
import java.util.Properties
19+
import java.util.jar.JarEntry
1920
import kotlin.io.path.ExperimentalPathApi
2021
import kotlin.io.path.Path
2122
import kotlin.io.path.absolutePathString
@@ -348,11 +349,10 @@ abstract class BasePluginTest {
348349
}
349350

350351
val junitJar: Path = requireResourceAsPath("junit-3.8.2.jar")
351-
val junitEntries: Array<String> = JarPath(junitJar)
352+
val junitRawEntries: List<JarEntry> = JarPath(junitJar)
352353
.use { it.entries().toList() }
353-
.map { entry -> entry.name }
354-
.filterNot { it == "junit3.8.2/" || it.startsWith("META-INF/") }
355-
.toTypedArray()
354+
.filterNot { it.name == "junit3.8.2/" || it.name.startsWith("META-INF/") }
355+
val junitEntries: Array<String> = junitRawEntries.map { it.name }.toTypedArray()
356356

357357
val shadowJar: String = """
358358
tasks.named('$SHADOW_JAR_TASK_NAME', ${ShadowJar::class.java.name})

src/functionalTest/kotlin/com/github/jengelman/gradle/plugins/shadow/RelocationTest.kt

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import assertk.assertions.isInstanceOf
88
import assertk.assertions.isNotEmpty
99
import assertk.fail
1010
import com.github.jengelman.gradle.plugins.shadow.ShadowJavaPlugin.Companion.SHADOW_JAR_TASK_NAME
11+
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowCopyAction.Companion.CONSTANT_TIME_FOR_ZIP_ENTRIES
1112
import com.github.jengelman.gradle.plugins.shadow.util.Issue
1213
import com.github.jengelman.gradle.plugins.shadow.util.containsEntries
1314
import com.github.jengelman.gradle.plugins.shadow.util.doesNotContainEntries
1415
import java.net.URLClassLoader
1516
import kotlin.io.path.appendText
1617
import kotlin.io.path.writeText
18+
import kotlin.time.Duration.Companion.seconds
1719
import org.junit.jupiter.api.Test
1820
import org.junit.jupiter.params.ParameterizedTest
1921
import org.junit.jupiter.params.provider.Arguments
@@ -347,6 +349,89 @@ class RelocationTest : BasePluginTest() {
347349
assertThat(excluded).isEmpty()
348350
}
349351

352+
@ParameterizedTest
353+
@MethodSource("preserveLastModifiedProvider")
354+
fun preserveLastModifiedCorrectly(enableRelocation: Boolean, preserveFileTimestamps: Boolean) {
355+
// Minus 3 sec to avoid the time difference between the file system and the JVM.
356+
val currentTimeMillis = System.currentTimeMillis() - 3.seconds.inWholeMilliseconds
357+
val junitEntryTimeRange = junitRawEntries.map { it.time }.let { it.min()..it.max() }
358+
writeMainClass(withImports = true)
359+
projectScriptPath.appendText(
360+
"""
361+
dependencies {
362+
implementation 'junit:junit:3.8.2'
363+
}
364+
$shadowJar {
365+
enableRelocation = $enableRelocation
366+
preserveFileTimestamps = $preserveFileTimestamps
367+
}
368+
""".trimIndent(),
369+
)
370+
371+
run(shadowJarTask)
372+
373+
if (enableRelocation) {
374+
val (relocatedEntries, otherEntries) = outputShadowJar.use {
375+
it.entries().toList().partition { entry -> entry.name.startsWith("shadow/") }
376+
}
377+
assertThat(relocatedEntries).isNotEmpty()
378+
assertThat(otherEntries).isNotEmpty()
379+
val (relocatedDirs, relocatedClasses) = relocatedEntries.partition { it.isDirectory }
380+
assertThat(relocatedDirs).isNotEmpty()
381+
assertThat(relocatedClasses).isNotEmpty()
382+
383+
if (preserveFileTimestamps) {
384+
relocatedClasses.forEach { entry ->
385+
// Relocated files should preserve the last modified time of the original files.
386+
if (entry.time !in junitEntryTimeRange) {
387+
fail("Relocated file ${entry.name} has an invalid last modified time: ${entry.time}")
388+
}
389+
}
390+
(relocatedDirs + otherEntries).forEach { entry ->
391+
// Relocated directories and other entries are newly created, so they should be in now time.
392+
if (entry.time < currentTimeMillis) {
393+
fail("Relocated directory ${entry.name} has an invalid last modified time: ${entry.time}")
394+
}
395+
}
396+
} else {
397+
(relocatedEntries + otherEntries).forEach { entry ->
398+
// All entries should be newly modified, that default to CONSTANT_TIME_FOR_ZIP_ENTRIES.
399+
if (entry.time != CONSTANT_TIME_FOR_ZIP_ENTRIES) {
400+
fail("Entry ${entry.name} has an invalid last modified time: ${entry.time}")
401+
}
402+
}
403+
}
404+
} else {
405+
val (shadowedEntries, otherEntries) = outputShadowJar.use {
406+
it.entries().toList().partition { entry -> entry.name.startsWith("junit/") }
407+
}
408+
assertThat(shadowedEntries).isNotEmpty()
409+
assertThat(otherEntries).isNotEmpty()
410+
411+
if (preserveFileTimestamps) {
412+
shadowedEntries.forEach { entry ->
413+
// Shadowed entries should preserve the last modified time of the original entries.
414+
if (entry.time !in junitEntryTimeRange) {
415+
fail("Shadowed entry ${entry.name} has an invalid last modified time: ${entry.time}")
416+
}
417+
}
418+
otherEntries.forEach { entry ->
419+
// Other entries are newly created, so they should be in now time.
420+
if (entry.time < currentTimeMillis) {
421+
fail("Entry ${entry.name} has an invalid last modified time: ${entry.time}")
422+
}
423+
}
424+
} else {
425+
(shadowedEntries + otherEntries).forEach { entry ->
426+
// All entries should be newly modified, defaults to CONSTANT_TIME_FOR_ZIP_ENTRIES.
427+
if (entry.time != CONSTANT_TIME_FOR_ZIP_ENTRIES) {
428+
fail("Entry ${entry.name} has an invalid last modified time: ${entry.time}")
429+
}
430+
}
431+
}
432+
}
433+
}
434+
350435
private companion object {
351436
@JvmStatic
352437
fun prefixProvider() = listOf(
@@ -355,5 +440,13 @@ class RelocationTest : BasePluginTest() {
355440
Arguments.of("new.pkg"),
356441
Arguments.of("new/path"),
357442
)
443+
444+
@JvmStatic
445+
fun preserveLastModifiedProvider() = listOf(
446+
Arguments.of(false, false),
447+
Arguments.of(true, false),
448+
Arguments.of(false, true),
449+
Arguments.of(true, true),
450+
)
358451
}
359452
}

src/main/kotlin/com/github/jengelman/gradle/plugins/shadow/tasks/ShadowCopyAction.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,15 @@ public open class ShadowCopyAction(
8787
val entries = zos::class.java.getDeclaredField("entries").apply { isAccessible = true }
8888
.get(zos).cast<List<ZipEntry>>().map { it.name }
8989
val added = entries.toMutableSet()
90+
val currentTimeMillis = System.currentTimeMillis()
9091

9192
fun addParent(name: String) {
9293
val parent = name.substringBeforeLast('/', "")
9394
val entryName = "$parent/"
9495
if (parent.isNotEmpty() && added.add(entryName)) {
95-
val details = visitedDirs[entryName]
96+
val details = visitedDirs[parent]
9697
val (lastModified, flag) = if (details == null) {
97-
CONSTANT_TIME_FOR_ZIP_ENTRIES to UnixStat.DEFAULT_DIR_PERM
98+
currentTimeMillis to UnixStat.DEFAULT_DIR_PERM
9899
} else {
99100
details.lastModified to details.permissions.toUnixNumeric()
100101
}
@@ -226,6 +227,12 @@ public open class ShadowCopyAction(
226227

227228
public companion object {
228229
private val logger = Logging.getLogger(ShadowCopyAction::class.java)
230+
231+
/**
232+
* A copy of [org.gradle.api.internal.file.archive.ZipEntryConstants.CONSTANT_TIME_FOR_ZIP_ENTRIES].
233+
*
234+
* 1980-02-01 00:00:00 (318182400000).
235+
*/
229236
public val CONSTANT_TIME_FOR_ZIP_ENTRIES: Long = GregorianCalendar(1980, 1, 1, 0, 0, 0).timeInMillis
230237
}
231238
}

0 commit comments

Comments
 (0)