Skip to content

Commit 65c0c26

Browse files
authored
Switch to FileMetadata to use statx instead of stat on Linux and Apple platforms (#1772)
* Switch to FileMetadata to use st_birthtimespec on Apple platforms #1755 * Use statx instead of stat on Linux * Fixup calling statx * Missing file * Hardcode __NR_statx for Linux X64 * More platforms * Fallback if statx isn't available Also fallback if STATX_BTIME isn't set
1 parent b11f17b commit 65c0c26

12 files changed

Lines changed: 502 additions & 30 deletions

File tree

gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ android.defaults.buildfeatures.renderscript=false
88
android.defaults.buildfeatures.resvalues=false
99
android.defaults.buildfeatures.shaders=false
1010

11+
kotlin.mpp.applyDefaultHierarchyTemplate=false
1112
kotlin.mpp.commonizerLogLevel=info
13+
kotlin.mpp.enableCInteropCommonization=true
1214
kotlin.mpp.stability.nowarn=true
1315

1416
GROUP=com.squareup.okio

okio-testing-support/build.gradle.kts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool
2+
13
plugins {
24
kotlin("multiplatform")
35
id("app.cash.burst")
@@ -19,6 +21,7 @@ kotlin {
1921
api(projects.okio)
2022
api(libs.kotlin.test)
2123
api(libs.burst.runtime)
24+
api(libs.test.assertk)
2225
}
2326
}
2427

@@ -72,3 +75,34 @@ kotlin {
7275
}
7376
}
7477
}
78+
79+
/*
80+
* AbstractFileSystemTest expects a file with specific metadata:
81+
*
82+
* path: build/AbstractFileSystemTestFiles/metadata.txt
83+
* createdAt: 2026-01-01T01:01:01Z
84+
* lastModifiedAt: 2026-02-02T02:02:02Z
85+
*
86+
* It would be nicer to do this in the test itself with exec(), but we don't yet have a handy
87+
* multiplatform API for that.
88+
*/
89+
val touchAbstractFileSystemTestFilesCreatedAt by tasks.registering(Exec::class) {
90+
val sampleFile = project.file("build/AbstractFileSystemTestFiles/metadata.txt")
91+
doFirst {
92+
sampleFile.parentFile.mkdirs()
93+
}
94+
environment("TZ" to "UTC")
95+
commandLine("touch", "-t", "202601010101.01", sampleFile.path)
96+
}
97+
val touchAbstractFileSystemTestFilesModifiedAt by tasks.registering(Exec::class) {
98+
dependsOn(touchAbstractFileSystemTestFilesCreatedAt)
99+
val sampleFile = project.file("build/AbstractFileSystemTestFiles/metadata.txt")
100+
environment("TZ" to "UTC")
101+
commandLine("touch", "-t", "202602020202.02", sampleFile.path)
102+
}
103+
tasks.withType<KotlinCompileTool> {
104+
dependsOn(
105+
touchAbstractFileSystemTestFilesCreatedAt,
106+
touchAbstractFileSystemTestFilesModifiedAt,
107+
)
108+
}

okio-testing-support/src/commonMain/kotlin/okio/AbstractFileSystemTest.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,27 @@ abstract class AbstractFileSystemTest(
14711471
assertInRange(metadata.lastAccessedAt, minTime, maxTime)
14721472
}
14731473

