Skip to content

Commit 4f4f2d3

Browse files
committed
Relocate mingw version introspection to aws-crt-kotlin
1 parent 450cbc6 commit 4f4f2d3

File tree

2 files changed

+144
-0
lines changed

2 files changed

+144
-0
lines changed

aws-crt-kotlin/build.gradle.kts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0
44
*/
5+
import aws.sdk.kotlin.gradle.kmp.NATIVE_ENABLED
56
import aws.sdk.kotlin.gradle.crt.CMakeBuildType
67
import aws.sdk.kotlin.gradle.crt.cmakeInstallDir
78
import aws.sdk.kotlin.gradle.crt.configureCrtCMakeBuild
@@ -12,6 +13,8 @@ import aws.sdk.kotlin.gradle.util.typedProp
1213
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
1314
import org.jetbrains.kotlin.konan.target.Family
1415
import org.jetbrains.kotlin.konan.target.HostManager
16+
import java.nio.file.Files
17+
import java.nio.file.Paths
1518

1619
plugins {
1720
alias(libs.plugins.kotlin.multiplatform)
@@ -103,6 +106,58 @@ kotlin {
103106
}
104107
}
105108
}
109+
110+
if (NATIVE_ENABLED && HostManager.hostIsMingw) {
111+
mingwX64 {
112+
val mingwHome = findMingwHome()
113+
val defPath = layout.buildDirectory.file("cinterop/winver.def")
114+
115+
// Dynamically construct def file because of dynamic mingw paths
116+
val defFileTask by tasks.registering {
117+
outputs.file(defPath)
118+
119+
val mingwLibs = Paths.get(mingwHome, "lib").toString().replace("\\", "\\\\") // Windows path shenanigans
120+
121+
doLast {
122+
Files.writeString(
123+
defPath.get().asFile.toPath(),
124+
"""
125+
package = aws.smithy.kotlin.native.winver
126+
headers = windows.h
127+
compilerOpts = \
128+
-DUNICODE \
129+
-DWINVER=0x0601 \
130+
-D_WIN32_WINNT=0x0601 \
131+
-DWINAPI_FAMILY=3 \
132+
-DOEMRESOURCE \
133+
-Wno-incompatible-pointer-types \
134+
-Wno-deprecated-declarations
135+
libraryPaths = $mingwLibs
136+
staticLibraries = libversion.a
137+
""".trimIndent(),
138+
)
139+
}
140+
}
141+
compilations["main"].cinterops {
142+
create("winver") {
143+
val mingwIncludes = Paths.get(mingwHome, "include").toString()
144+
includeDirs(mingwIncludes)
145+
definitionFile.set(defPath)
146+
147+
// Ensure that the def file is written first
148+
tasks[interopProcessingTaskName].dependsOn(defFileTask)
149+
}
150+
}
151+
152+
// TODO clean up
153+
val compilerArgs = listOf(
154+
"-Xverbose-phases=linker", // Enable verbose linking phase from the compiler
155+
"-linker-option",
156+
"-v",
157+
)
158+
compilerOptions.freeCompilerArgs.addAll(compilerArgs)
159+
}
160+
}
106161
}
107162

108163
configureIosSimulatorTasks()
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.crt.util
6+
7+
// The functions below are adapted from C++ SDK:
8+
// https://github.com/aws/aws-sdk-cpp/blob/0e6085bf0dd9a1cb1f27d101c4cf2db6ade6f307/src/aws-cpp-sdk-core/source/platform/windows/OSVersionInfo.cpp#L49-L106
9+
10+
private val wordHexFormat = HexFormat {
11+
upperCase = false
12+
number {
13+
removeLeadingZeros = true
14+
minLength = 4
15+
}
16+
}
17+
18+
private data class LangCodePage(
19+
val language: UShort,
20+
val codePage: UShort,
21+
)
22+
23+
public fun osVersionFromKernel(): String? = memScoped {
24+
withFileVersionInfo("Kernel32.dll") { versionInfoPtr ->
25+
getLangCodePage(versionInfoPtr)?.let { langCodePage ->
26+
getProductVersion(versionInfoPtr, langCodePage)
27+
}
28+
}
29+
}
30+
31+
private inline fun <R> withFileVersionInfo(fileName: String, block: (CPointer<ByteVarOf<Byte>>) -> R?): R? {
32+
val blobSize = GetFileVersionInfoSizeW(fileName, null)
33+
val blob = ByteArray(blobSize.convert())
34+
blob.usePinned { pinned ->
35+
val result = GetFileVersionInfoW(fileName, 0u, blobSize, pinned.addressOf(0))
36+
return if (result == 0) {
37+
null
38+
} else {
39+
block(pinned.addressOf(0))
40+
}
41+
}
42+
}
43+
44+
private fun MemScope.getLangCodePage(versionInfoPtr: CPointer<ByteVarOf<Byte>>): LangCodePage? {
45+
// Get _any_ language pack and codepage since they should all have the same version
46+
val langAndCodePagePtr = alloc<COpaquePointerVar>()
47+
val codePageSize = alloc<UIntVar>()
48+
val result = VerQueryValueW(
49+
versionInfoPtr,
50+
"""\VarFileInfo\Translation""",
51+
langAndCodePagePtr.ptr,
52+
codePageSize.ptr,
53+
)
54+
55+
return if (result == 0) {
56+
null
57+
} else {
58+
val langAndCodePage = langAndCodePagePtr.value!!.reinterpret<UIntVar>().pointed.value
59+
val language = (langAndCodePage and 0x0000ffffu).toUShort() // low WORD
60+
val codePage = (langAndCodePage and 0xffff0000u shr 16).toUShort() // high WORD
61+
LangCodePage(language, codePage)
62+
}
63+
}
64+
65+
private fun MemScope.getProductVersion(versionInfoPtr: CPointer<ByteVarOf<Byte>>, langCodePage: LangCodePage): String? {
66+
val versionId = buildString {
67+
// Something like: \StringFileInfo\04090fb0\ProductVersion
68+
append("""\StringFileInfo\""")
69+
append(langCodePage.language.toHexString(wordHexFormat))
70+
append(langCodePage.codePage.toHexString(wordHexFormat))
71+
append("""\ProductVersion""")
72+
}
73+
74+
// Get the block corresponding to versionId
75+
val block = alloc<COpaquePointerVar>()
76+
val blockSize = alloc<UIntVar>()
77+
val result = VerQueryValueW(versionInfoPtr, versionId, block.ptr, blockSize.ptr)
78+
79+
return if (result == 0) {
80+
null
81+
} else {
82+
// Copy the bytes into a Kotlin byte array
83+
val blockBytes = ByteArray(blockSize.value.convert())
84+
blockBytes.usePinned { pinned ->
85+
memcpy(pinned.addressOf(0), block.value!!.reinterpret<ByteVar>(), blockSize.value.convert())
86+
}
87+
blockBytes.decodeToString()
88+
}
89+
}

0 commit comments

Comments
 (0)