Skip to content

Commit 0e1aae3

Browse files
authored
feat: Kotlin/Native Windows compilation (#152)
1 parent 714c6b2 commit 0e1aae3

File tree

19 files changed

+211
-165
lines changed

19 files changed

+211
-165
lines changed

.github/scripts/run-container-test.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import subprocess
2424
import shlex
2525
import shutil
26+
import textwrap
2627

2728
VERBOSE = False
2829

@@ -32,6 +33,10 @@
3233
"al2": "public.ecr.aws/amazonlinux/amazonlinux:2"
3334
}
3435

36+
DISTRO_TO_PACKAGES = {
37+
"al2023": [ "libxcrypt-compat" ],
38+
}
39+
3540
DOCKER_PLATFORM_BY_ARCH = {
3641
"x64": "linux/amd64",
3742
"arm64": "linux/arm64"
@@ -83,17 +88,67 @@ def oci_executable():
8388
exit(1)
8489

8590

86-
def run_docker_test(opts):
91+
def create_docker_image(opts, oci_exe, base_image_name, packages):
92+
lines = []
93+
94+
# Set base image
95+
lines.append(f"FROM {base_image_name}")
96+
97+
# Install extra packages if necessary
98+
if packages:
99+
cmd = [
100+
"RUN yum -y install",
101+
*packages,
102+
]
103+
lines.append(' '.join(cmd))
104+
105+
# Compile the contents
106+
content = '\n'.join(lines) + '\n'
107+
108+
# Write the Dockerfile
109+
with open("Dockerfile", "w") as f:
110+
f.write(content)
111+
112+
# Build the image
113+
image_name = f"container-test-{opts.distro}:latest"
114+
platform = DOCKER_PLATFORM_BY_ARCH[opts.arch]
115+
cmd = shlex.join([
116+
oci_exe,
117+
"build",
118+
"-t",
119+
image_name,
120+
"--platform",
121+
platform,
122+
".",
123+
])
124+
shell(cmd)
125+
126+
print(f"Created Docker image {image_name}. Listing hosted images to be certain:")
127+
shell("docker image ls --all")
128+
129+
return image_name
130+
131+
132+
def get_docker_image(opts, oci_exe):
133+
base_image_name = DISTRO_TO_IMAGE_NAME[opts.distro]
134+
packages = DISTRO_TO_PACKAGES.get(opts.distro)
135+
if packages:
136+
return create_docker_image(opts, oci_exe, base_image_name, packages)
137+
else:
138+
return base_image_name
139+
140+
141+
def run_docker_test(opts):
87142
"""
88143
Run a docker test for a precompiled Kotlin/Native binary
89144
90145
:param opts: the parsed command line options
91146
"""
92147
platform = DOCKER_PLATFORM_BY_ARCH[opts.arch]
93148
oci_exe = oci_executable()
149+
image_name = get_docker_image(opts, oci_exe)
94150

95151
test_bin_dir = os.path.abspath(opts.test_bin_dir)
96-
image_name = DISTRO_TO_IMAGE_NAME[opts.distro]
97152
path_to_exe = f'./linux{opts.arch.capitalize()}/debugTest/test.kexe'
98153

99154
cmd = [

.github/workflows/ci.yaml

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
shell: bash
3535
run: |
3636
./gradlew jvmApiCheck
37-
./gradlew -Ptest.java.version=${{ matrix.java-version }} -Paws.sdk.kotlin.crt.disableCrossCompile=true jvmTest --stacktrace
37+
./gradlew -Ptest.java.version=${{ matrix.java-version }} jvmTest --stacktrace
3838
- name: Save Test Reports
3939
if: failure()
4040
uses: actions/upload-artifact@v4
@@ -58,7 +58,7 @@ jobs:
5858
uses: ./.github/actions/setup-build
5959
- name: Build and Test ${{ env.PACKAGE_NAME }}
6060
run: |
61-
./gradlew -Paws.sdk.kotlin.crt.disableCrossCompile=true build
61+
./gradlew build
6262
- name: Save Test Reports
6363
if: failure()
6464
uses: actions/upload-artifact@v4
@@ -68,7 +68,6 @@ jobs:
6868

6969
# build and test for targets: jvm, linuxX64
7070
# cross compile for: linuxX64, linuxArm64
71-
# TODO - add mingw as cross compile target
7271
linux:
7372
runs-on: ubuntu-22.04
7473
steps:
@@ -79,7 +78,7 @@ jobs:
7978
uses: ./.github/actions/setup-build
8079
- name: Configure Docker Images
8180
run: |
82-
./docker-images/build-all.sh
81+
./docker-images/build-images.sh linux-x64 linux-arm64
8382
- name: Build and Test ${{ env.PACKAGE_NAME }}
8483
run: |
8584
./gradlew build
@@ -97,7 +96,7 @@ jobs:
9796
if: failure()
9897
uses: actions/upload-artifact@v4
9998
with:
100-
name: test-reports-linux
99+
name: test-reports-linux-jvm
101100
path: '**/build/reports'
102101
retention-days: 15
103102

@@ -130,18 +129,26 @@ jobs:
130129
RUN_CONTAINER_TEST=./native-test-binaries/.github/scripts/run-container-test.py
131130
$RUN_CONTAINER_TEST --distro ${{ matrix.distro }} --arch ${{ matrix.arch }} --test-bin-dir ./native-test-binaries/aws-crt-kotlin/build/bin
132131
133-
# windows JVM
132+
# windows JVM & native
134133
windows:
135134
runs-on: windows-2022
136135
steps:
137136
- uses: actions/checkout@v4
138137
with:
139138
submodules: true
139+
- name: Set up MSYS2
140+
uses: msys2/setup-msys2@v2
141+
with:
142+
msystem: MINGW64
143+
install: >-
144+
mingw-w64-x86_64-cmake
145+
mingw-w64-x86_64-gcc
140146
- name: Setup build environment
141147
uses: ./.github/actions/setup-build
142148
- name: Build and Test ${{ env.PACKAGE_NAME }}
149+
shell: msys2 {0}
143150
run: |
144-
./gradlew -P"aws.sdk.kotlin.crt.disableCrossCompile"=true build
151+
./gradlew build -Paws.crt.disableContainerTargets=mingw_x64
145152
- name: Save Test Reports
146153
if: failure()
147154
uses: actions/upload-artifact@v4
@@ -150,5 +157,3 @@ jobs:
150157
path: '**/build/reports'
151158

152159
# TODO - native test reports?
153-
# TODO - windows native
154-

CMakeLists.txt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,22 @@ function(bundle_static_library name deps)
162162
COMMENT "Bundling ${name}"
163163
VERBATIM
164164
)
165+
166+
elseif (CMAKE_C_COMPILER_ID MATCHES "^MSVC$")
167+
foreach(tgt IN LISTS deps)
168+
list(APPEND static_libs_full_names $<TARGET_FILE:${tgt}>)
169+
endforeach()
170+
171+
add_custom_command(
172+
COMMAND ${CMAKE_AR} /OUT:${tgt_full_name} ${static_libs_full_names}
173+
OUTPUT ${tgt_full_name}
174+
COMMENT "Bundling ${name}"
175+
VERBATIM
176+
COMMAND_EXPAND_LISTS
177+
)
178+
165179
else()
166-
message(FATAL_ERROR "Unknown bundling!")
180+
message(FATAL_ERROR "Unknown bundling for C compiler ${CMAKE_C_COMPILER_ID}!")
167181
endif()
168182

169183
add_custom_target(bundling_target ALL DEPENDS ${tgt_full_name})

aws-crt-kotlin/build.gradle.kts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import aws.sdk.kotlin.gradle.crt.CMakeBuildType
66
import aws.sdk.kotlin.gradle.crt.cmakeInstallDir
77
import aws.sdk.kotlin.gradle.crt.configureCrtCMakeBuild
8-
import aws.sdk.kotlin.gradle.crt.disableCrossCompileTargets
98
import aws.sdk.kotlin.gradle.dsl.configurePublishing
109
import aws.sdk.kotlin.gradle.kmp.configureIosSimulatorTasks
1110
import aws.sdk.kotlin.gradle.kmp.configureKmpTargets
@@ -115,12 +114,6 @@ tasks.register("linuxTestBinaries") {
115114
}
116115
}
117116

