fix(ci): use .xlings.json + dynamic package detection #88
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'packages/**' | |
| - 'tests/**' | |
| - '.github/**' | |
| - '.xlings.json' | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - 'packages/**' | |
| - 'tests/**' | |
| - '.github/**' | |
| - '.xlings.json' | |
| env: | |
| XLINGS_NON_INTERACTIVE: 1 | |
| jobs: | |
| detect-changes: | |
| runs-on: ubuntu-24.04 | |
| outputs: | |
| packages: ${{ steps.detect.outputs.packages }} | |
| build_all: ${{ steps.detect.outputs.build_all }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changed packages | |
| id: detect | |
| run: | | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| BASE="${{ github.event.pull_request.base.sha }}" | |
| else | |
| BASE="${{ github.event.before }}" | |
| fi | |
| CI_CHANGED=$(git diff --name-only "$BASE" HEAD -- .github/ .xlings.json || true) | |
| if [ -n "$CI_CHANGED" ]; then | |
| echo "build_all=true" >> "$GITHUB_OUTPUT" | |
| echo "CI config changed, will build all packages" | |
| else | |
| echo "build_all=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| CHANGED_DIRS=$(git diff --name-only "$BASE" HEAD -- packages/ tests/ | \ | |
| sed -n 's|^packages/./\([^/]*\)/.*|\1|p; s|^tests/./\([^/]*\)/.*|\1|p' | \ | |
| sort -u) | |
| JSON="[" | |
| FIRST=true | |
| for pkg in $CHANGED_DIRS; do | |
| if [ "$FIRST" = true ]; then FIRST=false; else JSON="$JSON,"; fi | |
| JSON="$JSON\"$pkg\"" | |
| done | |
| JSON="$JSON]" | |
| echo "packages=$JSON" >> "$GITHUB_OUTPUT" | |
| echo "Changed packages: $JSON" | |
| build: | |
| needs: detect-changes | |
| if: needs.detect-changes.outputs.build_all == 'true' || needs.detect-changes.outputs.packages != '[]' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { os: ubuntu-24.04, shell: bash } | |
| - { os: macos-15, shell: bash } | |
| - { os: windows-latest, shell: pwsh } | |
| runs-on: ${{ matrix.os }} | |
| name: build (${{ matrix.os }}) | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: ./.github/actions/setup-toolchain | |
| # --- Configure (same pattern as openxlings/xlings CI) --- | |
| - name: Configure (linux) | |
| if: runner.os == 'Linux' | |
| working-directory: tests | |
| run: | | |
| MUSL_GCC_VERSION=$(python3 -c "import json; d=json.load(open('../.xlings.json')); print(d['workspace']['musl-gcc']['linux'])") | |
| MUSL_SDK="$HOME/.xlings/data/xpkgs/xim-x-musl-gcc/${MUSL_GCC_VERSION}" | |
| echo "Using musl-gcc ${MUSL_GCC_VERSION} from ${MUSL_SDK}" | |
| "${MUSL_SDK}/bin/x86_64-linux-musl-gcc" --version | head -1 | |
| xmake f -P . -y -vv \ | |
| --sdk="${MUSL_SDK}" \ | |
| --cross=x86_64-linux-musl- \ | |
| --cc="${MUSL_SDK}/bin/x86_64-linux-musl-gcc" \ | |
| --cxx="${MUSL_SDK}/bin/x86_64-linux-musl-g++" | |
| - name: Configure (macos) | |
| if: runner.os == 'macOS' | |
| working-directory: tests | |
| run: | | |
| LLVM_VERSION=$(python3 -c "import json; d=json.load(open('../.xlings.json')); print(d['workspace']['llvm']['macosx'])") | |
| LLVM_SDK="$HOME/.xlings/data/xpkgs/xim-x-llvm/${LLVM_VERSION}" | |
| echo "Using llvm ${LLVM_VERSION} from ${LLVM_SDK}" | |
| "${LLVM_SDK}/bin/clang" --version | head -1 | |
| xmake f -P . -y -vv \ | |
| --toolchain=llvm \ | |
| --sdk="${LLVM_SDK}" \ | |
| --cc="${LLVM_SDK}/bin/clang" \ | |
| --cxx="${LLVM_SDK}/bin/clang++" | |
| - name: Configure (windows) | |
| if: runner.os == 'Windows' | |
| working-directory: tests | |
| run: xmake f -P . -y -vv | |
| # --- Build and test --- | |
| - name: Build and test (unix) | |
| if: runner.os != 'Windows' | |
| working-directory: tests | |
| env: | |
| BUILD_ALL: ${{ needs.detect-changes.outputs.build_all }} | |
| CHANGED_PACKAGES: ${{ needs.detect-changes.outputs.packages }} | |
| run: | | |
| # Same compiler PATH as configure step | |
| if [ "$(uname)" = "Linux" ]; then | |
| MUSL_GCC_VERSION=$(python3 -c "import json; d=json.load(open('../.xlings.json')); print(d['workspace']['musl-gcc']['linux'])") | |
| export PATH="$HOME/.xlings/data/xpkgs/xim-x-musl-gcc/${MUSL_GCC_VERSION}/bin:$PATH" | |
| else | |
| LLVM_VERSION=$(python3 -c "import json; d=json.load(open('../.xlings.json')); print(d['workspace']['llvm']['macosx'])") | |
| export PATH="$HOME/.xlings/data/xpkgs/xim-x-llvm/${LLVM_VERSION}/bin:$PATH" | |
| fi | |
| # Collect test targets to build | |
| TARGETS="" | |
| if [ "$BUILD_ALL" = "true" ]; then | |
| TARGETS=$(grep -r 'target("' */*/xmake.lua 2>/dev/null | sed 's/.*target("\([^"]*\)").*/\1/' | sort -u) | |
| echo "Building ALL targets: $TARGETS" | |
| else | |
| for pkg in $(echo "$CHANGED_PACKAGES" | tr -d '[]"' | tr ',' ' '); do | |
| TARGET="${pkg}_test" | |
| if grep -rq "target(\"$TARGET\")" */*/xmake.lua 2>/dev/null; then | |
| TARGETS="$TARGETS $TARGET" | |
| else | |
| echo "Warning: no test target '$TARGET' found for package '$pkg', skipping" | |
| fi | |
| done | |
| echo "Building changed targets: $TARGETS" | |
| fi | |
| FAILED="" | |
| for target in $TARGETS; do | |
| echo "=== Building $target ===" | |
| if ! xmake build -P . -y "$target"; then | |
| echo "FAILED to build $target" | |
| FAILED="$FAILED $target" | |
| continue | |
| fi | |
| if [ "$target" = "llmapi_test" ]; then | |
| echo "Skipping run for $target (requires API key)" | |
| continue | |
| fi | |
| echo "=== Running $target ===" | |
| if [ "$target" = "cmdline_test" ]; then | |
| xmake run -P . "$target" test_input | |
| else | |
| xmake run -P . "$target" | |
| fi | |
| done | |
| if [ -n "$FAILED" ]; then | |
| echo "::error::Failed targets:$FAILED" | |
| exit 1 | |
| fi | |
| - name: Build and test (windows) | |
| if: runner.os == 'Windows' | |
| working-directory: tests | |
| env: | |
| BUILD_ALL: ${{ needs.detect-changes.outputs.build_all }} | |
| CHANGED_PACKAGES: ${{ needs.detect-changes.outputs.packages }} | |
| run: | | |
| $targets = @() | |
| if ($env:BUILD_ALL -eq "true") { | |
| $targets = Get-ChildItem -Recurse -Filter "xmake.lua" -Path "*/*" | | |
| Select-String 'target\("([^"]+)"\)' | | |
| ForEach-Object { $_.Matches[0].Groups[1].Value } | | |
| Sort-Object -Unique | |
| Write-Host "Building ALL targets: $($targets -join ', ')" | |
| } else { | |
| $packages = $env:CHANGED_PACKAGES | ConvertFrom-Json | |
| foreach ($pkg in $packages) { | |
| $target = "${pkg}_test" | |
| $found = Get-ChildItem -Recurse -Filter "xmake.lua" -Path "*/*" | | |
| Select-String "target\(`"$target`"\)" -Quiet | |
| if ($found) { | |
| $targets += $target | |
| } else { | |
| Write-Host "Warning: no test target '$target' found for package '$pkg', skipping" | |
| } | |
| } | |
| Write-Host "Building changed targets: $($targets -join ', ')" | |
| } | |
| $failed = @() | |
| foreach ($target in $targets) { | |
| Write-Host "=== Building $target ===" | |
| xmake build -P . -y $target | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Host "FAILED to build $target" | |
| $failed += $target | |
| continue | |
| } | |
| if ($target -eq "llmapi_test") { | |
| Write-Host "Skipping run for $target (requires API key)" | |
| continue | |
| } | |
| Write-Host "=== Running $target ===" | |
| if ($target -eq "cmdline_test") { | |
| xmake run -P . $target test_input | |
| } else { | |
| xmake run -P . $target | |
| } | |
| } | |
| if ($failed.Count -gt 0) { | |
| Write-Error "Failed targets: $($failed -join ', ')" | |
| exit 1 | |
| } |