Skip to content

fix: Windows linker flags — no -L/-rpath/-static (MSVC linker) #32

fix: Windows linker flags — no -L/-rpath/-static (MSVC linker)

fix: Windows linker flags — no -L/-rpath/-static (MSVC linker) #32

Workflow file for this run

name: ci-windows
# Windows validation CI for mcpp.
# Step 1: Verify xlings LLVM toolchain capabilities on Windows.
# Step 2: xmake bootstrap to produce first mcpp.exe.
# Step 3: Self-host — use the bootstrapped mcpp.exe to build itself (LLVM + MSVC STL).
# Step 4: Package the self-hosted binary into a distributable zip.
on:
push:
branches: [ feat/windows-support ]
pull_request:
branches: [ main ]
paths:
- 'src/toolchain/**'
- 'src/build/**'
- 'src/cli.cppm'
- '.github/workflows/ci-windows.yml'
workflow_dispatch:
concurrency:
group: ci-windows-${{ github.ref }}
cancel-in-progress: true
jobs:
windows-llvm-validation:
name: Windows x64 — LLVM toolchain validation
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4
- name: System info
shell: bash
run: |
echo "OS: $(uname -s)"
echo "Arch: $(uname -m)"
echo "Runner: $RUNNER_OS"
- name: Install xlings
shell: bash
env:
XLINGS_NON_INTERACTIVE: '1'
XLINGS_VERSION: '0.4.30'
run: |
WORK=$(mktemp -d)
zipfile="xlings-${XLINGS_VERSION}-windows-x86_64.zip"
curl -fsSL -o "${WORK}/${zipfile}" \
"https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${zipfile}"
cd "${WORK}"
unzip -q "${zipfile}"
XLINGS_DIR="${WORK}/xlings-${XLINGS_VERSION}-windows-x86_64"
echo "xlings dir: $XLINGS_DIR"
ls "$XLINGS_DIR/bin/"
"$XLINGS_DIR/subos/default/bin/xlings.exe" self install
echo "$USERPROFILE/.xlings/subos/default/bin" >> "$GITHUB_PATH"
echo "$USERPROFILE/.xlings/bin" >> "$GITHUB_PATH"
- name: Verify xlings
shell: bash
run: |
xlings.exe --version || xlings --version || echo "xlings not found in PATH"
- name: Install LLVM via xlings
shell: bash
run: |
xlings.exe install llvm -y || xlings.exe install llvm@20.1.7 -y || {
echo "::warning::xlings install llvm failed"
exit 1
}
# Find LLVM root
LLVM_ROOT=$(find "$USERPROFILE/.xlings" -path "*/xpkgs/xim-x-llvm/*/bin/clang.exe" 2>/dev/null | head -1)
if [ -n "$LLVM_ROOT" ]; then
LLVM_ROOT=$(dirname "$(dirname "$LLVM_ROOT")")
fi
echo "LLVM_ROOT=$LLVM_ROOT"
echo "LLVM_ROOT=$LLVM_ROOT" >> "$GITHUB_ENV"
- name: Inspect LLVM package structure
shell: bash
run: |
echo "=== bin/ ==="
ls "$LLVM_ROOT/bin/" | grep -iE "clang|lld|llvm-ar|scan" | head -20
echo "=== lib/ ==="
ls "$LLVM_ROOT/lib/" 2>/dev/null | head -10
echo "=== include/c++/ ==="
ls "$LLVM_ROOT/include/c++/" 2>/dev/null || echo "(no libc++ headers)"
echo "=== share/libc++/ ==="
find "$LLVM_ROOT" -name "std.cppm" 2>/dev/null || echo "(no std.cppm)"
echo "=== clang++.cfg ==="
cat "$LLVM_ROOT/bin/clang++.cfg" 2>/dev/null || echo "(no cfg)"
echo "=== clang version ==="
"$LLVM_ROOT/bin/clang++.exe" --version 2>/dev/null || "$LLVM_ROOT/bin/clang++" --version 2>/dev/null
echo "=== Target triple ==="
"$LLVM_ROOT/bin/clang++.exe" -dumpmachine 2>/dev/null || "$LLVM_ROOT/bin/clang++" -dumpmachine 2>/dev/null
- name: Test — non-module C++ compilation (clang++)
shell: bash
run: |
WORK=$(mktemp -d)
cd "$WORK"
CXX="$LLVM_ROOT/bin/clang++.exe"
test -x "$CXX" || CXX="$LLVM_ROOT/bin/clang++"
cat > main.cpp << 'EOF'
#include <iostream>
int main() {
std::cout << "Hello from clang++ on Windows!" << std::endl;
return 0;
}
EOF
echo "=== Compile ==="
"$CXX" -std=c++23 -o hello.exe main.cpp 2>&1 || {
echo "::warning::clang++ compilation failed, trying with MSVC headers"
# clang++ on Windows may need MSVC include paths
"$CXX" -std=c++23 --target=x86_64-pc-windows-msvc -o hello.exe main.cpp 2>&1 || true
}
if [ -f hello.exe ]; then
echo "=== Run ==="
./hello.exe
else
echo "::warning::Compilation did not produce hello.exe"
fi
- name: Test — clang-cl compilation
shell: bash
run: |
WORK=$(mktemp -d)
cd "$WORK"
CLANGCL="$LLVM_ROOT/bin/clang-cl.exe"
test -x "$CLANGCL" || CLANGCL="$LLVM_ROOT/bin/clang-cl"
cat > main.cpp << 'EOF'
#include <iostream>
int main() {
std::cout << "Hello from clang-cl on Windows!" << std::endl;
return 0;
}
EOF
echo "=== Compile with clang-cl ==="
"$CLANGCL" /std:c++latest /EHsc main.cpp /Fe:hello.exe 2>&1 || {
echo "::warning::clang-cl compilation failed"
echo "clang-cl may need Visual Studio installation for MSVC headers/libs"
}
if [ -f hello.exe ]; then
echo "=== Run ==="
./hello.exe
fi
- name: Check libc++ availability for import std
shell: bash
run: |
echo "=== Checking for libc++ in LLVM package ==="
find "$LLVM_ROOT" -name "*.cppm" 2>/dev/null | head -5 || echo "No .cppm files found"
find "$LLVM_ROOT" -path "*/c++/v1" -type d 2>/dev/null | head -3 || echo "No libc++ include dir"
find "$LLVM_ROOT" -name "libc++*" 2>/dev/null | head -5 || echo "No libc++ library files"
echo
echo "=== Checking MSVC STL for import std ==="
# Visual Studio on GitHub runners includes MSVC STL with module support
VSDIR="/c/Program Files/Microsoft Visual Studio/2022/Enterprise"
if [ -d "$VSDIR" ]; then
echo "Visual Studio found at: $VSDIR"
find "$VSDIR" -name "std.ixx" 2>/dev/null | head -3 || echo "No std.ixx found"
find "$VSDIR" -path "*/modules" -name "*.ixx" 2>/dev/null | head -5 || echo "No .ixx module files"
else
echo "Visual Studio not found at expected path"
ls "/c/Program Files/Microsoft Visual Studio/" 2>/dev/null || true
fi
- name: Summary
shell: bash
run: |
echo "=== Windows LLVM Validation Summary ==="
echo "LLVM Root: $LLVM_ROOT"
echo "Clang version: $("$LLVM_ROOT/bin/clang++.exe" --version 2>/dev/null | head -1 || echo 'N/A')"
echo "Target: $("$LLVM_ROOT/bin/clang++.exe" -dumpmachine 2>/dev/null || echo 'N/A')"
echo
echo "Has libc++: $([ -d "$LLVM_ROOT/include/c++/v1" ] && echo YES || echo NO)"
echo "Has std.cppm: $(find "$LLVM_ROOT" -name 'std.cppm' 2>/dev/null | head -1 | grep -q . && echo YES || echo NO)"
echo "Has clang-scan-deps: $([ -f "$LLVM_ROOT/bin/clang-scan-deps.exe" ] && echo YES || echo NO)"
echo "Has clang-cl: $([ -f "$LLVM_ROOT/bin/clang-cl.exe" ] && echo YES || echo NO)"
echo "Has lld-link: $([ -f "$LLVM_ROOT/bin/lld-link.exe" ] && echo YES || echo NO)"
- name: Install xmake via xlings
shell: bash
run: |
xlings.exe install xmake -y || xlings install xmake -y
xmake.exe --version || xmake --version
- name: Bootstrap mcpp with xmake (MSVC)
id: bootstrap
shell: pwsh
run: |
# Generate xmake.lua for bootstrap
@"
add_rules("mode.release")
set_languages("c++23")
package("cmdline")
set_homepage("https://github.com/mcpplibs/cmdline")
add_urls("https://github.com/mcpplibs/cmdline/archive/refs/tags/`$(version).tar.gz")
add_versions("0.0.1", "3fb2f5495c1a144485b3cbb2e43e27059151633460f702af0f3851cbff387ef0")
on_install(function (package)
import("package.tools.xmake").install(package)
end)
package_end()
add_requires("cmdline 0.0.1")
target("mcpp")
set_kind("binary")
add_files("src/main.cpp")
add_files("src/**.cppm")
add_packages("cmdline")
add_includedirs("src/libs/json")
set_policy("build.c++.modules", true)
"@ | Out-File -Encoding utf8 xmake.lua
xmake f -p windows -m release -y
xmake build -y mcpp
$mcpp = Get-ChildItem -Recurse build -Filter mcpp.exe -ErrorAction SilentlyContinue | Select-Object -First 1
if ($mcpp) {
Write-Host ":: mcpp.exe built at: $($mcpp.FullName)"
& $mcpp.FullName --version
# Export for subsequent steps
"MCPP_BOOTSTRAP=$($mcpp.FullName)" | Out-File -Append $env:GITHUB_ENV
} else {
Write-Host ":: Build produced files:"
Get-ChildItem -Recurse build -Filter *.exe | ForEach-Object { Write-Host " $($_.FullName)" }
exit 1
}
- name: Self-host — mcpp builds itself
shell: bash
run: |
echo "=== Self-host: using bootstrapped mcpp.exe to build mcpp ==="
# Save bootstrap binary before cleaning xmake artifacts
mkdir -p /tmp/mcpp-bootstrap
cp "$MCPP_BOOTSTRAP" /tmp/mcpp-bootstrap/mcpp.exe
MCPP_EXE="/tmp/mcpp-bootstrap/mcpp.exe"
# Clean xmake artifacts so mcpp starts fresh
rm -rf build xmake.lua .xmake
echo "Bootstrap binary: $MCPP_EXE"
"$MCPP_EXE" --version
# mcpp build uses its own build system; mcpp.toml now has
# windows = "llvm@20.1.7" so it will use the xlings LLVM.
# Use Windows-native path for MCPP_VENDORED_XLINGS
XLINGS_WIN=$(cygpath -w "$USERPROFILE/.xlings/subos/default/bin/xlings.exe")
echo "MCPP_VENDORED_XLINGS=$XLINGS_WIN"
export MCPP_VENDORED_XLINGS="$XLINGS_WIN"
echo "xlings exists: $(test -f "$USERPROFILE/.xlings/subos/default/bin/xlings.exe" && echo YES || echo NO)"
# Debug: test xlings directly
"$USERPROFILE/.xlings/subos/default/bin/xlings.exe" --version || echo "xlings direct call failed"
# Pre-seed the mcpp sandbox with the already-installed LLVM from
# the global xlings. xlings won't re-download if the package dir
# exists, but it may create an empty stub. Copy instead of junction
# to avoid Windows symlink permission issues on CI runners.
MCPP_XPKGS="$USERPROFILE/.mcpp/registry/data/xpkgs"
XLINGS_XPKGS="$USERPROFILE/.xlings/data/xpkgs"
if [ -d "$XLINGS_XPKGS/xim-x-llvm" ]; then
mkdir -p "$MCPP_XPKGS"
rm -rf "$MCPP_XPKGS/xim-x-llvm"
cp -r "$XLINGS_XPKGS/xim-x-llvm" "$MCPP_XPKGS/xim-x-llvm"
echo "Copied LLVM xpkg from global xlings"
ls "$MCPP_XPKGS/xim-x-llvm/20.1.7/bin/" | head -5
fi
"$MCPP_EXE" build
# Find the self-hosted binary
SELF_MCPP=$(find target -name "mcpp.exe" -path "*/bin/*" | head -1)
test -n "$SELF_MCPP" || {
echo "FAIL: self-host build did not produce mcpp.exe"
find target -name "*.exe" 2>/dev/null
exit 1
}
SELF_MCPP=$(cd "$(dirname "$SELF_MCPP")" && pwd)/$(basename "$SELF_MCPP")
echo "Self-hosted binary: $SELF_MCPP"
"$SELF_MCPP" --version
echo "MCPP_SELF=$SELF_MCPP" >> "$GITHUB_ENV"
- name: Package Windows release zip
id: package
shell: bash
run: |
VERSION=$(awk -F '"' '/^version[[:space:]]*=/{print $2; exit}' mcpp.toml)
PLAT="windows-x86_64"
WRAPPER="mcpp-${VERSION}-${PLAT}"
ZIPNAME="${WRAPPER}.zip"
echo "Packaging $ZIPNAME ..."
STAGING=$(mktemp -d)
mkdir -p "$STAGING/$WRAPPER/bin"
mkdir -p "$STAGING/$WRAPPER/registry/bin"
# Binary: use the self-hosted build
echo "Packaging binary: $MCPP_SELF"
cp "$MCPP_SELF" "$STAGING/$WRAPPER/bin/mcpp.exe"
# Launcher batch script (equivalent to the shell wrapper on Linux/macOS)
printf '@echo off\r\n"%%~dp0bin\\mcpp.exe" %%*\r\n' > "$STAGING/$WRAPPER/mcpp.bat"
# Metadata
cp README.md "$STAGING/$WRAPPER/" 2>/dev/null || true
cp LICENSE "$STAGING/$WRAPPER/" 2>/dev/null || true
# Bundle xlings
XLINGS_EXE="$USERPROFILE/.xlings/subos/default/bin/xlings.exe"
if [ -f "$XLINGS_EXE" ]; then
cp "$XLINGS_EXE" "$STAGING/$WRAPPER/registry/bin/xlings.exe"
echo "Bundled xlings.exe"
else
echo "::warning::xlings.exe not found at $XLINGS_EXE"
fi
# Create zip (use 7z to avoid backslash path issues from Compress-Archive)
mkdir -p dist
(cd "$STAGING" && 7z a -tzip "$ZIPNAME" "$WRAPPER")
cp "$STAGING/$ZIPNAME" "dist/$ZIPNAME"
# SHA256
(cd dist && sha256sum "$ZIPNAME" > "$ZIPNAME.sha256")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "zipname=$ZIPNAME" >> "$GITHUB_OUTPUT"
ls -la dist/
- name: Smoke-test the packaged zip
shell: bash
run: |
VERSION="${{ steps.package.outputs.version }}"
ZIPNAME="${{ steps.package.outputs.zipname }}"
WRAPPER="${ZIPNAME%.zip}"
SMOKE=$(mktemp -d)
(cd "$SMOKE" && unzip -q "$GITHUB_WORKSPACE/dist/$ZIPNAME")
echo "=== Layout ==="
find "$SMOKE/$WRAPPER" -type f
echo "=== Version check ==="
"$SMOKE/$WRAPPER/bin/mcpp.exe" --version
echo "=== xlings bundled ==="
test -f "$SMOKE/$WRAPPER/registry/bin/xlings.exe"
echo "=== Launcher ==="
test -f "$SMOKE/$WRAPPER/mcpp.bat"
echo "Smoke-test passed"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: mcpp-windows-x86_64
path: |
dist/*.zip
dist/*.sha256