From 9b945118c24780b06e2d89b1607f66fe30c47348 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 13:07:13 +0800 Subject: [PATCH 01/10] ci(release): three-tier release pipeline, consolidated workflows --- .github/workflows/check-format.yml | 36 ------- .github/workflows/cross-pair.yml | 1 - .github/workflows/deploy-docs.yml | 17 ---- .github/workflows/main.yml | 96 ++++++++++++++++--- .github/workflows/native-test.yml | 1 - .github/workflows/nightly.yml | 135 +++++++++++++++++++++++++++ .github/workflows/publish-clice.yml | 16 +++- .github/workflows/publish-vscode.yml | 89 +++++++++++++----- .github/workflows/release-check.yml | 26 ------ cmake/release.cmake | 3 +- 10 files changed, 297 insertions(+), 123 deletions(-) delete mode 100644 .github/workflows/check-format.yml delete mode 100644 .github/workflows/deploy-docs.yml create mode 100644 .github/workflows/nightly.yml delete mode 100644 .github/workflows/release-check.yml diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml deleted file mode 100644 index 59b8fb5ad..000000000 --- a/.github/workflows/check-format.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: format - -on: - workflow_call: - -jobs: - check-format: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v7 - with: - persist-credentials: false - - - uses: ./.github/actions/setup-pixi - with: - environments: format - - - name: Run formatter - run: pixi run format - continue-on-error: true - - - name: Auto correct - uses: huacnlee/autocorrect-action@v2.5.4 - with: - args: --lint ./docs - continue-on-error: true - - - name: Check diff - run: | - if ! git diff --quiet; then - echo "::error::Formatting changes detected. Please run 'pixi run format' and commit the result." - git --no-pager diff --stat - git --no-pager diff - exit 1 - fi diff --git a/.github/workflows/cross-pair.yml b/.github/workflows/cross-pair.yml index 6a020f19b..90da02cfc 100644 --- a/.github/workflows/cross-pair.yml +++ b/.github/workflows/cross-pair.yml @@ -21,7 +21,6 @@ env: CCACHE_DIR: ${{ github.workspace }}/.cache/ccache SCCACHE_DIR: ${{ github.workspace }}/.cache/sccache CCACHE_BASEDIR: ${{ github.workspace }} - SCCACHE_BASEDIRS: ${{ github.workspace }} CCACHE_COMPILERCHECK: content CCACHE_MAXSIZE: 2G SCCACHE_CACHE_SIZE: 2G diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml deleted file mode 100644 index 42bfe562b..000000000 --- a/.github/workflows/deploy-docs.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: publish docs - -on: - workflow_call: - -jobs: - publish: - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: clice-io/docs@main - with: - project: clice - token: ${{ secrets.PUBLISH_DOCS }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 522091f4a..2bdac3286 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,6 +6,8 @@ on: tags: ["v*"] pull_request: branches: [main] + # Manual full release dry run (packages + bundled extensions) from any ref. + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -13,7 +15,7 @@ concurrency: jobs: changes: - if: ${{ !startsWith(github.ref, 'refs/tags/') }} + if: ${{ !startsWith(github.ref, 'refs/tags/') && github.event_name != 'workflow_dispatch' }} runs-on: ubuntu-latest permissions: pull-requests: read @@ -24,6 +26,7 @@ jobs: vscode: ${{ steps.filter.outputs.vscode }} zed: ${{ steps.filter.outputs.zed }} cmake: ${{ steps.filter.outputs.cmake }} + release: ${{ steps.filter.outputs.release }} steps: - uses: actions/checkout@v7 @@ -48,6 +51,12 @@ jobs: zed: - 'editors/zed/**' - '.github/workflows/main.yml' + release: + - '.github/workflows/publish-clice.yml' + - '.github/workflows/publish-vscode.yml' + - 'CMakeLists.txt' + - 'cmake/release.cmake' + - 'cmake/archive.cmake' cmake: - 'CMakeLists.txt' - 'src/**' @@ -65,7 +74,7 @@ jobs: - '.github/workflows/editor-test.yml' conventional-commit: - if: ${{ !startsWith(github.ref, 'refs/tags/') }} + if: ${{ !startsWith(github.ref, 'refs/tags/') && github.event_name != 'workflow_dispatch' }} runs-on: ubuntu-latest steps: - name: Check conventional commit format @@ -92,20 +101,67 @@ jobs: format: needs: changes if: ${{ needs.changes.outputs.format == 'true' }} - uses: ./.github/workflows/check-format.yml + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v7 + with: + persist-credentials: false + + - uses: ./.github/actions/setup-pixi + with: + environments: format + + - name: Run formatter + run: pixi run format + continue-on-error: true + + - name: Auto correct + uses: huacnlee/autocorrect-action@v2.5.4 + with: + args: --lint ./docs + continue-on-error: true + + - name: Check diff + run: | + if ! git diff --quiet; then + echo "::error::Formatting changes detected. Please run 'pixi run format' and commit the result." + git --no-pager diff --stat + git --no-pager diff + exit 1 + fi deploy: needs: changes - if: ${{ needs.changes.outputs.docs == 'true' }} + if: ${{ needs.changes.outputs.docs == 'true' && github.ref == 'refs/heads/main' }} permissions: contents: write - uses: ./.github/workflows/deploy-docs.yml - secrets: inherit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: clice-io/docs@main + with: + project: clice + token: ${{ secrets.PUBLISH_DOCS }} + + # Release-pipeline dry run: exercises all six packages and all six bundled + # extension builds without publishing (upload steps are tag-gated). Runs on + # PRs that touch release plumbing, and on workflow_dispatch from any ref. + release-check-clice: + needs: changes + if: >- + ${{ !cancelled() && (github.event_name == 'workflow_dispatch' || + needs.changes.outputs.release == 'true') }} + uses: ./.github/workflows/publish-clice.yml - # clice: - # needs: changes - # if: ${{ needs.changes.outputs.clice == 'true' }} - # uses: ./.github/workflows/publish-clice.yml + release-check-vscode: + needs: release-check-clice + if: ${{ !cancelled() && needs.release-check-clice.result == 'success' }} + uses: ./.github/workflows/publish-vscode.yml + with: + source: package vscode: needs: changes @@ -143,6 +199,15 @@ jobs: if: ${{ needs.changes.outputs.cmake == 'true' }} uses: ./.github/workflows/editor-test.yml + # Instant builds: repackage the test-suite binaries as installable vsix + # artifacts so a fix can be tried the moment CI is green. + instant-vscode: + needs: [changes, native-test, cross-test] + if: ${{ needs.changes.outputs.cmake == 'true' }} + uses: ./.github/workflows/publish-vscode.yml + with: + source: build + release-clice: permissions: contents: write @@ -157,7 +222,7 @@ jobs: needs: release-clice uses: ./.github/workflows/publish-vscode.yml with: - bundle: true + source: package secrets: inherit checks-passed: @@ -166,12 +231,14 @@ jobs: - conventional-commit - format - deploy - # - clice - vscode - zed - native-test - cross-test - editor-test + - instant-vscode + - release-check-clice + - release-check-vscode runs-on: ubuntu-latest steps: @@ -179,6 +246,7 @@ jobs: uses: re-actors/alls-green@v1.2.2 with: allowed-skips: >- - conventional-commit,format,deploy,clice,vscode,zed, - native-test,cross-test,editor-test + conventional-commit,format,deploy,vscode,zed, + native-test,cross-test,editor-test,instant-vscode, + release-check-clice,release-check-vscode jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/native-test.yml b/.github/workflows/native-test.yml index a52a96d48..2d90c9fd1 100644 --- a/.github/workflows/native-test.yml +++ b/.github/workflows/native-test.yml @@ -7,7 +7,6 @@ env: CCACHE_DIR: ${{ github.workspace }}/.cache/ccache SCCACHE_DIR: ${{ github.workspace }}/.cache/sccache CCACHE_BASEDIR: ${{ github.workspace }} - SCCACHE_BASEDIRS: ${{ github.workspace }} CCACHE_COMPILERCHECK: content CCACHE_MAXSIZE: 2G SCCACHE_CACHE_SIZE: 2G diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 000000000..8bbd1192d --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,135 @@ +name: nightly + +# Nightly pre-release channel (odd-minor versions): if main gained commits +# since the last nightly, tag HEAD as v0.1.YYMMDDHH, build the full release +# matrix against that tag, and publish it as a GitHub pre-release plus a +# Marketplace pre-release. Old nightlies are pruned after 30 days; stable +# (even-minor) releases are never touched. +on: + schedule: + - cron: "17 2 * * *" + workflow_dispatch: + +permissions: + contents: write + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.version.outputs.tag }} + publish: ${{ steps.version.outputs.publish }} + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + ref: main + fetch-depth: 0 + fetch-tags: true + + - name: Compute nightly version + id: version + run: | + TAG="v0.1.$(date -u +%y%m%d%H)" + LAST=$(git tag -l 'v0.1.*' --sort=-v:refname | head -1) + if [ -n "$LAST" ] && [ "$(git rev-parse "$LAST^{commit}")" = "$(git rev-parse HEAD)" ]; then + echo "No new commits since $LAST; skipping." + echo "publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + if git rev-parse "$TAG" > /dev/null 2>&1; then + echo "$TAG already exists; skipping." + echo "publish=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "publish=true" >> "$GITHUB_OUTPUT" + + # Tags pushed with GITHUB_TOKEN do not trigger workflows, which is + # exactly right here: the publish workflows are invoked directly below + # instead of through the tag-push release path. + - name: Create tag and pre-release + if: steps.version.outputs.publish == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.version.outputs.tag }} + run: | + git tag "$TAG" + git push origin "$TAG" + + LAST=$(git tag -l 'v0.1.*' --sort=-v:refname | sed -n 2p) + { + echo "Nightly build from \`$(git rev-parse --short HEAD)\`." + echo + if [ -n "$LAST" ]; then + echo "## Changes since $LAST" + git log --format='- %s' "$LAST"..HEAD + echo + fi + echo "## Downloads" + echo + echo "| Platform | Server | VS Code extension |" + echo "| --- | --- | --- |" + echo "| Linux x64 | clice-x86_64-linux-gnu.tar.gz | clice-vscode-linux-x64-*.vsix |" + echo "| Linux arm64 | clice-aarch64-linux-gnu.tar.gz | clice-vscode-linux-arm64-*.vsix |" + echo "| macOS arm64 | clice-arm64-macos-darwin.tar.gz | clice-vscode-darwin-arm64-*.vsix |" + echo "| macOS x64 | clice-x86_64-macos-darwin.tar.gz | clice-vscode-darwin-x64-*.vsix |" + echo "| Windows x64 | clice-x64-windows-msvc.zip | clice-vscode-win32-x64-*.vsix |" + echo "| Windows arm64 | clice-aarch64-windows-msvc.zip | clice-vscode-win32-arm64-*.vsix |" + echo + echo "The \`*-symbol\` assets hold GSYM symbols for crash log" + echo "symbolization via \`scripts/symbolize.py\`. VS Code users on" + echo "the Marketplace pre-release channel update automatically." + } > /tmp/notes.md + gh release create "$TAG" --prerelease --title "$TAG" \ + --notes-file /tmp/notes.md + + clice: + needs: prepare + if: needs.prepare.outputs.publish == 'true' + permissions: + contents: write + uses: ./.github/workflows/publish-clice.yml + with: + release_tag: ${{ needs.prepare.outputs.tag }} + secrets: inherit + + vscode: + needs: [prepare, clice] + if: needs.prepare.outputs.publish == 'true' + permissions: + contents: write + uses: ./.github/workflows/publish-vscode.yml + with: + source: package + release_tag: ${{ needs.prepare.outputs.tag }} + prerelease: true + secrets: inherit + + # Nightlies expire after 30 days. Only odd-minor pre-releases are pruned; + # stable releases and their tags are never deleted. + cleanup: + needs: prepare + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Delete expired nightlies + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + CUTOFF=$(date -u -d '30 days ago' +%s) + gh release list --repo "$GITHUB_REPOSITORY" --limit 200 \ + --json tagName,isPrerelease,createdAt \ + -q '.[] | select(.isPrerelease) | .tagName + " " + .createdAt' | + while read -r TAG CREATED; do + case "$TAG" in + v0.1.*|v0.3.*|v0.5.*|v0.7.*|v0.9.*) ;; + *) continue ;; + esac + if [ "$(date -u -d "$CREATED" +%s)" -lt "$CUTOFF" ]; then + echo "Pruning $TAG (created $CREATED)" + gh release delete "$TAG" --repo "$GITHUB_REPOSITORY" \ + --cleanup-tag --yes + fi + done diff --git a/.github/workflows/publish-clice.yml b/.github/workflows/publish-clice.yml index 3d8d71297..c1f1bcb6b 100644 --- a/.github/workflows/publish-clice.yml +++ b/.github/workflows/publish-clice.yml @@ -2,6 +2,13 @@ name: clice on: workflow_call: + inputs: + # Publish the packages to this release tag even when the run is not on + # a tag ref (the nightly pipeline creates the tag itself and then calls + # this workflow). Empty means dry run unless the ref is a v* tag. + release_tag: + type: string + default: "" # Manual runs exercise the full packaging matrix from any branch without # tagging; the upload steps below stay tag-gated. workflow_dispatch: @@ -10,7 +17,6 @@ env: CCACHE_DIR: ${{ github.workspace }}/.cache/ccache SCCACHE_DIR: ${{ github.workspace }}/.cache/sccache CCACHE_BASEDIR: ${{ github.workspace }} - SCCACHE_BASEDIRS: ${{ github.workspace }} CCACHE_COMPILERCHECK: content CCACHE_MAXSIZE: 2G SCCACHE_CACHE_SIZE: 2G @@ -152,21 +158,21 @@ jobs: fi - name: Upload Main Package to Release - if: startsWith(github.ref, 'refs/tags/v') + if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} uses: svenstaro/upload-release-action@2.11.5 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: build/RelWithDebInfo/${{ matrix.artifact_name }} asset_name: ${{ matrix.asset_name }} - tag: ${{ github.ref }} + tag: ${{ inputs.release_tag || github.ref }} overwrite: true - name: Upload Symbol Package to Release - if: startsWith(github.ref, 'refs/tags/v') + if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} uses: svenstaro/upload-release-action@2.11.5 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: build/RelWithDebInfo/${{ matrix.symbol_artifact_name }} asset_name: ${{ matrix.symbol_asset_name }} - tag: ${{ github.ref }} + tag: ${{ inputs.release_tag || github.ref }} overwrite: true diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml index 7cd9ba1b1..dfbfaa2e3 100644 --- a/.github/workflows/publish-vscode.yml +++ b/.github/workflows/publish-vscode.yml @@ -1,18 +1,34 @@ name: vscode +# Single parameterized pipeline for every extension build the project ships: +# source: none — universal vsix without the server binary (PR check); +# the extension falls back to the clice.executable setting. +# source: package — six platform vsix embedding the release packages from +# publish-clice.yml (release, nightly, and dry runs). +# source: build — six platform vsix embedding the test-suite binaries +# (no LTO, no strip) already built by native/cross-test, +# so a fix is installable the moment its CI run is green. on: workflow_call: inputs: - bundle: - description: "Bundle per-platform clice binaries into platform-specific vsix packages" + source: + description: "Server binaries to bundle: none, package, or build" + type: string + default: "none" + # Publish against this release tag even off a tag ref (nightly creates + # the tag itself and calls this workflow). Empty means dry run unless + # the ref is a v* tag. The extension version is injected from the tag, + # so the placeholder version in package.json is never released. + release_tag: + type: string + default: "" + prerelease: type: boolean default: false jobs: - # PR validation: a universal vsix without the server binary; the extension - # falls back to the clice.executable setting at runtime. build-vscode: - if: ${{ !inputs.bundle }} + if: ${{ inputs.source == 'none' }} runs-on: ubuntu-latest steps: - name: Checkout repository @@ -29,26 +45,30 @@ jobs: FLAG="${{ contains(github.ref_name, '-') && '--pre-release' || '' }}" pixi run build-vscode $FLAG - # Release path: one platform-specific vsix per packaging target, each - # embedding the clice package produced by publish-clice.yml in this run. publish-vscode: - if: ${{ inputs.bundle }} + if: ${{ inputs.source != 'none' }} strategy: fail-fast: false matrix: include: - vsce_target: linux-x64 package_artifact: package-clice-x86_64-linux-gnu.tar.gz + build_artifact: native-build-ubuntu-24.04-RelWithDebInfo - vsce_target: linux-arm64 package_artifact: package-clice-aarch64-linux-gnu.tar.gz + build_artifact: cross-build-aarch64-linux-gnu - vsce_target: darwin-arm64 package_artifact: package-clice-arm64-macos-darwin.tar.gz + build_artifact: native-build-macos-15-RelWithDebInfo - vsce_target: darwin-x64 package_artifact: package-clice-x86_64-macos-darwin.tar.gz + build_artifact: cross-build-x86_64-apple-darwin - vsce_target: win32-x64 package_artifact: package-clice-x64-windows-msvc.zip + build_artifact: native-build-windows-2025-RelWithDebInfo - vsce_target: win32-arm64 package_artifact: package-clice-aarch64-windows-msvc.zip + build_artifact: cross-build-aarch64-pc-windows-msvc runs-on: ubuntu-latest @@ -62,49 +82,74 @@ jobs: with: environments: node - - name: Download clice package + - name: Download server binaries uses: actions/download-artifact@v7 with: - name: ${{ matrix.package_artifact }} - path: /tmp/clice-package + name: ${{ inputs.source == 'package' && matrix.package_artifact || matrix.build_artifact }} + path: /tmp/server + # Both sources end up as editors/vscode/clice/{bin,lib/clang,...}, the + # layout bundledExecutable() resolves inside the installed extension. - name: Stage bundled server run: | - cd /tmp/clice-package - if ls *.zip > /dev/null 2>&1; then - unzip -q *.zip + DEST=editors/vscode/clice + if [ "${{ inputs.source }}" = "package" ]; then + cd /tmp/server + if ls *.zip > /dev/null 2>&1; then unzip -q *.zip; else tar xzf *.tar.gz; fi + cd - + mv /tmp/server/clice "$DEST" else - tar xzf *.tar.gz + mkdir -p "$DEST/bin" "$DEST/lib" + cp /tmp/server/bin/clice* "$DEST/bin/" + cp -r /tmp/server/lib/clang "$DEST/lib/" + cp docs/clice.toml "$DEST/" + fi + cp LICENSE "$DEST/" editors/vscode/ + + # The repo carries a fixed placeholder version; every published build + # gets its real version from the release tag, injected here without + # being committed back. + - name: Inject release version + env: + RELEASE_TAG: ${{ inputs.release_tag || (startsWith(github.ref, 'refs/tags/v') && github.ref_name || '') }} + run: | + if [ -n "$RELEASE_TAG" ]; then + python3 - <<'EOF' + import json, os + path = "editors/vscode/package.json" + data = json.load(open(path)) + data["version"] = os.environ["RELEASE_TAG"].lstrip("v") + json.dump(data, open(path, "w"), indent=2) + EOF fi - mv clice "$GITHUB_WORKSPACE/editors/vscode/clice" - name: Package platform extension run: | - FLAG="${{ contains(github.ref_name, '-') && '--pre-release' || '' }}" + FLAG="${{ (inputs.prerelease || contains(github.ref_name, '-')) && '--pre-release' || '' }}" pixi run build-vscode $FLAG --target ${{ matrix.vsce_target }} - name: Upload vsix artifact uses: actions/upload-artifact@v7 with: - name: vsix-${{ matrix.vsce_target }} + name: vsix-${{ inputs.source }}-${{ matrix.vsce_target }} path: editors/vscode/*.vsix if-no-files-found: error retention-days: 7 - name: Publish to Marketplace - if: startsWith(github.ref, 'refs/tags/v') + if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} env: VSCE_PAT: ${{ secrets.VSCE_PAT }} run: | - FLAG="${{ contains(github.ref_name, '-') && '--pre-release' || '' }}" + FLAG="${{ (inputs.prerelease || contains(github.ref_name, '-')) && '--pre-release' || '' }}" pixi run publish-vscode -p "$VSCE_PAT" $FLAG \ --packagePath "$GITHUB_WORKSPACE/$(ls editors/vscode/*.vsix)" - name: Upload .vsix to Release uses: softprops/action-gh-release@v3 - if: startsWith(github.ref, 'refs/tags/v') + if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} with: files: "editors/vscode/*.vsix" - tag_name: ${{ github.ref }} + tag_name: ${{ inputs.release_tag || github.ref }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-check.yml b/.github/workflows/release-check.yml deleted file mode 100644 index 382f0134f..000000000 --- a/.github/workflows/release-check.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: release-check - -# Dry run of the release packaging pipeline: builds all six clice packages and -# all six bundled extension packages. Every upload step is tag-gated, so -# nothing is published. Runs automatically on PRs that touch release plumbing, -# and on demand from any branch. -on: - workflow_dispatch: - pull_request: - paths: - - ".github/workflows/publish-clice.yml" - - ".github/workflows/publish-vscode.yml" - - ".github/workflows/release-check.yml" - - "CMakeLists.txt" - - "cmake/release.cmake" - - "cmake/archive.cmake" - -jobs: - clice: - uses: ./.github/workflows/publish-clice.yml - - vscode: - needs: clice - uses: ./.github/workflows/publish-vscode.yml - with: - bundle: true diff --git a/cmake/release.cmake b/cmake/release.cmake index b17bfb781..7a4222fe0 100644 --- a/cmake/release.cmake +++ b/cmake/release.cmake @@ -54,7 +54,8 @@ add_custom_target(clice-pack ALL COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_PACK_DIR}/clice/bin" COMMAND ${CMAKE_COMMAND} -E copy "$" "${CLICE_PACK_DIR}/clice/bin/" COMMAND ${CMAKE_COMMAND} -E copy_directory "${LLVM_INSTALL_PATH}/lib/clang" "${CLICE_PACK_DIR}/clice/lib/clang" - COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/docs/clice.toml" "${CLICE_PACK_DIR}/clice/" + COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/docs/clice.toml" + "${PROJECT_SOURCE_DIR}/LICENSE" "${CLICE_PACK_DIR}/clice/" COMMAND ${CMAKE_COMMAND} -DOUTPUT="${PROJECT_BINARY_DIR}/clice${CLICE_ARCHIVE_EXT}" -DWORK_DIR="${CLICE_PACK_DIR}" From fd907107c699e69fe3a8c0c4fc5977126105d714 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 13:07:13 +0800 Subject: [PATCH 02/10] feat(support): stamp version and target in logs --- CMakeLists.txt | 6 ++++++ cmake/generate_version.cmake | 2 ++ cmake/version.h.in | 4 ++++ src/server/transport/master_server.cpp | 5 ++++- src/support/logging.cpp | 4 ++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0481cd4..744eeff7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,10 +167,16 @@ add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}") # Version header, regenerated on every build so the embedded git describe # tracks the checked-out commit (content-unchanged writes are elided). set(CLICE_VERSION_HEADER "${PROJECT_BINARY_DIR}/generated/version.h") +if(DEFINED CLICE_TARGET_TRIPLE) + set(CLICE_TARGET_STRING "${CLICE_TARGET_TRIPLE}") +else() + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}" CLICE_TARGET_STRING) +endif() add_custom_target(generate_version_header COMMAND ${CMAKE_COMMAND} -DSOURCE_DIR=${PROJECT_SOURCE_DIR} -DFALLBACK_VERSION=${PROJECT_VERSION} + -DTARGET_STRING=${CLICE_TARGET_STRING} -DTEMPLATE=${PROJECT_SOURCE_DIR}/cmake/version.h.in -DOUTPUT_FILE=${CLICE_VERSION_HEADER} -P "${PROJECT_SOURCE_DIR}/cmake/generate_version.cmake" diff --git a/cmake/generate_version.cmake b/cmake/generate_version.cmake index b4e48c64c..7f2b47c9b 100644 --- a/cmake/generate_version.cmake +++ b/cmake/generate_version.cmake @@ -47,4 +47,6 @@ else() set(CLICE_VERSION_STRING "${CLICE_GIT_DESCRIBE}") endif() +set(CLICE_TARGET_STRING "${TARGET_STRING}") + configure_file("${TEMPLATE}" "${OUTPUT_FILE}" @ONLY) diff --git a/cmake/version.h.in b/cmake/version.h.in index 82892580e..616ba9e52 100644 --- a/cmake/version.h.in +++ b/cmake/version.h.in @@ -11,4 +11,8 @@ namespace clice { /// Generated from cmake/version.h.in — do not edit. inline constexpr std::string_view version = "@CLICE_VERSION_STRING@"; +/// Target platform this binary was built for, e.g. "x86_64-linux". Logged +/// with the version so crash reports identify the exact artifact. +inline constexpr std::string_view target = "@CLICE_TARGET_STRING@"; + } // namespace clice diff --git a/src/server/transport/master_server.cpp b/src/server/transport/master_server.cpp index 8f3792d76..e8d2779ec 100644 --- a/src/server/transport/master_server.cpp +++ b/src/server/transport/master_server.cpp @@ -5,6 +5,7 @@ #include #include +#include "version.h" #include "server/protocol/worker.h" #include "server/state/file_tracker.h" #include "server/transport/agent_client.h" @@ -619,7 +620,9 @@ int run_serve_mode(const ServerOptions& opts, const char* self_path) { auto record = opts.record.value_or(""); auto ws = opts.workspace.value_or(""); - LOG_INFO("clice master starting: pid={}, mode={}, workspace={}", + LOG_INFO("clice master starting: version={} target={} pid={}, mode={}, workspace={}", + clice::version, + clice::target, llvm::sys::Process::getProcessId(), mode == ServerMode::Pipe ? "pipe" : "socket", ws.empty() ? "" : ws); diff --git a/src/support/logging.cpp b/src/support/logging.cpp index 0a721c333..5865842e6 100644 --- a/src/support/logging.cpp +++ b/src/support/logging.cpp @@ -16,6 +16,7 @@ #include #endif +#include "version.h" #include "support/filesystem.h" #include "support/stderr_sink.h" @@ -140,6 +141,9 @@ uintptr_t main_executable_base() { static void crash_handler(void*) { if(crash_log_stream) { *crash_log_stream << "\n=== CRASH STACK TRACE ===\n"; + // Identifies the exact artifact so symbolization uses the matching + // symbol file (all constexpr string_views: async-signal-safe). + *crash_log_stream << "clice " << version << " " << target << "\n"; // Release binaries are PIE and stripped: the frame addresses below are // ASLR-shifted, so offline symbolization against the shipped symbol // file needs this rebase value (see scripts/symbolize.py). Zero is a From 9a6e229c64d5b553c10b91e85bfbd4353237bbb1 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 13:07:13 +0800 Subject: [PATCH 03/10] docs: add release process command --- .claude/commands/release.md | 62 +++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .claude/commands/release.md diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 000000000..38ed876de --- /dev/null +++ b/.claude/commands/release.md @@ -0,0 +1,62 @@ +Operate the three-tier release process: instant builds, nightlies, and stable releases. + +## Versioning + +One version number everywhere: git tag `vX.Y.Z` == extension version == release +page. Odd minor = pre-release channel, even minor = stable (the VS Code +Marketplace convention). Nightlies compute `0.1.YYMMDDHH` (UTC hour) — nobody +edits version numbers by hand. The versions in `CMakeLists.txt`, `pixi.toml`, +and `editors/vscode/package.json` are permanent placeholders (`0.1.0`); the +real version is injected from the tag at build time (binary via git describe, +vsix via CI). A local `vsce publish` with the placeholder is rejected by the +Marketplace — that is intentional accident protection. + +## Tier 1 — Instant builds (every green CI run) + +Nothing to operate. Every `main` push and PR run repackages the test-suite +binaries (no LTO, no strip) into `vsix-build-` workflow artifacts, and +the raw binaries are in `native-build-*` / `cross-build-*` artifacts. To hand a +fix to a user: point them at the run's artifact (GitHub login required), or +have them set `clice.executable` to the extracted binary. + +## Tier 2 — Nightly (pre-release channel) + +`nightly.yml` runs daily (cron) or via `gh workflow run nightly.yml`: +skips when main has no new commits, otherwise tags `v0.1.YYMMDDHH`, builds the +full release matrix (ICF + strip + GSYM symbols), publishes a GitHub +pre-release and Marketplace `--pre-release`, and prunes odd-minor pre-releases +older than 30 days. A failed nightly just means no nightly that day — fix main +and rerun via dispatch. + +## Tier 3 — Stable release (manual, even minor) + +1. Freeze: stop merging features; the last nightlies of the freeze are the RCs. +2. When a nightly is judged good, tag its commit: `git tag v0.2.0 && git push origin v0.2.0`. + The tag push runs the release path of `main.yml` (packages + extensions, + Marketplace without `--pre-release`). +3. Write the release notes on the GitHub release page (the download table + format from the nightly notes is a good template). +4. Verify: assets present (6 packages + 4 symbol archives + 2 PDB zips + + 6 vsix), Marketplace shows the new stable version. + +## Dry run / plumbing changes + +PRs touching release plumbing (publish workflows, `cmake/release.cmake`, +`cmake/archive.cmake`, `CMakeLists.txt`) automatically run the full 6+6 dry +run via the `release-check-*` jobs; `gh workflow run main.yml --ref ` +does the same on demand. Uploads stay tag-gated, so nothing publishes. + +## Crash log support + +Ask the user for the log (worker `.log` from the workspace `.clice/logs/` or +the cache dir). The crash section starts with `clice ` — +download that release's `*-symbol.tar.xz` (GSYM) and run: + +```bash +python scripts/symbolize.py crash.log --symbols clice.gsym +``` + +For core-dump-level debugging, fetch the full DWARF from the release run's +`debug-info-*` workflow artifact (90-day retention). If the log predates the +version line or the release was pruned, symbolization is not possible — ask +the user to reproduce on a current nightly. From 36596aa7a8cadb5bc6628e31fc101b947949a263 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 14:07:55 +0800 Subject: [PATCH 04/10] fix(release): review follow-ups for staging and tests --- .github/workflows/main.yml | 2 +- .github/workflows/publish-vscode.yml | 9 +++++++-- CMakeLists.txt | 2 ++ src/server/transport/master_server.cpp | 2 +- tests/integration/lifecycle/test_anomaly.py | 3 +++ tests/unit/support/logging_tests.cpp | 8 ++++++++ 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2bdac3286..d762f0992 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -103,7 +103,7 @@ jobs: if: ${{ needs.changes.outputs.format == 'true' }} runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout repository uses: actions/checkout@v7 with: persist-credentials: false diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml index dfbfaa2e3..e32279cc0 100644 --- a/.github/workflows/publish-vscode.yml +++ b/.github/workflows/publish-vscode.yml @@ -100,11 +100,16 @@ jobs: mv /tmp/server/clice "$DEST" else mkdir -p "$DEST/bin" "$DEST/lib" - cp /tmp/server/bin/clice* "$DEST/bin/" + # Only the executable: test builds keep unit_tests and (on + # Windows) a huge PDB next to it. + for f in clice clice.exe; do + if [ -f "/tmp/server/bin/$f" ]; then cp "/tmp/server/bin/$f" "$DEST/bin/"; fi + done cp -r /tmp/server/lib/clang "$DEST/lib/" cp docs/clice.toml "$DEST/" fi - cp LICENSE "$DEST/" editors/vscode/ + cp LICENSE "$DEST/" + cp LICENSE editors/vscode/ # The repo carries a fixed placeholder version; every published build # gets its real version from the release tag, injected here without diff --git a/CMakeLists.txt b/CMakeLists.txt index 744eeff7f..623e8c2dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,8 @@ add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}") # Version header, regenerated on every build so the embedded git describe # tracks the checked-out commit (content-unchanged writes are elided). set(CLICE_VERSION_HEADER "${PROJECT_BINARY_DIR}/generated/version.h") +# Target identifier stamped next to the version: the explicit triple for +# cross builds (see cmake/toolchain.cmake), processor-os for native ones. if(DEFINED CLICE_TARGET_TRIPLE) set(CLICE_TARGET_STRING "${CLICE_TARGET_TRIPLE}") else() diff --git a/src/server/transport/master_server.cpp b/src/server/transport/master_server.cpp index e8d2779ec..0a69ee1d1 100644 --- a/src/server/transport/master_server.cpp +++ b/src/server/transport/master_server.cpp @@ -620,7 +620,7 @@ int run_serve_mode(const ServerOptions& opts, const char* self_path) { auto record = opts.record.value_or(""); auto ws = opts.workspace.value_or(""); - LOG_INFO("clice master starting: version={} target={} pid={}, mode={}, workspace={}", + LOG_INFO("clice master starting: version={}, target={}, pid={}, mode={}, workspace={}", clice::version, clice::target, llvm::sys::Process::getProcessId(), diff --git a/tests/integration/lifecycle/test_anomaly.py b/tests/integration/lifecycle/test_anomaly.py index 9dfd4ba97..22ac742c3 100644 --- a/tests/integration/lifecycle/test_anomaly.py +++ b/tests/integration/lifecycle/test_anomaly.py @@ -95,6 +95,9 @@ async def test_worker_crash_reported(executable, tmp_path): re.search(r"main executable base: 0x[0-9a-fA-F]+", text) for text in worker_texts ), "crash trace should record the executable base for offline symbolization" + assert any(re.search(r"^clice \S+ \S+$", text, re.M) for text in worker_texts), ( + "crash trace should stamp the version/target line" + ) master_texts = [p.read_text(errors="replace") for p in logs_dir.rglob("master.log")] assert master_texts and all( "CRASH STACK TRACE" not in text and "Stack dump" not in text diff --git a/tests/unit/support/logging_tests.cpp b/tests/unit/support/logging_tests.cpp index 05cad4304..c32acbb89 100644 --- a/tests/unit/support/logging_tests.cpp +++ b/tests/unit/support/logging_tests.cpp @@ -1,3 +1,4 @@ +#include "version.h" #include "test/test.h" #include "support/logging.h" @@ -6,6 +7,13 @@ namespace { TEST_SUITE(Logging) { +TEST_CASE(VersionStamps) { + // Guards the cmake target-stamping plumbing: an unset CLICE_TARGET_STRING + // would otherwise only surface in crash logs. + EXPECT_FALSE(clice::version.empty()); + EXPECT_FALSE(clice::target.empty()); +} + TEST_CASE(MainExecutableBase) { // Linux relies on the binary being PIE — a non-PIE image has bias 0, // which would silently void the crash-log rebase contract; Windows From 7baaba33880e9094a33bbf8cf5c6f69e55089bfd Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 14:29:23 +0800 Subject: [PATCH 05/10] fix(release): nightly version fidelity and channel rollover --- .claude/commands/release.md | 7 ++++--- .github/workflows/nightly.yml | 26 +++++++++++++++++++------- .github/workflows/publish-clice.yml | 4 ++++ .github/workflows/publish-vscode.yml | 3 ++- CMakeLists.txt | 16 ++++++++++++++-- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/.claude/commands/release.md b/.claude/commands/release.md index 38ed876de..8b7a58a4f 100644 --- a/.claude/commands/release.md +++ b/.claude/commands/release.md @@ -4,8 +4,9 @@ Operate the three-tier release process: instant builds, nightlies, and stable re One version number everywhere: git tag `vX.Y.Z` == extension version == release page. Odd minor = pre-release channel, even minor = stable (the VS Code -Marketplace convention). Nightlies compute `0.1.YYMMDDHH` (UTC hour) — nobody -edits version numbers by hand. The versions in `CMakeLists.txt`, `pixi.toml`, +Marketplace convention). Nightlies compute `X..YYMMDDHH` (UTC hour) on the +odd minor above the newest release — `0.1.*` today, `0.3.*` after stable +`v0.2.0` — so nobody edits version numbers by hand. The versions in `CMakeLists.txt`, `pixi.toml`, and `editors/vscode/package.json` are permanent placeholders (`0.1.0`); the real version is injected from the tag at build time (binary via git describe, vsix via CI). A local `vsce publish` with the placeholder is rejected by the @@ -22,7 +23,7 @@ have them set `clice.executable` to the extracted binary. ## Tier 2 — Nightly (pre-release channel) `nightly.yml` runs daily (cron) or via `gh workflow run nightly.yml`: -skips when main has no new commits, otherwise tags `v0.1.YYMMDDHH`, builds the +skips when main has no new commits, otherwise tags the nightly version, builds the full release matrix (ICF + strip + GSYM symbols), publishes a GitHub pre-release and Marketplace `--pre-release`, and prunes odd-minor pre-releases older than 30 days. A failed nightly just means no nightly that day — fix main diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 8bbd1192d..e08146488 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -30,8 +30,21 @@ jobs: - name: Compute nightly version id: version run: | - TAG="v0.1.$(date -u +%y%m%d%H)" - LAST=$(git tag -l 'v0.1.*' --sort=-v:refname | head -1) + # Nightlies live on the odd minor above the newest release: after + # stable v0.2.0 ships they become v0.3.*, keeping the Marketplace + # pre-release channel ahead of stable. + LATEST=$(git tag -l 'v*.*.*' --sort=-v:refname | head -1) + MAJOR=0 + MINOR=1 + if [ -n "$LATEST" ]; then + BASE=${LATEST#v} + MAJOR=${BASE%%.*} + REST=${BASE#*.} + MINOR=${REST%%.*} + if [ $((MINOR % 2)) -eq 0 ]; then MINOR=$((MINOR + 1)); fi + fi + TAG="v$MAJOR.$MINOR.$(date -u +%y%m%d%H)" + LAST=$(git tag -l "v$MAJOR.$MINOR.*" --sort=-v:refname | head -1) if [ -n "$LAST" ] && [ "$(git rev-parse "$LAST^{commit}")" = "$(git rev-parse HEAD)" ]; then echo "No new commits since $LAST; skipping." echo "publish=false" >> "$GITHUB_OUTPUT" @@ -57,7 +70,7 @@ jobs: git tag "$TAG" git push origin "$TAG" - LAST=$(git tag -l 'v0.1.*' --sort=-v:refname | sed -n 2p) + LAST=$(git tag -l "${TAG%.*}.*" --sort=-v:refname | sed -n 2p) { echo "Nightly build from \`$(git rev-parse --short HEAD)\`." echo @@ -123,10 +136,9 @@ jobs: --json tagName,isPrerelease,createdAt \ -q '.[] | select(.isPrerelease) | .tagName + " " + .createdAt' | while read -r TAG CREATED; do - case "$TAG" in - v0.1.*|v0.3.*|v0.5.*|v0.7.*|v0.9.*) ;; - *) continue ;; - esac + MINOR=$(echo "$TAG" | cut -d. -f2) + case "$MINOR" in *[!0-9]* | "") continue ;; esac + if [ $((MINOR % 2)) -eq 0 ]; then continue; fi if [ "$(date -u -d "$CREATED" +%s)" -lt "$CUTOFF" ]; then echo "Pruning $TAG (created $CREATED)" gh release delete "$TAG" --repo "$GITHUB_REPOSITORY" \ diff --git a/.github/workflows/publish-clice.yml b/.github/workflows/publish-clice.yml index c1f1bcb6b..648918c2f 100644 --- a/.github/workflows/publish-clice.yml +++ b/.github/workflows/publish-clice.yml @@ -78,9 +78,13 @@ jobs: shell: bash steps: + # For nightly calls the tag was pushed moments ago by nightly.yml: + # check out the tagged commit so git describe stamps the binary with + # exactly the released version. - name: Checkout repository uses: actions/checkout@v7 with: + ref: ${{ inputs.release_tag || github.ref }} persist-credentials: false - uses: ./.github/actions/setup-pixi diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml index e32279cc0..23d31e909 100644 --- a/.github/workflows/publish-vscode.yml +++ b/.github/workflows/publish-vscode.yml @@ -76,6 +76,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v7 with: + ref: ${{ inputs.release_tag || github.ref }} persist-credentials: false - uses: ./.github/actions/setup-pixi @@ -83,7 +84,7 @@ jobs: environments: node - name: Download server binaries - uses: actions/download-artifact@v7 + uses: actions/download-artifact@v8 with: name: ${{ inputs.source == 'package' && matrix.package_artifact || matrix.build_artifact }} path: /tmp/server diff --git a/CMakeLists.txt b/CMakeLists.txt index 623e8c2dc..715eeebe8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,11 +168,23 @@ add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}") # tracks the checked-out commit (content-unchanged writes are elided). set(CLICE_VERSION_HEADER "${PROJECT_BINARY_DIR}/generated/version.h") # Target identifier stamped next to the version: the explicit triple for -# cross builds (see cmake/toolchain.cmake), processor-os for native ones. +# cross builds (see cmake/toolchain.cmake); native builds compose the same +# arch-os-abi form the release asset names use, so a crash log names the +# exact downloadable artifact. if(DEFINED CLICE_TARGET_TRIPLE) set(CLICE_TARGET_STRING "${CLICE_TARGET_TRIPLE}") else() - string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}" CLICE_TARGET_STRING) + string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CLICE_TARGET_ARCH) + if(CLICE_TARGET_ARCH STREQUAL "amd64") + set(CLICE_TARGET_ARCH "x64") + endif() + if(WIN32) + set(CLICE_TARGET_STRING "${CLICE_TARGET_ARCH}-windows-msvc") + elseif(APPLE) + set(CLICE_TARGET_STRING "${CLICE_TARGET_ARCH}-macos-darwin") + else() + set(CLICE_TARGET_STRING "${CLICE_TARGET_ARCH}-linux-gnu") + endif() endif() add_custom_target(generate_version_header COMMAND ${CMAKE_COMMAND} From dee392e4b9638149e024164ebbdf8f8dcfe75b41 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 14:29:23 +0800 Subject: [PATCH 06/10] docs: document release channels in readmes --- README.md | 7 ++++++ editors/vscode/README.md | 51 ++++++++++++++++++++++++++++++---------- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 543eff9fd..a4e38fba2 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,13 @@ Download the latest binary from the [releases page](https://github.com/clice-io/ **Platforms:** Linux (x64, ARM64), macOS (x64, ARM64), Windows (x64, ARM64) +**Release channels:** + +- **Stable** (even minor, e.g. `v0.2.0`) — regular releases on the releases page and the VS Code Marketplace. +- **Nightly** (odd minor, e.g. `v0.1.26071802`) — built daily from `main`, published as GitHub pre-releases and on the Marketplace pre-release channel (`Switch to Pre-Release Version` on the extension page). Nightlies older than 30 days are removed. + +Each release also ships `*-symbol` packages: if clice crashes, attach the log from your workspace's `.clice/logs/` to an issue and the matching symbols let us reconstruct the stack. + ### Editor Setup | Editor | Setup | diff --git a/editors/vscode/README.md b/editors/vscode/README.md index c1ce18974..36fb08ad3 100644 --- a/editors/vscode/README.md +++ b/editors/vscode/README.md @@ -1,19 +1,44 @@ -# vscode-clice +# clice -This is the vscode extension for [clice](https://github.com/clice-io/clice). +C++ language support powered by [clice](https://github.com/clice-io/clice), a +language server written from scratch on LLVM/Clang: template-aware completion +inside generic code, first-class compilation contexts for headers and build +configurations, and native C++20 modules support. -## develop +## Getting started -- install dependencies +1. Install this extension — the clice server for your platform is bundled, no + download or extra setup needed. +2. Open a C++ project with a + [compilation database](https://clang.llvm.org/docs/JSONCompilationDatabase.html). + For CMake: `cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`. clice + searches the workspace root and its immediate subdirectories (e.g. + `build/`) automatically. -```shell -git clone https://github.com/clice-io/clice.git -cd clice/editors/vscode -npm install -``` +See the [documentation](https://docs.clice.io/clice/) for configuration via +`clice.toml` and the full feature guide. -- package +## Release channels -```shell -npm run package -``` +Stable versions use even minor numbers (`0.2.x`); daily pre-releases from +`main` use odd ones (`0.1.x`). Use `Switch to Pre-Release Version` on the +extension page to follow the pre-release channel. + +## Settings + +| Setting | Default | Description | +| ------------------ | ----------- | ---------------------------------------------------------------------- | +| `clice.executable` | _(bundled)_ | Path to a clice binary to use instead of the bundled one. | +| `clice.mode` | `pipe` | Server transport; `socket` connects to an external server (debugging). | +| `clice.host` | `127.0.0.1` | Host for socket mode. | +| `clice.port` | `50051` | Port for socket mode. | + +## Troubleshooting + +Server logs live in the `clice` output channel and in your workspace's +`.clice/logs/`. If the server crashes, please attach the newest log there to a +[GitHub issue](https://github.com/clice-io/clice/issues) — releases ship +symbol packages that let us reconstruct the exact stack. + +Extension development is documented in the +[contributor guide](https://docs.clice.io/clice/dev/extension). From d2ffd43d228e9097423cbc30231046b17be729ce Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 15:06:25 +0800 Subject: [PATCH 07/10] fix(release): address review-bot findings --- .github/workflows/main.yml | 7 +++++-- .github/workflows/nightly.yml | 14 +++++++++----- CMakeLists.txt | 6 +++++- cmake/generate_version.cmake | 20 +++++++++++++++++--- 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d762f0992..6bdbf9700 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,6 +54,8 @@ jobs: release: - '.github/workflows/publish-clice.yml' - '.github/workflows/publish-vscode.yml' + - '.github/workflows/main.yml' + - '.github/workflows/nightly.yml' - 'CMakeLists.txt' - 'cmake/release.cmake' - 'cmake/archive.cmake' @@ -211,14 +213,15 @@ jobs: release-clice: permissions: contents: write - if: startsWith(github.ref, 'refs/tags/v') + # push only: a workflow_dispatch aimed at a tag ref must stay a dry run. + if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push' }} uses: ./.github/workflows/publish-clice.yml secrets: inherit release-vscode: permissions: contents: write - if: startsWith(github.ref, 'refs/tags/v') + if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push' }} needs: release-clice uses: ./.github/workflows/publish-vscode.yml with: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e08146488..69731dbaa 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,10 +1,11 @@ name: nightly # Nightly pre-release channel (odd-minor versions): if main gained commits -# since the last nightly, tag HEAD as v0.1.YYMMDDHH, build the full release -# matrix against that tag, and publish it as a GitHub pre-release plus a -# Marketplace pre-release. Old nightlies are pruned after 30 days; stable -# (even-minor) releases are never touched. +# since the last nightly, tag HEAD as vX..YYMMDDHH on the odd minor +# above the newest release, build the full release matrix against that tag, +# and publish it as a GitHub pre-release plus a Marketplace pre-release. +# Old nightlies are pruned after 30 days; stable (even-minor) releases are +# never touched. on: schedule: - cron: "17 2 * * *" @@ -45,7 +46,10 @@ jobs: fi TAG="v$MAJOR.$MINOR.$(date -u +%y%m%d%H)" LAST=$(git tag -l "v$MAJOR.$MINOR.*" --sort=-v:refname | head -1) - if [ -n "$LAST" ] && [ "$(git rev-parse "$LAST^{commit}")" = "$(git rev-parse HEAD)" ]; then + # Manual dispatch skips only the same-hour duplicate below, so a + # failed nightly can be retried on the same HEAD. + if [ -n "$LAST" ] && [ "$(git rev-parse "$LAST^{commit}")" = "$(git rev-parse HEAD)" ] \ + && [ "$GITHUB_EVENT_NAME" = "schedule" ]; then echo "No new commits since $LAST; skipping." echo "publish=false" >> "$GITHUB_OUTPUT" exit 0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 715eeebe8..70e753c28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,7 +171,11 @@ set(CLICE_VERSION_HEADER "${PROJECT_BINARY_DIR}/generated/version.h") # cross builds (see cmake/toolchain.cmake); native builds compose the same # arch-os-abi form the release asset names use, so a crash log names the # exact downloadable artifact. -if(DEFINED CLICE_TARGET_TRIPLE) +if(CLICE_TARGET_TRIPLE STREQUAL "x86_64-apple-darwin") + set(CLICE_TARGET_STRING "x86_64-macos-darwin") +elseif(CLICE_TARGET_TRIPLE STREQUAL "aarch64-pc-windows-msvc") + set(CLICE_TARGET_STRING "aarch64-windows-msvc") +elseif(DEFINED CLICE_TARGET_TRIPLE) set(CLICE_TARGET_STRING "${CLICE_TARGET_TRIPLE}") else() string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" CLICE_TARGET_ARCH) diff --git a/cmake/generate_version.cmake b/cmake/generate_version.cmake index 7f2b47c9b..48c155e81 100644 --- a/cmake/generate_version.cmake +++ b/cmake/generate_version.cmake @@ -23,13 +23,27 @@ if(CLICE_TOPLEVEL_RESULT EQUAL 0 AND NOT CLICE_GIT_TOPLEVEL STREQUAL "") file(REAL_PATH "${CLICE_GIT_TOPLEVEL}" CLICE_GIT_TOPLEVEL) file(REAL_PATH "${SOURCE_DIR}" CLICE_SOURCE_REAL) if(CLICE_GIT_TOPLEVEL STREQUAL CLICE_SOURCE_REAL) + # A commit can carry several tags (a stable tag placed on a nightly's + # commit); describe alone picks one nondeterministically, so prefer + # the highest exact tag by version sort. execute_process( - COMMAND git -C "${SOURCE_DIR}" describe --tags --always --dirty - OUTPUT_VARIABLE CLICE_GIT_DESCRIBE + COMMAND git -C "${SOURCE_DIR}" tag --points-at HEAD --sort=-v:refname + OUTPUT_VARIABLE CLICE_HEAD_TAGS OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET - RESULT_VARIABLE CLICE_GIT_RESULT ) + if(NOT CLICE_HEAD_TAGS STREQUAL "") + string(REGEX REPLACE "\n.*" "" CLICE_GIT_DESCRIBE "${CLICE_HEAD_TAGS}") + set(CLICE_GIT_RESULT 0) + else() + execute_process( + COMMAND git -C "${SOURCE_DIR}" describe --tags --always --dirty + OUTPUT_VARIABLE CLICE_GIT_DESCRIBE + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE CLICE_GIT_RESULT + ) + endif() endif() endif() From d1d8af16f884e07de9ef9ceedea055e718e892f6 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 15:36:20 +0800 Subject: [PATCH 08/10] fix(release): least-privilege secrets and dirty tag stamping --- .github/workflows/main.yml | 1 - .github/workflows/nightly.yml | 1 - cmake/generate_version.cmake | 10 ++++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6bdbf9700..e9e2e2cd5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -216,7 +216,6 @@ jobs: # push only: a workflow_dispatch aimed at a tag ref must stay a dry run. if: ${{ startsWith(github.ref, 'refs/tags/v') && github.event_name == 'push' }} uses: ./.github/workflows/publish-clice.yml - secrets: inherit release-vscode: permissions: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 69731dbaa..dd2b84e02 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -109,7 +109,6 @@ jobs: uses: ./.github/workflows/publish-clice.yml with: release_tag: ${{ needs.prepare.outputs.tag }} - secrets: inherit vscode: needs: [prepare, clice] diff --git a/cmake/generate_version.cmake b/cmake/generate_version.cmake index 48c155e81..6112a61bb 100644 --- a/cmake/generate_version.cmake +++ b/cmake/generate_version.cmake @@ -34,6 +34,16 @@ if(CLICE_TOPLEVEL_RESULT EQUAL 0 AND NOT CLICE_GIT_TOPLEVEL STREQUAL "") ) if(NOT CLICE_HEAD_TAGS STREQUAL "") string(REGEX REPLACE "\n.*" "" CLICE_GIT_DESCRIBE "${CLICE_HEAD_TAGS}") + # Keep parity with describe --dirty: a modified tree must not + # claim to be the pristine tagged artifact. + execute_process( + COMMAND git -C "${SOURCE_DIR}" diff-index --quiet HEAD -- + RESULT_VARIABLE CLICE_DIRTY_RESULT + ERROR_QUIET + ) + if(NOT CLICE_DIRTY_RESULT EQUAL 0) + string(APPEND CLICE_GIT_DESCRIBE "-dirty") + endif() set(CLICE_GIT_RESULT 0) else() execute_process( From 35cd936f9d64fa44406771c7f9ada55e4cc06440 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 16:09:09 +0800 Subject: [PATCH 09/10] fix(release): strip instant binaries, gate uploads on push --- .github/workflows/publish-clice.yml | 4 ++-- .github/workflows/publish-vscode.yml | 7 +++++-- pixi.lock | 21 +++++++++++++++++++++ pixi.toml | 5 +++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-clice.yml b/.github/workflows/publish-clice.yml index 648918c2f..b5a940a6d 100644 --- a/.github/workflows/publish-clice.yml +++ b/.github/workflows/publish-clice.yml @@ -162,7 +162,7 @@ jobs: fi - name: Upload Main Package to Release - if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} + if: ${{ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || inputs.release_tag != '' }} uses: svenstaro/upload-release-action@2.11.5 with: repo_token: ${{ secrets.GITHUB_TOKEN }} @@ -172,7 +172,7 @@ jobs: overwrite: true - name: Upload Symbol Package to Release - if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} + if: ${{ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || inputs.release_tag != '' }} uses: svenstaro/upload-release-action@2.11.5 with: repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml index 23d31e909..056fc124f 100644 --- a/.github/workflows/publish-vscode.yml +++ b/.github/workflows/publish-vscode.yml @@ -106,6 +106,9 @@ jobs: for f in clice clice.exe; do if [ -f "/tmp/server/bin/$f" ]; then cp "/tmp/server/bin/$f" "$DEST/bin/"; fi done + # Test builds embed full debug info (~700MB on ELF); the vsix + # only needs the runnable binary. + pixi run -e node -- llvm-strip --strip-debug --strip-unneeded "$DEST"/bin/clice* cp -r /tmp/server/lib/clang "$DEST/lib/" cp docs/clice.toml "$DEST/" fi @@ -143,7 +146,7 @@ jobs: retention-days: 7 - name: Publish to Marketplace - if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} + if: ${{ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || inputs.release_tag != '' }} env: VSCE_PAT: ${{ secrets.VSCE_PAT }} run: | @@ -153,7 +156,7 @@ jobs: - name: Upload .vsix to Release uses: softprops/action-gh-release@v3 - if: ${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} + if: ${{ (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || inputs.release_tag != '' }} with: files: "editors/vscode/*.vsix" tag_name: ${{ inputs.release_tag || github.ref }} diff --git a/pixi.lock b/pixi.lock index 542407a8a..98e0782a5 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1402,6 +1402,8 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_19.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm20-20.1.8-hf7376ad_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda @@ -1409,7 +1411,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_19.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.1-h5347b49_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.52.1-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-tools-20-20.1.8-h3b15d91_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-tools-20.1.8-hb700be7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-26.3.0-he4ff34a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda @@ -1755,12 +1761,19 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_16.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm20-20.1.8-hf7376ad_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.51.1-h0c1763c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-15.2.0-hdf11a46_16.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-ha9997c6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-h26afc86_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-tools-20-20.1.8-h3b15d91_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-tools-20.1.8-hb700be7_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/nodejs-24.12.0-h36edbcc_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.0-h26f9b46_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pnpm-10.26.2-hcdd682b_0.conda @@ -3553,6 +3566,9 @@ packages: - xz 5.8.3.* license: 0BSD purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 size: 113478 timestamp: 1775825492909 - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-devel-5.8.1-hb9d3cd8_2.conda @@ -3848,6 +3864,7 @@ packages: - libxml2 2.15.1 license: MIT license_family: MIT + run_exports: {} size: 556302 timestamp: 1761015637262 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.1-hca6bf5a_1.conda @@ -3948,6 +3965,10 @@ packages: - libzlib >=1.3.1,<2.0a0 license: MIT license_family: MIT + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.1 size: 45283 timestamp: 1761015644057 - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.1-he237659_1.conda diff --git a/pixi.toml b/pixi.toml index eb41cc51d..3492296ff 100644 --- a/pixi.toml +++ b/pixi.toml @@ -277,6 +277,11 @@ platforms = ["win-64", "linux-64", "osx-arm64", "osx-64", "linux-aarch64"] nodejs = ">=20" pnpm = ">=10,<11" +# The instant-build packaging strips test binaries before bundling them +# into the vsix (an unstripped ELF carries ~700MB of DWARF). +[feature.node.target.linux-64.dependencies] +llvm-tools = "==20.1.8" + [feature.node.tasks] build-vscode = { cwd = "editors/vscode", cmd = "pnpm run package", depends-on = ["install-vscode"] } publish-vscode = { cwd = "editors/vscode", cmd = "pnpm run publish", depends-on = ["install-vscode"] } From 27c09f9793b05c185c99efea131488b29ffbb4eb Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 18 Jul 2026 16:56:31 +0800 Subject: [PATCH 10/10] fix(release): portable strip flags for instant builds --- .github/workflows/publish-vscode.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-vscode.yml b/.github/workflows/publish-vscode.yml index 056fc124f..0ccefbecd 100644 --- a/.github/workflows/publish-vscode.yml +++ b/.github/workflows/publish-vscode.yml @@ -107,8 +107,9 @@ jobs: if [ -f "/tmp/server/bin/$f" ]; then cp "/tmp/server/bin/$f" "$DEST/bin/"; fi done # Test builds embed full debug info (~700MB on ELF); the vsix - # only needs the runnable binary. - pixi run -e node -- llvm-strip --strip-debug --strip-unneeded "$DEST"/bin/clice* + # only needs the runnable binary. --strip-debug only: further + # options are not supported for Mach-O. + pixi run -e node -- llvm-strip --strip-debug "$DEST"/bin/clice* cp -r /tmp/server/lib/clang "$DEST/lib/" cp docs/clice.toml "$DEST/" fi