1474+
/** https://github.com/square/okio/issues/1755 */
1475+
@Test
1476+
fun fileMetadataTimestampsAreDistinct() {
1477+
if (fileSystem.isFakeFileSystem) return
1478+
if (fileSystem is ForwardingFileSystem) return
1479+
if (isJimFileSystem()) return
1480+
if (!fileSystemHasGoodMetadata) return
1481+
1482+
// These timestamps are hardcoded in the following Gradle tasks:
1483+
// :okio-testing-support:touchAbstractFileSystemTestFilesCreatedAt
1484+
// :okio-testing-support:touchAbstractFileSystemTestFilesModifiedAt
1485+
val createdAt = fromIso8601String("2026-01-01T01:01:01Z")
1486+
val lastModifiedAt = fromIso8601String("2026-02-02T02:02:02Z")
1487+
1488+
val path = okioRoot / "okio-testing-support" / "build/AbstractFileSystemTestFiles/metadata.txt"
1489+
val metadata = fileSystem.metadata(path)
1490+
assertTrue(metadata.isRegularFile)
1491+
assertInRange(metadata.createdAt, createdAt, createdAt)
1492+
assertInRange(metadata.lastModifiedAt, lastModifiedAt, lastModifiedAt)
1493+
}
1494+
14741495
@Test
14751496
fun directoryMetadata() {
14761497
val minTime = clock.now()

okio-testing-support/src/commonMain/kotlin/okio/TestingCommon.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ fun randomToken(length: Int) = Random.nextBytes(length).toByteString(0, length).
4040

4141
expect fun isBrowser(): Boolean
4242

43+
/**
44+
* Returns true if the host file system probably exposes metadata like file creation time.
45+
*
46+
* The file system that GitHub actions gives us doesn't do anything when we `touch` a file.
47+
*/
48+
val fileSystemHasGoodMetadata: Boolean
49+
get() = getEnv("GITHUB_WORKSPACE") == null
50+
4351
val FileMetadata.createdAt: Instant?
4452
get() {
4553
val createdAt = createdAtMillis ?: return null
@@ -58,6 +66,9 @@ val FileMetadata.lastAccessedAt: Instant?
5866
return Instant.fromEpochMilliseconds(lastAccessedAt)
5967
}
6068

69+
fun fromIso8601String(iso8601String: String): Instant =
70+
Instant.fromEpochMilliseconds(Instant.parse(iso8601String).toEpochMilliseconds())
71+
6172
expect val FileSystem.isFakeFileSystem: Boolean
6273

6374
expect val FileSystem.allowSymlinks: Boolean

okio/build.gradle.kts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import com.vanniktech.maven.publish.JavadocJar
33
import com.vanniktech.maven.publish.KotlinMultiplatform
44
import com.vanniktech.maven.publish.MavenPublishBaseExtension
55
import kotlinx.validation.ApiValidationExtension
6+
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
67
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests
78
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
89
import org.jetbrains.kotlin.gradle.plugin.mpp.TestExecutable
10+
import org.jetbrains.kotlin.konan.target.Family
911

1012
plugins {
1113
kotlin("multiplatform")
@@ -153,9 +155,9 @@ kotlin {
153155
nativeMain.dependsOn(zlibMain)
154156
nativeMain.dependsOn(systemFileSystemMain)
155157
createSourceSet(
156-
"mingwMain",
157-
parent = nativeMain,
158-
children = mingwTargets,
158+
"mingwMain",
159+
parent = nativeMain,
160+
children = mingwTargets,
159161
).also { mingwMain ->
160162
mingwMain.dependsOn(nonAppleMain)
161163
mingwMain.dependsOn(nonJsMain)
@@ -164,13 +166,23 @@ kotlin {
164166
.also { unixMain ->
165167
unixMain.dependsOn(nonJsMain)
166168
createSourceSet(
167-
"linuxMain",
168-
parent = unixMain,
169-
children = linuxTargets,
169+
"linuxMain",
170+
parent = unixMain,
171+
children = linuxTargets,
170172
).also { linuxMain ->
171173
linuxMain.dependsOn(nonAppleMain)
172174
}
173-
createSourceSet("appleMain", parent = unixMain, children = appleTargets)
175+
createSourceSet(
176+
name = "appleMain",
177+
parent = unixMain,
178+
children = appleTargets,
179+
).also { appleMain ->
180+
createSourceSet(
181+
name = "appleNonMacosX64Main",
182+
parent = appleMain,
183+
children = appleTargets - "macosX64",
184+
)
185+
}
174186
}
175187
}
176188

@@ -197,6 +209,17 @@ kotlin {
197209
}
198210
}
199211

212+
targets.withType<KotlinNativeTarget> {
213+
if (konanTarget.family == Family.LINUX) {
214+
compilations["main"].cinterops.create("linux") {
215+
packageName("okio.internal.linux")
216+
headers(
217+
File(project.projectDir, "src/linuxMain/headers/include/uapi/linux/stat.h"),
218+
File(project.projectDir, "src/linuxMain/headers/okio_statx.h"),
219+
)
220+
}
221+
}
222+
}
200223
targets.withType<KotlinNativeTargetWithTests<*>> {
201224
binaries {
202225
// Configure a separate test where code runs in background

okio/src/appleMain/kotlin/okio/ApplePosixVariant.kt renamed to okio/src/appleNonMacosX64Main/kotlin/okio/NonMacosX64PosixVariant.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ internal actual fun PosixFileSystem.variantMetadataOrNull(path: Path): FileMetad
3636
return@memScoped FileMetadata(
3737
isRegularFile = stat.st_mode.toInt() and S_IFMT == S_IFREG,
3838
isDirectory = stat.st_mode.toInt() and S_IFMT == S_IFDIR,
39-
symlinkTarget = symlinkTarget(stat, path),
39+
symlinkTarget = symlinkTarget(stat.st_mode.toInt(), path),
4040
size = stat.st_size,
41-
createdAtMillis = stat.st_ctimespec.epochMillis,
41+
createdAtMillis = stat.st_birthtimespec.epochMillis,
4242
lastModifiedAtMillis = stat.st_mtimespec.epochMillis,
4343
lastAccessedAtMillis = stat.st_atimespec.epochMillis,
4444
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Linux User API
2+
3+
This directory includes files from the Linux kernel user API, used as symbols to build Okio against.
4+
The license 'GPL-2.0 WITH Linux-syscall-note' permits us to build against these symbols without
5+
being a derived work.
6+
7+
https://github.com/torvalds/linux/blob/master/LICENSES/preferred/GPL-2.0
8+
https://github.com/torvalds/linux/blob/master/LICENSES/exceptions/Linux-syscall-note

0 commit comments

Comments
 (0)