118-
val disableCrossCompile = providers.gradleProperty("aws.sdk.kotlin.crt.disableCrossCompile").getOrNull() == "true"
119-
if (disableCrossCompile) {
120-
logger.warn("aws.sdk.kotlin.crt.disableCrossCompile=true: Cross compilation is disabled.")
121-
disableCrossCompileTargets()
122-
}
123-
124117
// run tests on specific JVM version
125118
val testJavaVersion = typedProp<String>("test.java.version")?.let {
126119
JavaLanguageVersion.of(it)

aws-crt-kotlin/native/interop/crt.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ headerFilter = aws/common/* aws/io/* aws/http/* aws/compression/* aws/auth/* aws
3333

3434
linkerOpts.osx = -framework Security -framework Network
3535
linkerOpts.ios = -framework Security -framework Network
36+
linkerOpts.mingw = -lcrypt32 -lsecur32 -lncrypt -lshlwapi
3637

3738
# included libs are linked automatically, adding linkerOpts like `-laws-c-common` causes
3839
# issues downstream as it will search for that library still.

build-support/src/main/kotlin/aws/sdk/kotlin/gradle/crt/CMakeTasks.kt

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.gradle.api.tasks.TaskProvider
1111
import org.gradle.kotlin.dsl.named
1212
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
1313
import org.jetbrains.kotlin.konan.target.HostManager
14+
import org.jetbrains.kotlin.konan.target.KonanTarget
1415

1516
/**
1617
* See [CMAKE_BUILD_TYPE](https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html)
@@ -79,8 +80,8 @@ private fun Project.registerCmakeConfigureTask(
7980
val cmakeBuildDir = project.cmakeBuildDir(knTarget)
8081
val installDir = project.cmakeInstallDir(knTarget)
8182

82-
val relativeBuildDir = cmakeBuildDir.relativeTo(project.rootDir).path
83-
val relativeInstallDir = installDir.relativeTo(project.rootDir).path
83+
val relativeBuildDir = cmakeBuildDir.relativeTo(project.rootDir).slashPath
84+
val relativeInstallDir = installDir.relativeTo(project.rootDir).slashPath
8485
val cmakeLists = project.rootProject.projectDir.resolve("CMakeLists.txt")
8586

8687
return project.tasks.register(knTarget.cmakeConfigureTaskName) {
@@ -141,7 +142,7 @@ private fun Project.registerCmakeBuildTask(
141142
buildType: CMakeBuildType,
142143
): TaskProvider<Task> {
143144
val cmakeBuildDir = project.cmakeBuildDir(knTarget)
144-
val relativeBuildDir = cmakeBuildDir.relativeTo(project.rootDir).path
145+
val relativeBuildDir = cmakeBuildDir.relativeTo(project.rootDir).slashPath
145146

146147
return project.tasks.register(knTarget.cmakeBuildTaskName) {
147148
group = "ffi"
@@ -187,7 +188,7 @@ private fun Project.registerCmakeInstallTask(
187188
buildType: CMakeBuildType,
188189
): TaskProvider<Task> {
189190
val cmakeBuildDir = project.cmakeBuildDir(knTarget)
190-
val relativeBuildDir = cmakeBuildDir.relativeTo(project.rootDir).path
191+
val relativeBuildDir = cmakeBuildDir.relativeTo(project.rootDir).slashPath
191192
val installDir = project.cmakeInstallDir(knTarget)
192193

193194
return project.tasks.register(knTarget.cmakeInstallTaskName) {
@@ -208,20 +209,51 @@ private fun Project.registerCmakeInstallTask(
208209
}
209210
}
210211

212+
/**
213+
* Konan targets for which a container will be used execute CMake tasks. Targets that do not appear in this list or are
214+
* disabled by `aws.sdk.kotlin.crt.disableCrossCompile` will be compiled without using a container (i.e., in the same
215+
* shell executing Gradle).
216+
*/
217+
private val containerCompileTargets = setOf(
218+
KonanTarget.LINUX_X64,
219+
KonanTarget.LINUX_ARM64,
220+
KonanTarget.MINGW_X64,
221+
)
222+
223+
/**
224+
* Konan targets which require explicitly running CMake inside of Bash
225+
*/
226+
private val requiresExplicitBash = setOf(
227+
KonanTarget.MINGW_X64,
228+
)
229+
211230
private fun runCmake(project: Project, target: KotlinNativeTarget, cmakeArgs: List<String>) {
231+
val disableContainerTargets = (project.properties["aws.crt.disableContainerTargets"] as? String ?: "")
232+
.split(',')
233+
.map { it.trim() }
234+
.toSet()
235+
236+
val useContainer = target.konanTarget in containerCompileTargets &&
237+
target.konanTarget.name !in disableContainerTargets
238+
212239
project.exec {
213240
workingDir(project.rootDir)
214241
val exeArgs = cmakeArgs.toMutableList()
215-
val exeName = when {
216-
target.konanTarget in crossCompileTargets -> {
217-
// cross compiling via dockcross - set the docker exe to cmake
218-
val containerScriptArgs = listOf("--args", "--pull=missing", "--", "cmake")
219-
exeArgs.addAll(0, containerScriptArgs)
220-
val script = "dockcross-" + target.konanTarget.name.replace("_", "-")
221-
validateCrossCompileScriptsAvailable(project, script)
242+
val exeName = if (useContainer) {
243+
// cross compiling via dockcross - set the docker exe to cmake
244+
val containerScriptArgs = listOf("--args", "--pull=missing", "--", "cmake")
245+
exeArgs.addAll(0, containerScriptArgs)
246+
val script = "dockcross-" + target.konanTarget.name.replace("_", "-")
247+
validateCrossCompileScriptsAvailable(project, script)
248+
249+
if (target.konanTarget in requiresExplicitBash) {
250+
exeArgs.add(0, "./$script")
251+
"bash"
252+
} else {
222253
"./$script"
223254
}
224-
else -> "cmake"
255+
} else {
256+
"cmake"
225257
}
226258

227259
project.logger.info("$exeName ${exeArgs.joinToString(separator = " ")}")
@@ -238,8 +270,6 @@ private fun validateCrossCompileScriptsAvailable(project: Project, script: Strin
238270
the cross compile scripts.
239271
240272
e.g. `./docker-images/build-all.sh`
241-
242-
Alternatively disable cross compilation by setting the property `-Paws.sdk.kotlin.crt.disableCrossCompile=true`
243273
""".trimIndent()
244274
error(message)
245275
}

build-support/src/main/kotlin/aws/sdk/kotlin/gradle/crt/CMakeUtils.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,6 @@ val KotlinNativeTarget.cmakeBuildTaskName: String
8787

8888
val KotlinNativeTarget.cmakeInstallTaskName: String
8989
get() = namedSuffix("cmakeInstall", capitalized = true)
90+
91+
val File.slashPath: String
92+
get() = path.replace(File.separatorChar, '/')

0 commit comments

Comments
 (0)