fix(ci): use .xlings.json + dynamic package detection #86
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: | | |
| # Determine the base ref for comparison | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| BASE="${{ github.event.pull_request.base.sha }}" | |
| else | |
| BASE="${{ github.event.before }}" | |
| fi | |
| # Check if CI config itself changed — if so, build all | |
| 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 | |
| # Collect changed package names from packages/ and tests/ directories | |
| CHANGED_DIRS=$(git diff --name-only "$BASE" HEAD -- packages/ tests/ | \ | |
| sed -n 's|^packages/./\([^/]*\)/.*|\1|p; s|^tests/./\([^/]*\)/.*|\1|p' | \ | |
| sort -u) | |
| # Build JSON array of changed packages | |
| 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 | |
| - name: Configure (linux) | |
| if: runner.os == 'Linux' | |
| working-directory: tests | |
| run: | | |
| GCC_VERSION=$(python3 -c "import json; d=json.load(open('../.xlings.json')); print(d['workspace']['gcc']['linux'])") | |
| GCC_BIN="$HOME/.xlings/data/xpkgs/xim-x-gcc/${GCC_VERSION}/bin" | |
| echo "Using gcc ${GCC_VERSION} from ${GCC_BIN}" | |
| "${GCC_BIN}/gcc" --version | head -1 | |
| # xlings gcc needs system sysroot for headers and CRT files | |
| export LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:/usr/lib:/lib/x86_64-linux-gnu" | |
| export C_INCLUDE_PATH="/usr/include:/usr/include/x86_64-linux-gnu" | |
| export CPLUS_INCLUDE_PATH="/usr/include:/usr/include/x86_64-linux-gnu" | |
| export PATH="${GCC_BIN}:$PATH" | |
| xmake f -P . -y -vv | |
| - 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_BIN="$HOME/.xlings/data/xpkgs/xim-x-llvm/${LLVM_VERSION}/bin" | |
| echo "Using llvm ${LLVM_VERSION} from ${LLVM_BIN}" | |
| "${LLVM_BIN}/clang" --version | head -1 | |
| export PATH="${LLVM_BIN}:$PATH" | |
| xmake f -P . -y -vv --toolchain=llvm | |
| - name: Configure (windows) | |
| if: runner.os == 'Windows' | |
| working-directory: tests | |
| run: xmake f -P . -y -vv | |
| - 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: | | |
| # Put correct compiler on PATH and set system paths for xmake subprocesses | |
| if [ "$(uname)" = "Linux" ]; then | |
| GCC_VERSION=$(python3 -c "import json; d=json.load(open('../.xlings.json')); print(d['workspace']['gcc']['linux'])") | |
| export PATH="$HOME/.xlings/data/xpkgs/xim-x-gcc/${GCC_VERSION}/bin:$PATH" | |
| export LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:/usr/lib:/lib/x86_64-linux-gnu" | |
| export C_INCLUDE_PATH="/usr/include:/usr/include/x86_64-linux-gnu" | |
| export CPLUS_INCLUDE_PATH="/usr/include:/usr/include/x86_64-linux-gnu" | |
| 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 | |
| # Build and run each target | |
| for target in $TARGETS; do | |
| echo "=== Building $target ===" | |
| xmake build -P . -y "$target" | |
| # llmapi requires API key, only build don't run | |
| 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 | |
| - 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: | | |
| # Collect test targets | |
| $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 ', ')" | |
| } | |
| foreach ($target in $targets) { | |
| Write-Host "=== Building $target ===" | |
| xmake build -P . -y $target | |
| 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 | |
| } | |
| } |