kommand icon indicating copy to clipboard operation
kommand copied to clipboard

Implement static linking for Windows executables and add CI validation workflow

Open Copilot opened this issue 4 months ago • 4 comments

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 buildKommandCoreForWin task 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
  • Rust configuration (kommand-core/.cargo/config.toml):

    • Created cargo config with rustflags for x86_64-pc-windows-gnu target
    • Configured static linking for libgcc and libstdc++ at the Rust build level

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
    • Verifies MinGW is not in PATH during testing phase
    • Builds all examples: ./gradlew :kommand-examples:assemble
    • Locates and executes all produced .exe files
    • Checks DLL dependencies via dumpbin if available
    • Fails if any build or executable returns non-zero exit code
  • Triggers: push/PR to main/dev with path filters for relevant source directories

  • Artifacts: uploads executables (7-day retention) for debugging

How It Works

  1. 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

  2. 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.
  1. Create the workflow file at .github/workflows/windows-no-mingw-examples.yml with the YAML below and add it to this PR.
  2. Ensure the workflow is committed as part of this PR so it runs against these changes.
  3. 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 with build.gradle or build.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 avatar Oct 27 '25 03:10 Copilot

@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++" ]

BppleMan avatar Oct 27 '25 05:10 BppleMan

@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 avatar Oct 27 '25 05:10 Copilot

@copilot 所有workflow全部失败了

BppleMan avatar Oct 27 '25 08:10 BppleMan

@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.

Copilot avatar Oct 27 '25 08:10 Copilot