diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c028ef7..6581727 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,10 @@ on: branches: [main] pull_request: +env: + XLINGS_VERSION: 0.4.51 + MCPP_VERSION: 0.0.52 + jobs: build-linux-mcpp: name: build (linux x86_64, mcpp) @@ -13,8 +17,6 @@ jobs: - uses: actions/checkout@v4 - name: Install xlings - env: - XLINGS_VERSION: 0.4.31 run: | tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz" curl -fsSL -o "/tmp/${tarball}" \ @@ -26,14 +28,14 @@ jobs: - name: Refresh package index run: xlings update - - name: Install workspace tools (.xlings.json → mcpp 0.0.13) + - name: Install workspace tools (.xlings.json -> mcpp ${{ env.MCPP_VERSION }}) run: xlings install -y - name: Cache mcpp sandbox uses: actions/cache@v4 with: - path: ~/.xlings/data/xpkgs/xim-x-mcpp/0.0.13/registry - key: mcpp-sandbox-${{ runner.os }}-mcpp0.0.13 + path: ~/.xlings/data/xpkgs/xim-x-mcpp/${{ env.MCPP_VERSION }}/registry + key: mcpp-sandbox-${{ runner.os }}-mcpp${{ env.MCPP_VERSION }} - name: Build with mcpp run: mcpp build @@ -46,15 +48,18 @@ jobs: "$binary" --version build-macos: - runs-on: macos-15 + name: build (${{ matrix.os }}, mcpp) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-14, macos-15] steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Install Xlings + - name: Install xlings env: XLINGS_NON_INTERACTIVE: 1 - XLINGS_VERSION: 0.4.31 run: | TARBALL="xlings-${XLINGS_VERSION}-macosx-arm64.tar.gz" curl -fSL -o "$RUNNER_TEMP/$TARBALL" "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${TARBALL}" @@ -63,44 +68,117 @@ jobs: xattr -dr com.apple.quarantine "$EXTRACT_DIR" 2>/dev/null || true chmod +x "$EXTRACT_DIR/bin/xlings" "$EXTRACT_DIR/bin/xlings" self install - echo "PATH=$HOME/.xlings/subos/current/bin:$PATH" >> "$GITHUB_ENV" + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" - - name: Install Project Dependencies via Xlings - run: | - xlings install - clang --version + - name: Refresh package index + run: xlings update - - name: Configure xmake - run: | - LLVM_ROOT="$HOME/.xlings/data/xpkgs/xim-x-llvm" - LLVM_SDK=$(find "$LLVM_ROOT" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -1) - test -d "$LLVM_SDK" - "$LLVM_SDK/bin/clang++" --version - xmake f -m release --toolchain=llvm --sdk="$LLVM_SDK" -y -vvD + - name: Install workspace tools (.xlings.json -> mcpp ${{ env.MCPP_VERSION }}) + run: xlings install -y - - name: Build with xmake - run: xmake -a -j"$(sysctl -n hw.logicalcpu)" + - name: Build with mcpp + run: mcpp build - name: Verify d2x run: | - ./build/macosx/arm64/release/d2x --version + BIN=$(find target -name d2x -type f | head -1) + test -n "$BIN" || { echo "d2x binary not found"; find target -type f | head -20; exit 1; } + chmod +x "$BIN" + "$BIN" --version build-windows: + name: build (windows x86_64, mcpp) runs-on: windows-latest + defaults: + run: + shell: bash + env: + XLINGS_NON_INTERACTIVE: 1 steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - - name: Setup xmake - uses: xmake-io/github-action-setup-xmake@v1 - with: - xmake-version: latest + # Everything in one bash step so the in-process PATH is used directly + # (avoids cross-step PATH translation issues for the msys/Windows mix). + - name: Install xlings and build with mcpp + run: | + set -e + ZIP="xlings-${XLINGS_VERSION}-windows-x86_64.zip" + curl -fSL -o "$RUNNER_TEMP/$ZIP" "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${ZIP}" + unzip -q "$RUNNER_TEMP/$ZIP" -d "$RUNNER_TEMP/xl" + XL=$(find "$RUNNER_TEMP/xl" -name 'xlings.exe' | head -1) + echo "xlings: $XL" + "$XL" self install + export PATH="$HOME/.xlings/subos/current/bin:$PATH" + xlings update + xlings install -y + mcpp build + D2X=$(find target -name 'd2x.exe' -type f | head -1) + test -n "$D2X" || { echo "d2x.exe not found"; find target -type f | head -20; exit 1; } + "$D2X" --version + + # Smoke test: a real `d2x checker` run against the d2mcpp course must reach + # and report the FIRST exercise's build error within a bounded window, rather + # than hanging on the init log (issue #24). The checker waits for file edits + # forever, so a timeout kill is the expected, healthy outcome. + checker-smoke: + name: checker smoke (linux) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - - name: Build with xmake + - name: Install xlings run: | - xmake f -m release -y - xmake -j$env:NUMBER_OF_PROCESSORS + tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz" + curl -fsSL -o "/tmp/${tarball}" \ + "https://github.com/d2learn/xlings/releases/download/v${XLINGS_VERSION}/${tarball}" + tar -xzf "/tmp/${tarball}" -C /tmp + "/tmp/xlings-${XLINGS_VERSION}-linux-x86_64/subos/default/bin/xlings" self install + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" - - name: Verify d2x + - name: Refresh package index + run: xlings update + + - name: Build d2x with mcpp + run: | + xlings install -y + mcpp build + + # Use a real xmake (not the xlings `xmake` shim): d2mcpp's .xlings.json + # pins xmake 3.0.7, which is gone from the registry, so the shim would + # fail to resolve. A standalone xmake ignores .xlings.json. Put it ahead + # of the xlings bin on PATH. + - name: Install xmake + run: | + curl -fsSL https://xmake.io/shget.text | bash + source ~/.xmake/profile 2>/dev/null || true + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + "$HOME/.local/bin/xmake" --version + + - name: Checkout d2mcpp course + uses: actions/checkout@v4 + with: + repository: mcpp-community/d2mcpp + path: d2mcpp + + - name: Run d2x checker (must reach first exercise, not hang on load) run: | - build\windows\x64\release\d2x.exe --version + D2X="$PWD/$(find target -name d2x -type f | head -1)" + test -x "$D2X" || chmod +x "$D2X" + cd d2mcpp + # Force the print UI so output is plain text in a non-TTY runner. + sed -i 's/"ui_backend": *"tui"/"ui_backend": "print"/' .d2x.json || true + xmake f -y + set +e + timeout 120 "$D2X" checker --ui print --lang en > checker.out 2>&1 + code=$? + set -e + echo "checker exit=$code (124 = killed by timeout while waiting for edits = expected)" + echo "------------------ checker output (tail) ------------------" + tail -n 40 checker.out || true + echo "-----------------------------------------------------------" + if grep -q "hello-mcpp" checker.out && grep -qiE "error" checker.out; then + echo "OK: checker reached and reported the first exercise (not stuck on the loading log)" + else + echo "FAIL: checker produced no exercise build output within the window (stuck on load?)" + exit 1 + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca1a6ab..eb1ef5c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ on: type: string env: - XLINGS_VERSION: v0.4.0 + XLINGS_VERSION: v0.4.51 jobs: build-linux: @@ -36,19 +36,29 @@ jobs: "$EXTRACT_DIR/bin/xlings" self install echo "PATH=$HOME/.xlings/subos/current/bin:$PATH" >> "$GITHUB_ENV" - - name: Install Project Dependencies via Xlings - run: | - xlings install + - name: Refresh package index + run: xlings update + + - name: Install workspace tools (.xlings.json -> mcpp) + run: xlings install -y + + - name: Build with mcpp (static) + run: mcpp build --static - - name: Build with xmake + - name: Locate & verify binary run: | - xmake f -m release -y - xmake -j$(nproc) + BIN=$(find target -name d2x -type f | head -1) + test -n "$BIN" || { echo "d2x binary not found"; find target -type f | head; exit 1; } + chmod +x "$BIN" + ver=$("$BIN" --version) + echo "built d2x version: $ver (release input: ${{ inputs.version }})" + test "$ver" = "${{ inputs.version }}" || { echo "ERROR: binary version ($ver) != release version (${{ inputs.version }})"; exit 1; } + echo "BIN=$BIN" >> "$GITHUB_ENV" - name: Create release package run: | mkdir -p d2x-${{ inputs.version }}-linux-x86_64 - cp build/linux/x86_64/release/d2x d2x-${{ inputs.version }}-linux-x86_64/ + cp "$BIN" d2x-${{ inputs.version }}-linux-x86_64/ tar -czf d2x-${{ inputs.version }}-linux-x86_64.tar.gz d2x-${{ inputs.version }}-linux-x86_64 - name: Upload artifact @@ -58,7 +68,9 @@ jobs: path: d2x-${{ inputs.version }}-linux-x86_64.tar.gz build-macos: - runs-on: macos-15 + # Built on the oldest supported macOS so the artifact also runs on newer + # releases. mcpp manages its own toolchain, so no SDK pinning is needed. + runs-on: macos-14 steps: - name: Checkout code uses: actions/checkout@v4 @@ -77,31 +89,29 @@ jobs: "$EXTRACT_DIR/bin/xlings" self install echo "PATH=$HOME/.xlings/subos/current/bin:$PATH" >> "$GITHUB_ENV" - - name: Install Project Dependencies via Xlings - run: | - xlings install - clang --version + - name: Refresh package index + run: xlings update - - name: Build with xmake - run: | - export LLVM_PREFIX=$HOME/.xlings/data/xpkgs/xim-x-llvm/20.1.7 - xmake f -m release --toolchain=llvm --sdk=$LLVM_PREFIX -vv -y - xmake -j$(nproc) + - name: Install workspace tools (.xlings.json -> mcpp) + run: xlings install -y + + - name: Build with mcpp + run: mcpp build - - name: Verify no LLVM runtime dependency + - name: Locate & verify binary run: | - BIN=build/macosx/arm64/release/d2x - if otool -L "$BIN" | grep -q "llvm"; then - otool -L "$BIN" - echo "FAIL: binary links against LLVM dylib" - exit 1 - fi - echo "OK: binary has no LLVM runtime dependency" + BIN=$(find target -name d2x -type f | head -1) + test -n "$BIN" || { echo "d2x binary not found"; find target -type f | head; exit 1; } + chmod +x "$BIN" + ver=$("$BIN" --version) + echo "built d2x version: $ver (release input: ${{ inputs.version }})" + test "$ver" = "${{ inputs.version }}" || { echo "ERROR: binary version ($ver) != release version (${{ inputs.version }})"; exit 1; } + echo "BIN=$BIN" >> "$GITHUB_ENV" - name: Create release package run: | mkdir -p d2x-${{ inputs.version }}-macosx-arm64 - cp build/macosx/arm64/release/d2x d2x-${{ inputs.version }}-macosx-arm64/ + cp "$BIN" d2x-${{ inputs.version }}-macosx-arm64/ tar -czf d2x-${{ inputs.version }}-macosx-arm64.tar.gz d2x-${{ inputs.version }}-macosx-arm64 - name: Upload artifact @@ -112,25 +122,39 @@ jobs: build-windows: runs-on: windows-latest + defaults: + run: + shell: bash + env: + XLINGS_NON_INTERACTIVE: 1 steps: - name: Checkout code uses: actions/checkout@v4 - - name: Setup xmake - uses: xmake-io/github-action-setup-xmake@v1 - with: - xmake-version: latest - - - name: Build with xmake + - name: Install xlings and build with mcpp run: | - xmake f -m release -y - xmake -j$env:NUMBER_OF_PROCESSORS - - - name: Create release package + set -e + VERSION_NUM="${XLINGS_VERSION#v}" + ZIP="xlings-${VERSION_NUM}-windows-x86_64.zip" + curl -fSL -o "$RUNNER_TEMP/$ZIP" "https://github.com/d2learn/xlings/releases/download/${XLINGS_VERSION}/${ZIP}" + unzip -q "$RUNNER_TEMP/$ZIP" -d "$RUNNER_TEMP/xl" + XL=$(find "$RUNNER_TEMP/xl" -name 'xlings.exe' | head -1) + "$XL" self install + export PATH="$HOME/.xlings/subos/current/bin:$PATH" + xlings update + xlings install -y + mcpp build + D2X=$(find target -name 'd2x.exe' -type f | head -1) + test -n "$D2X" || { echo "d2x.exe not found"; find target -type f | head; exit 1; } + ver=$("$D2X" --version) + echo "built d2x version: $ver (release input: ${{ inputs.version }})" + test "$ver" = "${{ inputs.version }}" || { echo "ERROR: binary version ($ver) != release version (${{ inputs.version }})"; exit 1; } + mkdir -p "d2x-${{ inputs.version }}-windows-x86_64" + cp "$D2X" "d2x-${{ inputs.version }}-windows-x86_64/" + + - name: Zip release package shell: pwsh run: | - New-Item -ItemType Directory -Path "d2x-${{ inputs.version }}-windows-x86_64" -Force - Copy-Item "build\windows\x64\release\d2x.exe" -Destination "d2x-${{ inputs.version }}-windows-x86_64\" Compress-Archive -Path "d2x-${{ inputs.version }}-windows-x86_64" -DestinationPath "d2x-${{ inputs.version }}-windows-x86_64.zip" - name: Upload artifact diff --git a/.gitignore b/.gitignore index 700d111..75cc0d6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build target d2x.zip +mcpp.lock +compile_commands.json diff --git a/.xlings.json b/.xlings.json index 1eb6d31..e88cf7b 100644 --- a/.xlings.json +++ b/.xlings.json @@ -1,8 +1,5 @@ { "workspace": { - "mcpp": { "linux": "0.0.13" }, - "xmake": "3.0.7", - "gcc": { "linux": "15.1.0" }, - "llvm": { "macosx": "20" } + "mcpp": { "linux": "0.0.52", "macosx": "0.0.52", "windows": "0.0.52" } } } diff --git a/README.md b/README.md index 13c57d4..1e9e81f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ | --- | ```cpp -d2x version: 0.1.4 +d2x version: 0.1.5 Usage: $ d2x [command] [target] [options] diff --git a/mcpp.toml b/mcpp.toml index 966fdb6..b3adb9e 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,6 +1,6 @@ [package] name = "d2x" -version = "0.1.2" +version = "0.1.5" description = "AI-powered development assistant for C++ projects" license = "Apache-2.0" repo = "https://github.com/d2learn/d2x" diff --git a/src/cmdprocessor.cppm b/src/cmdprocessor.cppm index 0a987de..ee8fbc9 100644 --- a/src/cmdprocessor.cppm +++ b/src/cmdprocessor.cppm @@ -91,7 +91,14 @@ export int run(int argc, char* argv[]) { .action([](const cmdline::ParsedArgs& a) { apply_global_options(a); auto bookdir = std::filesystem::path(platform::get_rundir()) / "book"; - if (Config::lang() == "en") bookdir /= "en"; + // Resolve language sub-book generically (issue #8): any language + // shipped by the target project as book/ is supported, not + // just zh/en. Falls back to the default book/ when absent. + const auto& lang = Config::lang(); + if (!lang.empty() && lang != "auto") { + auto langdir = bookdir / lang; + if (std::filesystem::exists(langdir)) bookdir = langdir; + } std::println("Opening book: {}", bookdir.string()); if (std::filesystem::exists(bookdir)) { platform::run_command_capture("xlings install mdbook -y"); diff --git a/src/config.cppm b/src/config.cppm index 472c75d..8e44234 100644 --- a/src/config.cppm +++ b/src/config.cppm @@ -17,7 +17,7 @@ export struct EnvVars; export class Config; struct Info { - static constexpr std::string_view VERSION = "0.1.4"; + static constexpr std::string_view VERSION = "0.1.5"; static constexpr std::string_view REPO = "https://github.com/d2learn/d2x"; }; diff --git a/src/platform/windows.cppm b/src/platform/windows.cppm index e39bf33..e17f124 100644 --- a/src/platform/windows.cppm +++ b/src/platform/windows.cppm @@ -15,7 +15,8 @@ namespace platform_impl { export constexpr std::string_view XLINGS_INSTALL_CMD = "powershell -Command \"irm https://d2learn.org/xlings-install.ps1.txt | iex\""; export std::pair run_command_capture(const std::string& cmd) { - FILE* pipe = _popen(cmd.c_str(), "r"); + std::string full = cmd + " 2>&1"; // redirect stderr to stdout (match linux/macos) + FILE* pipe = _popen(full.c_str(), "r"); if (!pipe) { return {-1, std::string{}}; } diff --git a/xmake.lua b/xmake.lua deleted file mode 100644 index bef1f14..0000000 --- a/xmake.lua +++ /dev/null @@ -1,27 +0,0 @@ -add_rules("mode.debug", "mode.release") - -set_languages("c++23") - -add_repositories("mcpplibs-index https://github.com/mcpplibs/mcpplibs-index.git") - -add_requires("llmapi 0.2.5") -add_requires("mcpplibs-tinyhttps 0.2.2") -add_requires("mbedtls v3.6.1") -add_requires("cmdline 0.0.2") -add_requires("ftxui 6.1.9") - -target("d2x") - set_kind("binary") - - add_files("src/main.cpp") - -- add common module interface units - add_files("src/**.cppm") - add_packages("ftxui", "llmapi", "mcpplibs-tinyhttps", "mbedtls", "cmdline") - set_policy("build.c++.modules", true) - - -- platform specific settings - if is_plat("macosx") then - set_toolchains("llvm") - elseif is_plat("linux") then - add_ldflags("-static", {force = true}) - end