diff --git a/.github/actions/setup-toolchain/action.yml b/.github/actions/setup-toolchain/action.yml index ac56262..f55100e 100644 --- a/.github/actions/setup-toolchain/action.yml +++ b/.github/actions/setup-toolchain/action.yml @@ -1,61 +1,43 @@ name: Setup C++23 Toolchain -description: Install xlings and C++23 toolchain for the current platform +description: Install xlings and project dependencies declared in .xlings.json runs: using: composite steps: - - name: Setup (linux) - if: runner.os == 'Linux' - shell: bash - run: | - LATEST_VERSION=$(gh release view --repo d2learn/xlings --json tagName -q '.tagName') - VERSION_NUM=${LATEST_VERSION#v} - TARBALL="xlings-${VERSION_NUM}-linux-x86_64.tar.gz" - gh release download "$LATEST_VERSION" --repo d2learn/xlings --pattern "$TARBALL" --dir /tmp - mkdir -p "$HOME/.xlings" - tar -xzf "/tmp/$TARBALL" -C "$HOME/.xlings" --strip-components=1 - "$HOME/.xlings/bin/xlings" self install - env: - GH_TOKEN: ${{ github.token }} - - - name: Setup (macos) - if: runner.os == 'macOS' + - name: Install xlings (unix) + if: runner.os != 'Windows' shell: bash run: | - LATEST_VERSION=$(gh release view --repo d2learn/xlings --json tagName -q '.tagName') - VERSION_NUM=${LATEST_VERSION#v} - ARCH=$(uname -m) - TARBALL="xlings-${VERSION_NUM}-macosx-${ARCH}.tar.gz" - gh release download "$LATEST_VERSION" --repo d2learn/xlings --pattern "$TARBALL" --dir /tmp - mkdir -p "$HOME/.xlings" - tar -xzf "/tmp/$TARBALL" -C "$HOME/.xlings" --strip-components=1 - xattr -dr com.apple.quarantine "$HOME/.xlings" 2>/dev/null || true - "$HOME/.xlings/bin/xlings" self install - env: - GH_TOKEN: ${{ github.token }} + curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash + echo "XLINGS_HOME=$HOME/.xlings" >> "$GITHUB_ENV" + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + echo "$HOME/.xlings/bin" >> "$GITHUB_PATH" - - name: Setup (windows) + - name: Install xlings (windows) if: runner.os == 'Windows' shell: pwsh - run: irm https://raw.githubusercontent.com/d2learn/xlings/refs/heads/main/tools/other/quick_install.ps1 | iex + run: | + irm https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.ps1 | iex + "XLINGS_HOME=$env:USERPROFILE\.xlings" >> $env:GITHUB_ENV + "$env:USERPROFILE\.xlings\subos\current\bin" >> $env:GITHUB_PATH + "$env:USERPROFILE\.xlings\bin" >> $env:GITHUB_PATH - - name: Refresh package index (unix) + - name: Refresh package index if: runner.os != 'Windows' shell: bash - run: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xlings update + run: xlings update - name: Refresh package index (windows) if: runner.os == 'Windows' shell: pwsh - run: | - $env:PATH = "$env:USERPROFILE\.xlings\subos\current\bin;$env:PATH" - xlings update + run: xlings update - - name: Install toolchain (unix) + - name: Install dependencies if: runner.os != 'Windows' shell: bash - run: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xlings install -y + run: xlings install -y + + - name: Install dependencies (windows) + if: runner.os == 'Windows' + shell: pwsh + run: xlings install -y diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b771e17..cc4fcc0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,13 +6,15 @@ on: paths: - 'packages/**' - 'tests/**' - - '.github/workflows/ci.yml' + - '.github/**' + - '.xlings.json' pull_request: branches: [main] paths: - 'packages/**' - 'tests/**' - - '.github/workflows/ci.yml' + - '.github/**' + - '.xlings.json' env: XLINGS_NON_INTERACTIVE: 1 @@ -21,46 +23,48 @@ jobs: detect-changes: runs-on: ubuntu-24.04 outputs: - templates: ${{ steps.filter.outputs.templates }} - cmdline: ${{ steps.filter.outputs.cmdline }} - llmapi: ${{ steps.filter.outputs.llmapi }} - lua: ${{ steps.filter.outputs.lua }} - xpkg: ${{ steps.filter.outputs.xpkg }} + packages: ${{ steps.detect.outputs.packages }} + build_all: ${{ steps.detect.outputs.build_all }} steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 - id: filter with: - filters: | - templates: - - 'packages/t/templates/**' - - 'tests/t/templates/**' - - '.github/workflows/ci.yml' - cmdline: - - 'packages/c/cmdline/**' - - 'tests/c/cmdline/**' - - '.github/workflows/ci.yml' - llmapi: - - 'packages/l/llmapi/**' - - 'tests/l/llmapi/**' - - '.github/workflows/ci.yml' - lua: - - 'packages/m/mcpplibs-capi-lua/**' - - 'tests/l/lua/**' - - '.github/workflows/ci.yml' - xpkg: - - 'packages/m/mcpplibs-xpkg/**' - - 'tests/m/mcpplibs-xpkg/**' - - '.github/workflows/ci.yml' + 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.templates == 'true' || - needs.detect-changes.outputs.cmdline == 'true' || - needs.detect-changes.outputs.llmapi == 'true' || - needs.detect-changes.outputs.lua == 'true' || - needs.detect-changes.outputs.xpkg == 'true' + if: needs.detect-changes.outputs.build_all == 'true' || needs.detect-changes.outputs.packages != '[]' strategy: fail-fast: false matrix: @@ -78,97 +82,128 @@ jobs: if: runner.os == 'Linux' working-directory: tests run: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xmake f -P . -y + MUSL_VER=$(python3 -c "import json; print(json.load(open('../.xlings.json'))['workspace']['musl-gcc']['linux'])") + MUSL_SDK="$HOME/.xlings/data/xpkgs/xim-x-musl-gcc/${MUSL_VER}" + 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: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xmake f -P . -y --toolchain=llvm + LLVM_VER=$(python3 -c "import json; print(json.load(open('../.xlings.json'))['workspace']['llvm']['macosx'])") + LLVM_SDK="$HOME/.xlings/data/xpkgs/xim-x-llvm/${LLVM_VER}" + xmake f -P . -y -vv --toolchain=llvm --sdk="${LLVM_SDK}" - name: Configure (windows) if: runner.os == 'Windows' working-directory: tests - run: | - $env:PATH = "$env:USERPROFILE\.xlings\subos\current\bin;$env:PATH" - xmake f -P . -y + run: xmake f -P . -y -vv - # templates - - name: templates (unix) - if: runner.os != 'Windows' && needs.detect-changes.outputs.templates == 'true' + - 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: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xmake build -P . -y templates_test - xmake run -P . templates_test - - name: templates (windows) - if: runner.os == 'Windows' && needs.detect-changes.outputs.templates == 'true' - working-directory: tests - run: | - $env:PATH = "$env:USERPROFILE\.xlings\subos\current\bin;$env:PATH" - xmake build -P . -y templates_test - xmake run -P . templates_test + 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 - # cmdline - - name: cmdline (unix) - if: runner.os != 'Windows' && needs.detect-changes.outputs.cmdline == 'true' - working-directory: tests - run: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xmake build -P . -y cmdline_test - xmake run -P . cmdline_test test_input - - name: cmdline (windows) - if: runner.os == 'Windows' && needs.detect-changes.outputs.cmdline == 'true' - working-directory: tests - run: | - $env:PATH = "$env:USERPROFILE\.xlings\subos\current\bin;$env:PATH" - xmake build -P . -y cmdline_test - xmake run -P . cmdline_test test_input + # llmapi_test: skip entirely — requires API key to run, and xmake's + # C++23 module system can't resolve transitive module deps from + # installed packages. Tested in llmapi's own repo CI instead. + TARGETS=$(echo "$TARGETS" | tr ' ' '\n' | grep -v llmapi_test | tr '\n' ' ') - # llmapi (build only, needs API key to run) - - name: llmapi (unix) - if: runner.os != 'Windows' && needs.detect-changes.outputs.llmapi == 'true' - working-directory: tests - run: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xmake build -P . -y llmapi_test - - name: llmapi (windows) - if: runner.os == 'Windows' && needs.detect-changes.outputs.llmapi == 'true' - working-directory: tests - run: | - $env:PATH = "$env:USERPROFILE\.xlings\subos\current\bin;$env:PATH" - xmake build -P . -y llmapi_test + 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 - # lua - - name: lua (unix) - if: runner.os != 'Windows' && needs.detect-changes.outputs.lua == 'true' - working-directory: tests - run: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xmake build -P . -y lua_test - xmake run -P . lua_test - - name: lua (windows) - if: runner.os == 'Windows' && needs.detect-changes.outputs.lua == 'true' - working-directory: tests - run: | - $env:PATH = "$env:USERPROFILE\.xlings\subos\current\bin;$env:PATH" - xmake build -P . -y lua_test - xmake run -P . lua_test + echo "=== Running $target ===" + if [ "$target" = "cmdline_test" ]; then + xmake run -P . "$target" test_input + else + xmake run -P . "$target" + fi + done - # mcpplibs-xpkg - - name: mcpplibs-xpkg (unix) - if: runner.os != 'Windows' && needs.detect-changes.outputs.xpkg == 'true' - working-directory: tests - run: | - export PATH="$HOME/.xlings/subos/current/bin:$PATH" - xmake build -P . -y mcpplibs-xpkg_test - xmake run -P . mcpplibs-xpkg_test - - name: mcpplibs-xpkg (windows) - if: runner.os == 'Windows' && needs.detect-changes.outputs.xpkg == 'true' + 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: | - $env:PATH = "$env:USERPROFILE\.xlings\subos\current\bin;$env:PATH" - xmake build -P . -y mcpplibs-xpkg_test - xmake run -P . mcpplibs-xpkg_test + $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 ', ')" + } + + # Skip llmapi_test (same reason as unix) + $targets = $targets | Where-Object { $_ -ne "llmapi_test" } + + $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 + } + + 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-Host "::error::Failed targets: $($failed -join ', ')" + exit 1 + } diff --git a/.xlings.json b/.xlings.json new file mode 100644 index 0000000..4494b57 --- /dev/null +++ b/.xlings.json @@ -0,0 +1,7 @@ +{ + "workspace": { + "xmake": "3.0.7", + "musl-gcc": { "linux": "15.1.0" }, + "llvm": { "macosx": "20.1.7" } + } +} diff --git a/packages/l/llmapi/xmake.lua b/packages/l/llmapi/xmake.lua index 8b3bcae..163d15a 100644 --- a/packages/l/llmapi/xmake.lua +++ b/packages/l/llmapi/xmake.lua @@ -19,13 +19,24 @@ package("llmapi") add_versions("0.0.1", "174f86d3afdf48a57ad1cc9688718d1f1100a78a7e56686c823c573c3ccf99f4") add_includedirs("include") - add_deps("mcpplibs-tinyhttps 0.2.2") on_load(function (package) package:add("links", "llmapi") + if package:version():ge("0.2.0") then + -- 0.2.0+ extracted tinyhttps into a separate package + package:add("deps", "mcpplibs-tinyhttps >=0.2.0") + elseif package:version():ge("0.1.0") then + -- 0.1.0 bundles tinyhttps inline, but still needs mbedtls + package:add("deps", "mbedtls >=3.6.1") + end end) on_install(function (package) local configs = {} - import("package.tools.xmake").install(package, configs, {target = "llmapi"}) + if package:version():ge("0.2.0") then + import("package.tools.xmake").install(package, configs, {target = "llmapi"}) + else + -- 0.1.0 and earlier: install all targets (llmapi bundles tinyhttps) + import("package.tools.xmake").install(package, configs) + end end) diff --git a/packages/m/mcpplibs-tinyhttps/xmake.lua b/packages/m/mcpplibs-tinyhttps/xmake.lua index f8155b9..43bd614 100644 --- a/packages/m/mcpplibs-tinyhttps/xmake.lua +++ b/packages/m/mcpplibs-tinyhttps/xmake.lua @@ -14,13 +14,12 @@ package("mcpplibs-tinyhttps") add_versions("0.2.0", "81dab607227f353fa83068d4fee47b6877ceff891719a60a9cd75eaf827fab44") add_versions("0.1.0", "af7daa6a63f264070a1ac8fe42725713ba7ea54e58f1e8b8e190d1b4c58a0896") - add_deps("mbedtls 3.6.1") - on_load(function (package) package:add("links", "tinyhttps") + package:add("deps", "mbedtls >=3.6.1") end) on_install(function (package) local configs = {} - import("package.tools.xmake").install(package, configs) + import("package.tools.xmake").install(package, configs, {target = "tinyhttps"}) end) diff --git a/tests/l/llmapi/xmake.lua b/tests/l/llmapi/xmake.lua index 8e1dfac..19f822a 100644 --- a/tests/l/llmapi/xmake.lua +++ b/tests/l/llmapi/xmake.lua @@ -1,4 +1,4 @@ -add_requires("llmapi 0.2.5") +add_requires("llmapi 0.2.6") target("llmapi_test") set_kind("binary") @@ -6,3 +6,6 @@ target("llmapi_test") add_files("main.cpp") add_packages("llmapi") set_policy("build.c++.modules", true) + -- xmake C++23 module system requires explicit transitive deps + -- when the dep's modules import other packages' modules + add_packages("mcpplibs-tinyhttps", "mbedtls")