Skip to content

Commit 8f3f9c7

Browse files
authored
kn: tidy up, get SDK usable on Native (#1555)
1 parent 77f1899 commit 8f3f9c7

File tree

10 files changed

+249
-9
lines changed

10 files changed

+249
-9
lines changed

.github/actions/setup-build/action.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,22 @@ runs:
1313
ref: '0.4.2'
1414
sparse-checkout: |
1515
.github
16+
1617
- name: Checkout smithy-kotlin
1718
uses: ./aws-kotlin-repo-tools/.github/actions/checkout-head
1819
with:
1920
# checkout smithy-kotlin as a sibling which will automatically make it an included build
2021
path: 'smithy-kotlin'
2122
repository: 'smithy-lang/smithy-kotlin'
23+
24+
# FIXME Remove this checkout of aws-crt-kotlin once K/N artifacts are published to Maven
25+
- name: Checkout aws-crt-kotlin
26+
uses: ./aws-kotlin-repo-tools/.github/actions/checkout-head
27+
with:
28+
# checkout aws-crt-kotlin as a sibling which will automatically make it an included build (transitively, through smithy-kotlin)
29+
path: 'aws-crt-kotlin'
30+
repository: 'awslabs/aws-crt-kotlin'
31+
2232
- name: Configure JDK
2333
uses: actions/setup-java@v3
2434
with:

.github/workflows/continuous-integration.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,23 @@ jobs:
6262
uses: actions/checkout@v4
6363
with:
6464
path: 'aws-sdk-kotlin'
65+
6566
- name: Setup Build
6667
uses: ./aws-sdk-kotlin/.github/actions/setup-build
68+
69+
# FIXME Re-enable Kotlin/Native builds when artifacts are available
70+
- name: Disable Kotlin/Native builds
71+
shell: bash
72+
run: |
73+
echo -e "\naws.kotlin.native=false" >> ./aws-sdk-kotlin/gradle.properties
74+
echo -e "\naws.kotlin.native=false" >> ./smithy-kotlin/gradle.properties
75+
echo -e "\naws.kotlin.native=false" >> ./aws-crt-kotlin/gradle.properties
76+
6777
- name: Configure Gradle - smithy-kotlin
6878
uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main
6979
with:
7080
working-directory: ./smithy-kotlin
81+
7182
- name: Build smithy-kotlin
7283
working-directory: ./smithy-kotlin
7384
shell: bash
@@ -76,10 +87,12 @@ jobs:
7687
pwd
7788
./gradlew --parallel assemble
7889
./gradlew publishToMavenLocal
90+
7991
- name: Configure Gradle aws-sdk-kotlin
8092
uses: awslabs/aws-kotlin-repo-tools/.github/actions/configure-gradle@main
8193
with:
8294
working-directory: ./aws-sdk-kotlin
95+
8396
- name: Test
8497
working-directory: ./aws-sdk-kotlin
8598
shell: bash
@@ -90,6 +103,7 @@ jobs:
90103
./gradlew apiCheck
91104
./gradlew test jvmTest
92105
./gradlew testAllProtocols
106+
93107
- name: Save Test Reports
94108
if: failure()
95109
uses: actions/upload-artifact@v4

.github/workflows/kat-transform.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ jobs:
5252
run: |
5353
pwd
5454
ls -lsa
55-
kat bump-version # Bump from `vNext-SNAPSHOT` to `vNext`. kat transform only works on non-SNAPSHOT versions
56-
./gradlew build
57-
./gradlew publishAllPublicationsToTestLocalRepository
55+
kat bump-version # Bump from `vNext-SNAPSHOT` to `vNext`. kat transform only works on non-SNAPSHOT versions
56+
57+
./gradlew -Paws.kotlin.native=false build
58+
./gradlew -Paws.kotlin.native=false publishAllPublicationsToTestLocalRepository
5859
5960
- name: Transform
6061
working-directory: ./aws-sdk-kotlin

aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials
99
import aws.sdk.kotlin.runtime.config.AwsSdkSetting
1010
import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric
1111
import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric
12+
import aws.smithy.kotlin.runtime.IgnoreNative
1213
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
1314
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException
1415
import aws.smithy.kotlin.runtime.http.Headers
@@ -143,6 +144,9 @@ class EcsCredentialsProviderTest {
143144
engine.assertRequests()
144145
}
145146

147+
// FIXME iosSimulator fails with a different exception: "Failed to resolve host amazonaws.com: nodename nor servname provided, or not known"
148+
// Need to fix this by applying the same --standalone removal that we do in smithy-kotlin, aws-crt-kotlin. See `configureIosSimulatorTasks` in aws-kotlin-repo-tools.
149+
@IgnoreNative
146150
@Test
147151
fun testNonLocalFullUri() = runTest {
148152
val uri = "http://amazonaws.com/full"
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import aws.sdk.kotlin.runtime.auth.credentials.executeCommand
2+
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException
3+
import aws.smithy.kotlin.runtime.util.OsFamily
4+
import aws.smithy.kotlin.runtime.util.PlatformProvider
5+
import kotlinx.coroutines.TimeoutCancellationException
6+
import kotlinx.coroutines.test.runTest
7+
import kotlin.test.Test
8+
import kotlin.test.assertEquals
9+
import kotlin.test.assertFailsWith
10+
11+
class ExecuteCommandTest {
12+
val provider = PlatformProvider.System
13+
14+
val platformNewline = when (provider.osInfo().family) {
15+
OsFamily.Windows -> "\r\n"
16+
else -> "\n"
17+
}
18+
19+
@Test
20+
fun testExecuteCommand() = runTest {
21+
val command = "echo Hello, World!"
22+
23+
val (exitCode, output) = executeCommand(
24+
command = command,
25+
platformProvider = provider,
26+
maxOutputLengthBytes = 1024L,
27+
timeoutMillis = 1000,
28+
)
29+
30+
assertEquals(0, exitCode)
31+
assertEquals("Hello, World!$platformNewline", output)
32+
}
33+
34+
@Test
35+
fun testExecutionTimedOut() = runTest {
36+
assertFailsWith<TimeoutCancellationException> {
37+
executeCommand(
38+
command = "this won't be executed",
39+
platformProvider = provider,
40+
maxOutputLengthBytes = 1024L,
41+
timeoutMillis = 0,
42+
)
43+
}
44+
}
45+
46+
@Test
47+
fun testTooManyBytes() = runTest {
48+
val command = "echo ${"Hello! ".repeat(500)}"
49+
50+
val ex = assertFailsWith<CredentialsProviderException> {
51+
executeCommand(
52+
command = command,
53+
platformProvider = provider,
54+
maxOutputLengthBytes = 1024L,
55+
timeoutMillis = 1000,
56+
)
57+
}
58+
59+
assertEquals("Process output exceeded limit of 1024 bytes", ex.message)
60+
}
61+
62+
@Test
63+
fun testCommandHasQuotes() = runTest {
64+
val command = when (provider.osInfo().family) {
65+
OsFamily.Windows -> """echo "Hello, in quotes!""""
66+
else -> """echo \"Hello, in quotes!\""""
67+
}
68+
69+
val (exitCode, output) = executeCommand(
70+
command = command,
71+
platformProvider = provider,
72+
maxOutputLengthBytes = 1024L,
73+
timeoutMillis = 1000,
74+
)
75+
76+
assertEquals(0, exitCode)
77+
assertEquals(""""Hello, in quotes!"$platformNewline""", output)
78+
}
79+
80+
@Test
81+
fun testMultipleArgumentCommand() = runTest {
82+
val command = when (provider.osInfo().family) {
83+
OsFamily.Windows -> "powershell -Command \"& {Write-Host 'Arg1'; Write-Host 'Arg2'; Write-Host 'Arg3'}\""
84+
else -> "printf '%s\\n%s\\n%s\\n' 'Arg1' 'Arg2' 'Arg3'"
85+
}
86+
87+
val (exitCode, output) = executeCommand(
88+
command = command,
89+
platformProvider = provider,
90+
maxOutputLengthBytes = 1024L,
91+
timeoutMillis = 1000,
92+
)
93+
94+
assertEquals(0, exitCode)
95+
assertEquals("Arg1\nArg2\nArg3\n", output)
96+
}
97+
98+
@Test
99+
fun testErrorReturnsStderr() = runTest {
100+
val errorCommand = when (provider.osInfo().family) {
101+
OsFamily.Windows -> "echo Error message 1>&2 & exit /b 13"
102+
else -> "echo 'Error message' >&2; exit 13"
103+
}
104+
105+
// Windows command output has an extra space at the end
106+
// Can't wrap it in quotes because Windows just echoes them back
107+
// Can't use `<nul set /p` because that doesn't terminate with CRLF
108+
val expectedOutput = when (provider.osInfo().family) {
109+
OsFamily.Windows -> "Error message "
110+
else -> "Error message"
111+
}
112+
113+
val (exitCode, output) = executeCommand(
114+
command = errorCommand,
115+
platformProvider = provider,
116+
maxOutputLengthBytes = 1024L,
117+
timeoutMillis = 1000,
118+
)
119+
120+
assertEquals(13, exitCode)
121+
assertEquals("$expectedOutput$platformNewline", output)
122+
}
123+
}

aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/region/DefaultRegionProviderChainTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package aws.sdk.kotlin.runtime.region
77

88
import aws.sdk.kotlin.runtime.util.TestInstanceMetadataProvider
9+
import aws.smithy.kotlin.runtime.IgnoreNative
910
import aws.smithy.kotlin.runtime.util.TestPlatformProvider
1011
import kotlinx.coroutines.test.runTest
1112
import kotlinx.serialization.json.*
@@ -21,6 +22,8 @@ class DefaultRegionProviderChainTest {
2122
val targets: List<String> = emptyList(),
2223
)
2324

25+
// FIXME "jvm property is favored" tests need to be made JVM-only. Native does not have system properties, so those tests will always fail.
26+
@IgnoreNative
2427
@Test
2528
fun testSuite() = runTest {
2629
val tests = Json.parseToJsonElement(REGION_PROVIDER_CHAIN_TEST_SUITE).jsonArray

aws-runtime/aws-config/jvm/src/aws/sdk/kotlin/runtime/auth/credentials/executeCommandJVM.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ internal actual suspend fun executeCommand(
5656
val rc = reader.read(buffer)
5757
if (rc == -1) break
5858

59-
output.append(buffer)
59+
output.append(buffer, 0, rc)
6060
if (output.length > maxOutputLengthBytes) {
6161
throw CredentialsProviderException("Process output exceeded limit of $maxOutputLengthBytes bytes")
6262
}

aws-runtime/aws-config/native/src/aws/sdk/kotlin/runtime/auth/credentials/executeCommandNative.kt

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,98 @@
44
*/
55
package aws.sdk.kotlin.runtime.auth.credentials
66

7+
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException
8+
import aws.smithy.kotlin.runtime.io.internal.SdkDispatchers
79
import aws.smithy.kotlin.runtime.time.Clock
10+
import aws.smithy.kotlin.runtime.util.OsFamily
811
import aws.smithy.kotlin.runtime.util.PlatformProvider
12+
import kotlinx.cinterop.*
13+
import kotlinx.coroutines.withContext
14+
import kotlinx.coroutines.withTimeout
15+
import platform.posix.*
916

17+
@OptIn(ExperimentalForeignApi::class)
1018
internal actual suspend fun executeCommand(
1119
command: String,
1220
platformProvider: PlatformProvider,
1321
maxOutputLengthBytes: Long,
1422
timeoutMillis: Long,
1523
clock: Clock,
16-
): Pair<Int, String> {
17-
TODO("Not yet implemented")
24+
): Pair<Int, String> = withContext(SdkDispatchers.IO) {
25+
val pipeFds = IntArray(2)
26+
if (pipe(pipeFds.refTo(0)) != 0) {
27+
error("Failed to create pipe")
28+
}
29+
val (readFd, writeFd) = pipeFds
30+
31+
val pid = fork()
32+
if (pid == -1) {
33+
close(readFd)
34+
close(writeFd)
35+
error("Failed to fork")
36+
}
37+
38+
if (pid == 0) {
39+
// Child process
40+
close(readFd) // Close read end
41+
42+
// Pass stdout and stderr back to parent
43+
try {
44+
dup2(writeFd, STDOUT_FILENO)
45+
dup2(writeFd, STDERR_FILENO)
46+
} finally {
47+
close(writeFd)
48+
}
49+
50+
val shell = when (platformProvider.osInfo().family) {
51+
OsFamily.Windows -> "cmd.exe"
52+
else -> "sh"
53+
}
54+
55+
val shellArg = when (platformProvider.osInfo().family) {
56+
OsFamily.Windows -> "/C"
57+
else -> "-c"
58+
}
59+
60+
val argv = memScoped { (arrayOf(shell, shellArg, command).map { it.cstr.ptr } + null).toCValues() }
61+
execvp(shell, argv)
62+
_exit(127) // If exec fails
63+
}
64+
65+
// Parent process
66+
close(writeFd) // Close write end
67+
68+
val output = try {
69+
buildString {
70+
val buffer = ByteArray(1024)
71+
var totalBytesRead = 0L
72+
73+
withTimeout(timeoutMillis) {
74+
while (true) {
75+
val nBytes = minOf(maxOutputLengthBytes - totalBytesRead, buffer.size.toLong())
76+
if (nBytes == 0L) {
77+
throw CredentialsProviderException("Process output exceeded limit of $maxOutputLengthBytes bytes")
78+
}
79+
80+
val rc = read(readFd, buffer.refTo(0), nBytes.toULong()).toInt()
81+
if (rc <= 0) break
82+
totalBytesRead += rc
83+
append(buffer.decodeToString(0, rc))
84+
}
85+
}
86+
}
87+
} finally {
88+
close(readFd)
89+
}
90+
91+
memScoped {
92+
val status = alloc<IntVar>()
93+
waitpid(pid, status.ptr, 0)
94+
val exitCode = when (platformProvider.osInfo().family) {
95+
OsFamily.Windows -> status.value
96+
else -> (status.value shr 8) and 0xFF
97+
}
98+
99+
exitCode to output
100+
}
18101
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ ksp-version = "2.1.0-1.0.29" # Keep in sync with kotlin-version
44

55
dokka-version = "1.9.10"
66

7-
aws-kotlin-repo-tools-version = "0.4.20"
7+
aws-kotlin-repo-tools-version = "0.4.25-kn"
88

99
# libs
1010
coroutines-version = "1.9.0"

hll/dynamodb-mapper/dynamodb-mapper/build.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,10 @@ if (project.NATIVE_ENABLED) {
112112
}
113113
}
114114

115-
tasks.named("jvmSourcesJar") {
116-
dependsOn(moveGenSrc)
115+
listOf("jvmSourcesJar", "metadataSourcesJar").forEach {
116+
tasks.named(it) {
117+
dependsOn(moveGenSrc)
118+
}
117119
}
118120

119121
tasks.withType<KotlinCompilationTask<*>> {

0 commit comments

Comments
 (0)