Skip to content

Commit ae80656

Browse files
authored
kn: implement SystemDefaultProvider for Native (#1231)
1 parent 7c3c73f commit ae80656

File tree

8 files changed

+200
-32
lines changed

8 files changed

+200
-32
lines changed

gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ kotlin.code.style=official
22
kotlin.incremental.js=true
33
kotlin.incremental.multiplatform=true
44
kotlin.mpp.stability.nowarn=true
5+
kotlin.mpp.enableCInteropCommonization=true
56
kotlin.native.ignoreDisabledTargets=true
67

78
# atomicfu

runtime/runtime-core/api/runtime-core.api

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,9 +2383,12 @@ public final class aws/smithy/kotlin/runtime/util/OperatingSystem {
23832383
public final class aws/smithy/kotlin/runtime/util/OsFamily : java/lang/Enum {
23842384
public static final field Android Laws/smithy/kotlin/runtime/util/OsFamily;
23852385
public static final field Ios Laws/smithy/kotlin/runtime/util/OsFamily;
2386+
public static final field IpadOs Laws/smithy/kotlin/runtime/util/OsFamily;
23862387
public static final field Linux Laws/smithy/kotlin/runtime/util/OsFamily;
23872388
public static final field MacOs Laws/smithy/kotlin/runtime/util/OsFamily;
2389+
public static final field TvOs Laws/smithy/kotlin/runtime/util/OsFamily;
23882390
public static final field Unknown Laws/smithy/kotlin/runtime/util/OsFamily;
2391+
public static final field WatchOs Laws/smithy/kotlin/runtime/util/OsFamily;
23892392
public static final field Windows Laws/smithy/kotlin/runtime/util/OsFamily;
23902393
public static fun getEntries ()Lkotlin/enums/EnumEntries;
23912394
public fun toString ()Ljava/lang/String;

runtime/runtime-core/build.gradle.kts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6+
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
7+
68
plugins {
79
alias(libs.plugins.kotlinx.serialization)
810
}
@@ -54,4 +56,15 @@ kotlin {
5456
languageSettings.optIn("aws.smithy.kotlin.runtime.InternalApi")
5557
}
5658
}
59+
60+
targets.withType<KotlinNativeTarget> {
61+
compilations["main"].cinterops {
62+
val interopDir = "$projectDir/native/src/nativeInterop/cinterop"
63+
create("environ") {
64+
includeDirs(interopDir)
65+
packageName("aws.smithy.platform.posix")
66+
headers(listOf("$interopDir/environ.h"))
67+
}
68+
}
69+
}
5770
}

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/util/Platform.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ public enum class OsFamily {
5858
Windows,
5959
Android,
6060
Ios,
61+
IpadOs,
62+
TvOs,
63+
WatchOs,
6164
Unknown,
6265
;
6366

@@ -67,6 +70,9 @@ public enum class OsFamily {
6770
Windows -> "windows"
6871
Android -> "android"
6972
Ios -> "ios"
73+
IpadOs -> "ipados"
74+
TvOs -> "tvos"
75+
WatchOs -> "watchos"
7076
Unknown -> "unknown"
7177
}
7278
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.util
6+
7+
import kotlinx.coroutines.test.runTest
8+
import kotlin.test.Test
9+
import kotlin.test.assertContentEquals
10+
import kotlin.test.assertNotEquals
11+
import kotlin.test.assertNotNull
12+
import kotlin.test.assertNull
13+
import kotlin.test.assertTrue
14+
15+
class SystemPlatformProviderTest {
16+
@Test
17+
fun testReadWriteFile() = runTest {
18+
val ps = PlatformProvider.System
19+
20+
val tempDir = if (ps.osInfo().family == OsFamily.Windows) {
21+
requireNotNull(ps.getenv("TEMP")) { "%TEMP% unexpectedly null" }
22+
} else {
23+
"/tmp"
24+
}
25+
val path = "$tempDir/testReadWriteFile-${Uuid.random()}.txt"
26+
27+
val expected = "Hello, File!".encodeToByteArray()
28+
29+
ps.writeFile(path, expected)
30+
assertTrue(ps.fileExists(path))
31+
32+
val actual = ps.readFileOrNull(path)
33+
assertContentEquals(expected, actual)
34+
}
35+
36+
@Test
37+
fun testGetEnv() = runTest {
38+
val envVarKeys = listOf("PATH", "USERPROFILE") // PATH is not set on Windows CI
39+
assertNotNull(
40+
envVarKeys.firstNotNullOfOrNull { PlatformProvider.System.getenv(it) },
41+
)
42+
43+
assertNull(PlatformProvider.System.getenv("THIS_ENV_VAR_IS_NOT_SET"))
44+
}
45+
46+
@Test
47+
fun testGetAllEnvVars() = runTest {
48+
val allEnv = PlatformProvider.System.getAllEnvVars()
49+
assertTrue(allEnv.isNotEmpty())
50+
51+
val envVarKeys = listOf("PATH", "USERPROFILE") // PATH is not set on Windows CI
52+
assertTrue(
53+
envVarKeys.any { allEnv.contains(it) },
54+
)
55+
}
56+
57+
@Test
58+
fun testOsInfo() = runTest {
59+
val osInfo = PlatformProvider.System.osInfo()
60+
assertNotEquals(OsFamily.Unknown, osInfo.family)
61+
}
62+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.smithy.kotlin.runtime.util
6+
7+
import kotlinx.coroutines.test.runTest
8+
import kotlin.test.Test
9+
import kotlin.test.assertEquals
10+
11+
class SystemPlatformProviderLinuxX64Test {
12+
@Test
13+
fun testOsInfo() = runTest {
14+
val osInfo = PlatformProvider.System.osInfo()
15+
assertEquals(OsFamily.Linux, osInfo.family)
16+
}
17+
}

runtime/runtime-core/native/src/aws/smithy/kotlin/runtime/util/PlatformNative.kt

Lines changed: 86 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,104 @@
44
*/
55
package aws.smithy.kotlin.runtime.util
66

7+
import aws.smithy.kotlin.runtime.io.IOException
8+
import aws.smithy.kotlin.runtime.io.internal.SdkDispatchers
9+
import aws.smithy.platform.posix.get_environ_ptr
10+
import kotlinx.cinterop.*
11+
import kotlinx.coroutines.withContext
12+
import platform.posix.*
13+
714
internal actual object SystemDefaultProvider : PlatformProvider {
8-
actual override fun getAllEnvVars(): Map<String, String> {
9-
TODO("Not yet implemented")
15+
actual override fun getAllEnvVars(): Map<String, String> = memScoped {
16+
val environ = get_environ_ptr()
17+
generateSequence(0) { it + 1 }
18+
.map { idx -> environ?.get(idx)?.toKString() }
19+
.takeWhile { it != null }
20+
.associate { env ->
21+
val parts = env?.split("=", limit = 2)
22+
check(parts?.size == 2) { "Environment entry \"$env\" is malformed" }
23+
parts[0] to parts[1]
24+
}
1025
}
1126

12-
actual override fun getenv(key: String): String? {
13-
TODO("Not yet implemented")
14-
}
27+
actual override fun getenv(key: String): String? = platform.posix.getenv(key)?.toKString()
1528

1629
actual override val filePathSeparator: String
17-
get() = TODO("Not yet implemented")
30+
get() = when (osInfo().family) {
31+
OsFamily.Windows -> "\\"
32+
else -> "/"
33+
}
1834

19-
actual override suspend fun readFileOrNull(path: String): ByteArray? {
20-
TODO("Not yet implemented")
21-
}
35+
actual override suspend fun readFileOrNull(path: String): ByteArray? = withContext(SdkDispatchers.IO) {
36+
try {
37+
val file = fopen(path, "rb") ?: return@withContext null
2238

23-
actual override suspend fun writeFile(path: String, data: ByteArray) {
24-
TODO("Not yet implemented")
25-
}
39+
try {
40+
// Get file size
41+
fseek(file, 0L, SEEK_END)
42+
val size = ftell(file)
43+
fseek(file, 0L, SEEK_SET)
2644

27-
actual override fun fileExists(path: String): Boolean {
28-
TODO("Not yet implemented")
45+
// Read file content
46+
val buffer = ByteArray(size.toInt()).pin()
47+
val rc = fread(buffer.addressOf(0), 1uL, size.toULong(), file)
48+
if (rc == size.toULong()) buffer.get() else null
49+
} finally {
50+
fclose(file)
51+
}
52+
} catch (_: Exception) {
53+
null
54+
}
2955
}
3056

31-
actual override fun osInfo(): OperatingSystem {
32-
TODO("Not yet implemented")
57+
actual override suspend fun writeFile(path: String, data: ByteArray) = withContext(SdkDispatchers.IO) {
58+
val file = fopen(path, "wb") ?: throw IOException("Cannot open file for writing: $path")
59+
try {
60+
val wc = fwrite(data.refTo(0), 1uL, data.size.toULong(), file)
61+
if (wc != data.size.toULong()) {
62+
throw IOException("Failed to write all bytes to file $path, expected ${data.size.toLong()}, wrote $wc")
63+
}
64+
} finally {
65+
fclose(file)
66+
}
3367
}
3468

35-
actual override val isJvm: Boolean
36-
get() = TODO("Not yet implemented")
37-
actual override val isAndroid: Boolean
38-
get() = TODO("Not yet implemented")
39-
actual override val isBrowser: Boolean
40-
get() = TODO("Not yet implemented")
41-
actual override val isNode: Boolean
42-
get() = TODO("Not yet implemented")
43-
actual override val isNative: Boolean
44-
get() = TODO("Not yet implemented")
45-
46-
actual override fun getAllProperties(): Map<String, String> {
47-
TODO("Not yet implemented")
48-
}
69+
actual override fun fileExists(path: String): Boolean = access(path, F_OK) == 0
4970

50-
actual override fun getProperty(key: String): String? {
51-
TODO("Not yet implemented")
71+
actual override fun osInfo(): OperatingSystem = memScoped {
72+
val utsname = alloc<utsname>()
73+
uname(utsname.ptr)
74+
75+
val sysName = utsname.sysname.toKString().lowercase()
76+
val version = utsname.release.toKString()
77+
val machine = utsname.machine.toKString().lowercase() // Helps differentiate Apple platforms
78+
79+
val family = when {
80+
sysName.contains("darwin") -> {
81+
when {
82+
machine.startsWith("iphone") -> OsFamily.Ios
83+
// TODO Validate that iPadOS/tvOS/watchOS resolves correctly on each of these devices
84+
machine.startsWith("ipad") -> OsFamily.IpadOs
85+
machine.startsWith("tv") -> OsFamily.TvOs
86+
machine.startsWith("watch") -> OsFamily.WatchOs
87+
else -> OsFamily.MacOs
88+
}
89+
}
90+
sysName.contains("linux") -> OsFamily.Linux
91+
sysName.contains("windows") -> OsFamily.Windows
92+
else -> OsFamily.Unknown
93+
}
94+
95+
return OperatingSystem(family, version)
5296
}
97+
98+
actual override val isJvm: Boolean = false
99+
actual override val isAndroid: Boolean = false
100+
actual override val isBrowser: Boolean = false
101+
actual override val isNode: Boolean = false
102+
actual override val isNative: Boolean = true
103+
104+
// Kotlin/Native doesn't have system properties
105+
actual override fun getAllProperties(): Map<String, String> = emptyMap()
106+
actual override fun getProperty(key: String): String? = null
53107
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef ENVIRON_H
2+
#define ENVIRON_H
3+
4+
// External declaration to get environment variables
5+
extern char **environ;
6+
7+
// Helper function to get the environ pointer
8+
char** get_environ_ptr() {
9+
return environ;
10+
}
11+
12+
#endif

0 commit comments

Comments
 (0)