Implement static linking for Windows executables and add CI validation workflow
✅ Static Linking Implementation for Windows Executables
Implements comprehensive static linking to resolve libstdc++-6.dll dependency issues on Windows.
Changes Made
Static Linking Implementation
-
All example build.gradle.kts files (example1, example2, example3, timeout):
- Added static linking flags:
-static-libgcc,-static-libstdc++for MINGW_X64 target - Configured linking to kommand-core static library (
libkommand_core.a) - Added
buildKommandCoreForWintask to build Rust library with static linking before assembly - Fixed: Task now only runs on Windows platform (currentPlatform == MINGW_X64)
- Added compiler and linker options specifically for Windows MinGW target
- Added static linking flags:
-
Rust configuration (
kommand-core/.cargo/config.toml):- Created cargo config with rustflags for
x86_64-pc-windows-gnutarget - Configured static linking for libgcc and libstdc++ at the Rust build level
- Created cargo config with rustflags for
CI Workflow
-
New workflow:
.github/workflows/windows-no-mingw-examples.yml-
Two-job approach:
- Job 1 (
build-examples): Builds with MSYS2/MinGW installed, uploads executables as artifacts - Job 2 (
windows-no-mingw-test): Tests executables in clean environment WITHOUT MinGW
- Job 1 (
- Verifies MinGW is not in PATH during testing phase
- Builds all examples:
./gradlew :kommand-examples:assemble - Locates and executes all produced
.exefiles - Checks DLL dependencies via dumpbin if available
- Fails if any build or executable returns non-zero exit code
-
Two-job approach:
-
Triggers: push/PR to main/dev with path filters for relevant source directories
-
Artifacts: uploads executables (7-day retention) for debugging
How It Works
-
Build phase (with MinGW): Builds kommand-core Rust library as static lib with static linking flags, then links examples against it with
-static-libgcc -static-libstdc++to produce self-contained .exe files -
Test phase (without MinGW): Downloads built executables and runs them in a clean Windows environment to verify they don't require external MinGW runtime DLLs
Validation
The workflow ensures Windows executables are truly self-contained and can run on systems without MinGW, catching linking issues that would cause runtime DLL dependency failures on end-user machines.
- Fixes kgit2/kommand#23
Original prompt
This section details on the original issue you should resolve
<issue_title>libstdc++-6.dll Missing on windows</issue_title> <issue_description>Good evening, I may be entirely in the realm of wrong as I've only recently started looking into the world of kotlin/multiplatform. When using IntelliJ on linux and building a mingwx64 binary it will not run on windows when including this library stating that libstdc++-6.dll is missing or not found. I have tried linker opts below with no luck.
Any ideas outside of installing mingw on windows? Or any idea what would be causing this on all my windows 10/11 instances? My hopes is a binary without any additional dependencies needed or libs copied alongside the exe
mingwX64() { binaries { compilerOptions { allWarningsAsErrors = true } executable { linkerOpts.addAll(listOf("-static", "-static-libgcc", "-static-libstdc++")) entryPoint = "main" } } }</issue_description><agent_instructions>Purpose
- Verify that recent Windows linking changes do not break any kommand examples on a Windows runner that does NOT have MinGW installed.
- Create the workflow file at
.github/workflows/windows-no-mingw-examples.ymlwith the YAML below and add it to this PR.- Ensure the workflow is committed as part of this PR so it runs against these changes.
- Do not install or configure MinGW/MSYS in the workflow—the job must simulate a Windows environment without MinGW.
What the workflow must do
- Trigger on push and pull_request (restricted to relevant paths).
- Run on
windows-latest.- Checkout the repo and set up Java 17 (Temurin).
- Discover all example subprojects under
kommand-examples(folders withbuild.gradleorbuild.gradle.kts).- For each example: run
./gradlew :kommand-examples:<name>:assemble(fail the job on any non-zero exit).- Locate produced native executables (e.g.,
kommand-examples/**/build/bin/**/*.exe) and run each one (fail the job if any executable exits with non-zero code).- Upload per-run logs/artifacts for debugging (stdout/stderr, Gradle logs).
Acceptance criteria
- CI succeeds only if every example builds and every produced executable exits with code 0.
- On failure, logs/artifacts are uploaded to aid troubleshooting. </agent_instructions>
Comments on the Issue (you are @copilot in this section)
- Fixes kgit2/kommand#23
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.
@copilot repaire this: *** 新增文件: .github/workflows/windows-no-mingw-examples.yml *** [把整个下面的 YAML 内容写入该路径] name: Windows No-MinGW Examples Test
开启:推送:分支:[“main”,“dev”]路径:-'kommand-examples / ' -'kommand-core / '-'src /'-'build.gradle.kts'-'.github/workflows/windows-no-mingw-examples.yml'pull_request:分支:[“main”,“dev”]路径:-'kommand-examples / '-' kommand-core /'-'src /'-'build.gradle.kts'-'.github/workflows/windows-no-mingw-examples.yml'workflow_dispatch:
权限:内容:读取
作业:构建示例:名称:构建示例(使用 MSYS2 / MinGW)运行于:windows-latest 步骤:-名称:Checkout 用途:actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
architecture: 'x64'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
- name: Install MSYS2/Mingw toolchain
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: >
mingw-w64-x86_64-toolchain mingw-w64-x86_64-gcc
- name: Add MSYS2 mingw to PATH
shell: pwsh
run: |
$mingw = 'C:\msys64\mingw64\bin'
if (Test-Path $mingw) { Write-Host "Adding $mingw to PATH"; echo "::add-path::$mingw" } else { Write-Host "MSYS2 mingw not found at $mingw" }
- name: Build all examples
shell: pwsh
run: |
.\gradlew.bat :kommand-examples:assemble --no-daemon --stacktrace
- name: Find built executables and upload
shell: pwsh
run: |
$matches = Get-ChildItem -Path "kommand-examples" -Recurse -Filter "*.exe" | Where-Object { $_.FullName -like "*\build\bin\*" } | Select-Object -ExpandProperty FullName
if ($matches.Count -eq 0) {
Write-Host "No executables found, showing build directories for debugging"
Get-ChildItem -Path "kommand-examples" -Recurse -Directory -Filter "build" | ForEach-Object { Get-ChildItem -Path $_.FullName -Recurse | Select-Object FullName | Format-Table -AutoSize }
exit 1
} else {
Write-Host "Found executables:"
$matches | ForEach-Object { Write-Host " - $_" }
}
- name: Upload built executables
uses: actions/upload-artifact@v4
with:
name: built-executables
path: kommand-examples/**/build/bin/**/*.exe
retention-days: 7
windows-no-mingw-test:名称:Windows 上的测试示例(无 MinGW)运行于:windows-latest 需求:build-examples 步骤:-名称:Checkout 用途:actions/checkout@v4
- name: Verify MinGW not installed
shell: pwsh
run: |
Write-Host "Checking that MinGW is not in PATH..."
$mingwPaths = @("C:\msys64","C:\mingw-w64","C:\MinGW")
foreach ($p in $mingwPaths) { if (Test-Path $p) { Write-Host "WARNING: $p exists" } }
$pathEnv = $env:PATH -split ';'
$mingwInPath = $pathEnv | Where-Object { $_ -like "*mingw*" -or $_ -like "*msys*" }
if ($mingwInPath) { Write-Host "WARNING: MinGW/MSYS found in PATH: $mingwInPath"; exit 1 } else { Write-Host "✓ No MinGW/MSYS found in PATH" }
- name: Download built executables
uses: actions/download-artifact@v4
with:
name: built-executables
path: artifacts
- name: Find executables to test
shell: pwsh
run: |
$executables = Get-ChildItem -Path "artifacts" -Recurse -Filter "*.exe" | Where-Object { $_.FullName -like "*\build\bin\*" }
if ($executables.Count -eq 0) {
Write-Host "✗ No executables found in artifacts!"
exit 1
}
Write-Host "Found $($executables.Count) exe(s):"
$executables | ForEach-Object { Write-Host " - $($_.FullName)" }
- name: Test each executable
shell: pwsh
run: |
$executables = Get-ChildItem -Path "artifacts" -Recurse -Filter "*.exe" | Where-Object { $_.FullName -like "*\build\bin\*" }
$errors = @()
foreach ($exe in $executables) {
Write-Host "`n--- Running $($exe.FullName) ---"
$dumpbin = Get-Command dumpbin.exe -ErrorAction SilentlyContinue
if ($dumpbin) {
& dumpbin /DEPENDENTS $exe.FullName 2>&1 | Tee-Object -FilePath "$($exe.Name)-dependencies.log"
} else {
Write-Host "dumpbin not available; skipping dependency dump"
}
$proc = Start-Process -FilePath $exe.FullName -NoNewWindow -PassThru -Wait -RedirectStandardOutput "$($exe.Name)-stdout.log" -RedirectStandardError "$($exe.Name)-stderr.log"
if ($proc.ExitCode -ne 0) {
Write-Host "✗ $($exe.Name) exited with $($proc.ExitCode)"
$errors += "$($exe.Name) (exit $($proc.ExitCode))"
} else {
Write-Host "✓ $($exe.Name) ran (exit 0)"
}
}
if ($errors.Count -gt 0) { Write-Host "Failures: $errors"; exit 1 } else { Write-Host "All ok" }
*** 修改文件: kommand-examples/example1/build.gradle.kts *** (替换该文件为下面内容:) import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget import org.jetbrains.kotlin.konan.target.KonanTarget
val currentPlatform:Platform = when { DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MACOS_X64 DefaultNativePlatform.getCurrentOperatingSystem().isMacOsX && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.MACOS_ARM64 DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.LINUX_X64 DefaultNativePlatform.getCurrentOperatingSystem().isLinux && DefaultNativePlatform.getCurrentArchitecture().isArm64 -> Platform.LINUX_ARM64 DefaultNativePlatform.getCurrentOperatingSystem().isWindows && DefaultNativePlatform.getCurrentArchitecture().isAmd64 -> Platform.MINGW_X64 else -> throw GradleException("Kotlin/Native 不支持主机操作系统。") }
插件 { kotlin("多平台") }
存储库 { mavenCentral() gradlePluginPortal() }
kotlin { jvm { compilerOptions { jvmTarget.set(JvmTarget.JVM_17) } withJava() testRuns["test"].executionTask.configure { useJUnitPlatform() } }
val nativeTarget = when (currentPlatform) {
Platform.MACOS_X64 -> macosX64()
Platform.MACOS_ARM64 -> macosArm64()
Platform.LINUX_X64 -> linuxX64()
Platform.LINUX_ARM64 -> linuxArm64()
Platform.MINGW_X64 -> mingwX64()
}
nativeTarget.apply {
binaries {
executable {
entryPoint = "com.kgit2.kommand.main"
}
}
}
// Ensure kommand-core static lib is built and linked for mingw target
targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget::class.java).configureEach {
binaries.configureEach {
if (konanTarget == org.jetbrains.kotlin.konan.target.KonanTarget.MINGW_X64) {
// static-link libgcc/libstdc++
linkerOpts.addAll(listOf("-static-libgcc", "-static-libstdc++"))
// point to kommand-core static lib output (built by cargo)
val coreLibDir = file("${rootProject.projectDir}/kommand-core/target/x86_64-pc-windows-gnu/release")
linkerOpts.addAll(listOf("-L", coreLibDir.absolutePath, "-l:libkommand_core.a"))
// include dir for generated header
compilerOpts.addAll(listOf("-I", "${rootProject.projectDir}/kommand-core"))
}
}
}
// Ensure kommand-core is built before assembling examples
tasks.register("buildKommandCoreForWin") {
doLast {
exec {
workingDir = file("${rootProject.projectDir}/kommand-core")
environment("RUSTFLAGS", "-C link-arg=-static-libgcc -C link-arg=-static-libstdc++")
commandLine("cargo", "build", "--release", "--target", "x86_64-pc-windows-gnu")
}
}
}
tasks.matching { it.name == "assemble" }.configureEach { dependsOn(tasks.named("buildKommandCoreForWin")) }
applyDefaultHierarchyTemplate()
sourceSets {
commonMain {
dependencies {
implementation(rootProject)
}
}
}
枚举类平台(val archName:String){MACOS_X64(“x86_64-apple-darwin”),MACOS_ARM64(“aarch64-apple-darwin”),LINUX_X64(“x86_64-unknown-linux-gnu”),LINUX_ARM64(“aarch64-unknown-linux-gnu”),MINGW_X64(“x86_64-pc-windows-gnu”),;}
*** 修改文件: kommand-examples/example2/build.gradle.kts *** (把和 example1 中四个的应用到 example2 的 build.gradle.kts;内容同上,确保加上 Targets.withType ... linkerOpts 和 buildKommandCoreForWin 任务)
*** 修改文件: kommand-examples/example3/build.gradle.kts *** (把和 example1 中四个的应用到 example3 的 build.gradle.kts;内容同上)
*** 修改文件: kommand-examples/timeout/build.gradle.kts *** (把和 example1 中四个的应用到 timeout 的 build.gradle.kts;内容同上)
*** 修改/新增文件: kommand-core/Cargo.toml *** (在 kommand-core 的 Cargo.toml 中添加/确保以下段落;如果已有 [lib] 则合入) [lib] name = "kommand_core" crate-type = ["staticlib"]
*** 新增文件: kommand-core/.cargo/config.toml *** [target.x86_64-pc-windows-gnu] rustflags = [ "-C", "link-arg=-static-libgcc", "-C", "link-arg=-static-libstdc++" ]
@copilot repaire this: *** 新增文件: .github/workflows/windows-no-mingw-examples.yml *** [把整个下面的 YAML 内容写入该路径] name: Windows No-MinGW Examples Test
...
All requested changes implemented in commit c3671cb:
✅ Workflow split into two jobs (build with MinGW, test without MinGW) ✅ Static linking flags added to all 4 example build.gradle.kts files ✅ kommand-core static library linking configured for MINGW_X64 target ✅ buildKommandCoreForWin task added to build Rust library before assembly ✅ cargo config.toml created with static linking flags for Windows
The workflow now builds executables with MSYS2/MinGW in the first job, then tests them in a clean Windows environment without MinGW in the second job to verify they're self-contained.
@copilot 所有workflow全部失败了
@copilot 所有workflow全部失败了
Fixed in commit d81c07f. The buildKommandCoreForWin task was running on all platforms (macOS, Linux, Windows), causing failures on non-Windows CI runners since they don't have the Windows Rust target toolchain.
The task is now wrapped in a platform check:
if (currentPlatform == Platform.MINGW_X64) {
tasks.register("buildKommandCoreForWin") { ... }
}
This ensures it only runs when building for Windows. The workflows should now pass on all platforms.