Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ae0f936
fix: locate the CLion backend and verify stable IDE runtimes
Sunrisepeak Nov 5, 2026
6e44d54
test: exercise CLion build run and test actions
Sunrisepeak Nov 5, 2026
6a726f7
fix: keep CLion completion and native views usable during analysis
Sunrisepeak Nov 5, 2026
c5aed8e
fix: start native CLion debug sessions and repair stable CI setup
Sunrisepeak Nov 5, 2026
4bb1868
test: verify native CLion breakpoints stepping and stop
Sunrisepeak Nov 5, 2026
9f8b486
test: check native debugger values and keyboard completion
Sunrisepeak Nov 5, 2026
95a6a1c
fix: load and validate settings in stable CLion dialogs
Sunrisepeak Nov 5, 2026
5708351
fix: report graph query failures and verify stable CLion rendering
Sunrisepeak Nov 5, 2026
c160dc9
fix: preserve the selected configuration in debugger launches
Sunrisepeak Nov 6, 2026
eb3d10c
build: align IDE payloads with the public OpenKal 0.24 group
Sunrisepeak Nov 6, 2026
22ca0b2
ci: align the project shim with the published bootstrap
Sunrisepeak Nov 6, 2026
c213907
ci: record sanitized CLion IDE error fingerprints
Sunrisepeak Nov 6, 2026
4d5ade8
ci: classify stock CLion debugger stop only after functional checks
Sunrisepeak Nov 6, 2026
08fa6ca
ci: publish verified CLion release to JetBrains Marketplace
Sunrisepeak Nov 6, 2026
74c6ca7
test: assert verified Marketplace release jobs
Sunrisepeak Nov 6, 2026
1a51bb1
fix(release): upload JetBrains artifact once per release
Sunrisepeak Nov 6, 2026
ec437d3
feat(ide): notify once when an active action needs missing mcpp
Sunrisepeak Nov 6, 2026
d493f53
fix(vscode): keep missing-tool notice out of autosave builds
Sunrisepeak Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 235 additions & 0 deletions .agents/docs/2026-11-05-clion-backend-location-and-stable-runtime.md

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions .agents/docs/2026-11-06-debuggee-launch-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Debug launches from the selected build configuration

Status, 2026-11-06: implemented in the current core and IDE worktrees. Local
core/backend controls and compilation against both stable CLion SDKs pass.
Final public-group CI, packaged IDE runtime and release qualification remain
separate requirements.

## Responsibility and selection

The core describes the program that `run` or `test --print-launch` actually
selected. The additive `configuration` object contains its triple, profile,
resolved platform and available named runners. The runner names remain
available when a project uses no active platform. This avoids querying a
default configuration separately or taking the first leg of a target-set
environment report, neither of which identifies the program selected to run.

The IDE first asks for that ordinary launch. If it declares `debuggee` and the
request did not explicitly choose a runner, the IDE asks for that named runner
with the selected triple and profile. A different triple, profile or platform
in the second answer is refused. An explicit runner, including an explicit
empty/default runner, takes precedence. A device action named `debug` is not
interpreted as a host-debugger launch. Older cores that omit the additive
configuration retain their ordinary behavior.

The core assigns no OpenKal meaning to `debuggee`. Platform 0.24 declares
`okrun --debugger` through the existing named-runner mechanism. `mcpp test`
accepts `--runner` through the same resolver as `run`, including launch
descriptions and archive execution. A missing name or a simultaneous
`--no-runner` is refused. The core feature version is 2026.11.6.1.

## Editor behavior

Custom profile names are passed with `--profile`; the CLion run-configuration
profile selector accepts typed values. CLion obtains the backend's debug
configuration for its selected native debugger. The adapter enables LLDB's
GDB JIT reader in that session before loading the target. It does not change
a user's global debugger initialization file. Other runners keep their own
semantics and do not implicitly promise source symbols.

The backend describes both program and individual-test debug launches. The
existing CLion debug executor remains limited to program run configurations;
no CLion test-debug UI is claimed by the backend protocol tests.

## Validation and current boundaries

The actual protocol test drives the mock executable through the backend's
wire. It covers older-core fallback, ordinary board actions, target-set
selection, a custom profile, program and test paths, explicit runner
precedence, missing configuration and changed target/profile/platform refusal.
It also checks space-containing and empty arguments.

A separate native integration starts the actual backend with the newly built
core and a real project. Eleven checks cover ordinary and debug launches for
both a program and a test: actual profile and runner declarations, unchanged
configuration, no execution while describing a launch, execution of the
returned argv, argument preservation and explicit default-runner precedence.
It uses no mock and does not stand in for CLion GUI or JIT qualification.

The fresh core passes E2E 01, 333, 993 and 936. E2E 993 includes the actual
named runner, archive execution, missing-name and contradictory-option
controls. E2E 936 initially inherited `NO_COLOR=1`; removing that external
setting passes the unchanged color-precedence checks. The core build uses
the OS 0.3 candidate source path and is not final public dependency evidence.

The changed Kotlin adapters compile against CLion 2026.1.5.2 and 2026.2.4.1.
Compilation does not establish packaged runtime behavior. Image source
debugging still requires retained debug relocations and discoverable JIT
registration symbols in the packaged runner. The source-runner GDB/LLDB
controls and the precompiled-runner packaging qualification are separate.
The independently reproduced CLion 2026.2 native frame-variable cancellation
error remains separate from these changes and is not suppressed.
2 changes: 2 additions & 0 deletions .github/actions/setup-mcpp/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ runs:
shell: bash
run: |
set -euo pipefail
python=python3; [ "$RUNNER_OS" = Windows ] && python=python
"$python" .github/scripts/check-bootstrap-pin.py
while IFS='=' read -r name value; do
case "$name" in ''|\#*) continue ;; esac
if [ -z "${!name:-}" ]; then echo "$name=$value" >> "$GITHUB_ENV"; fi
Expand Down
11 changes: 11 additions & 0 deletions .github/scripts/check-bootstrap-pin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Check the project's shim pin against the declared CI bootstrap before installation."""
import json
from pathlib import Path

values = dict(line.split('=', 1) for line in Path('.github/versions.env').read_text().splitlines()
if line and not line.startswith('#'))
declared = values['MCPP_VERSION']
project = json.loads(Path('.xlings.json').read_text())['workspace']['mcpp']
if project != declared:
raise SystemExit(f'bootstrap mismatch: .xlings.json selects {project}, versions.env installs {declared}')
print(f'project shim and CI bootstrap both select mcpp {declared}')
98 changes: 98 additions & 0 deletions .github/scripts/publish-jetbrains.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Submit one verified release archive to the documented Marketplace update API."""
import argparse
import hashlib
import io
import json
import os
from pathlib import Path
import re
import time
import urllib.error
import urllib.request
import uuid
import xml.etree.ElementTree as ET
import zipfile


def validate(archive, xml_id, version, sha256):
data = archive.read_bytes()
assert hashlib.sha256(data).hexdigest() == sha256, 'Release archive digest differs'
assert re.fullmatch(r'[A-Za-z0-9_.-]+', xml_id)
assert re.fullmatch(r'\d+(?:\.\d+)+(?:[-+][A-Za-z0-9.-]+)?', version)
assert re.fullmatch(r'[A-Za-z0-9_.-]+\.(?:zip|jar)', archive.name)
descriptors = []
with zipfile.ZipFile(io.BytesIO(data)) as outer:
for name in outer.namelist():
if not name.endswith('.jar'):
continue
with zipfile.ZipFile(io.BytesIO(outer.read(name))) as jar:
if 'META-INF/plugin.xml' in jar.namelist():
root = ET.fromstring(jar.read('META-INF/plugin.xml'))
descriptors.append((root.findtext('id'), root.findtext('version')))
assert descriptors == [(xml_id, version)], descriptors
return data


def submit(data, archive, xml_id, token, receipt):
boundary = 'mcpp-marketplace-' + uuid.uuid4().hex
prefix = ('--' + boundary + '\r\nContent-Disposition: form-data; name="xmlId"\r\n\r\n'
+ xml_id + '\r\n--' + boundary
+ '\r\nContent-Disposition: form-data; name="file"; filename="' + archive.name
+ '"\r\nContent-Type: application/zip\r\n\r\n').encode()
body = prefix + data + ('\r\n--' + boundary + '--\r\n').encode()
request = urllib.request.Request('https://plugins.jetbrains.com/api/updates/upload', body,
headers={'Authorization': 'Bearer ' + token,
'Content-Type': 'multipart/form-data; boundary=' + boundary}, method='POST')
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, request, stream, code, message, headers, new_url):
return None
try:
with urllib.request.build_opener(NoRedirect).open(request, timeout=120) as response:
raw = response.read().decode(errors='replace').replace(token, '<redacted>')
receipt['http'] = response.status
except urllib.error.HTTPError as error:
raw = error.read().decode(errors='replace').replace(token, '<redacted>')
receipt.update(http=error.code, accepted=False, error=raw[:4000])
return False
except (urllib.error.URLError, TimeoutError) as error:
receipt.update(accepted=False, result_uncertain=True,
error='Network error during the single upload; inspect Marketplace before retrying',
error_type=type(error).__name__)
return False
try:
result = json.loads(raw)
except json.JSONDecodeError:
result = {'text': raw[:4000]}
receipt['marketplace_response'] = result
receipt['accepted'] = receipt['http'] in (200, 201, 202) and not (isinstance(result, dict) and result.get('error'))
return receipt['accepted']


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--archive', required=True, type=Path)
for name in ('xml-id', 'version', 'sha256'):
parser.add_argument('--' + name, required=True)
parser.add_argument('--output', required=True, type=Path)
parser.add_argument('--verify-only', action='store_true')
args = parser.parse_args()
data = validate(args.archive, args.xml_id, args.version, args.sha256)
receipt = dict(xml_id=args.xml_id, version=args.version, archive=args.archive.name,
sha256=args.sha256, bytes=len(data), verified=True,
utc=time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
if args.verify_only:
receipt['upload_attempted'] = False
success = True
else:
token = os.environ.get('JETBRAINS_TOKEN')
assert token, 'JETBRAINS_TOKEN is required'
receipt['upload_attempted'] = True
success = submit(data, args.archive, args.xml_id, token, receipt)
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(receipt, indent=2) + '\n')
print(json.dumps(receipt))
return 0 if success else 1


if __name__ == '__main__':
raise SystemExit(main())
10 changes: 3 additions & 7 deletions .github/versions.env
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
# The versions CI builds and tests with, in one file; .github/actions/setup-mcpp exports them.
# The released mcpp CI installs and uses: it states its features (`features`, the project's mcpp of
# part 2 §4.3), test-launch among them (2026.10.31.2), and builds the backend's ok images on
# openkal-platform 0.23.3, which it compiles the std module for with the platform's words
# (2026.11.3.1). The mock replays the control contract the fixtures were captured from
# (protocol/fixtures/version.txt).
MCPP_VERSION=2026.11.3.1
# The published bootstrap; the project explicitly selects platform 0.24.
MCPP_VERSION=2026.11.5.1
# A ref of mcpp-community/mcpp CI builds mcpp from and uses instead, for a contract not yet released.
# Empty: the release above is used.
MCPP_SOURCE_REF=
# The LLVM the backend builds with on macOS and Windows (the targets mcpp reaches there with it).
LLVM_VERSION=23.1.3
# The xlings mcpp 2026.11.3.1 bundles, which reads the current index.
# The xlings mcpp 2026.11.5.1 bundles, which reads the current index.
XLINGS_VERSION=v2026.10.26.1
# The oldest VS Code the extension supports (engines.vscode), which its tests run in.
VSCODE_VERSION=1.91.0
21 changes: 20 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
- { os: ubuntu-24.04-arm, platform: linux-arm64, llvm: 'false' }
- { os: macos-15, platform: darwin-arm64, llvm: 'true' }
- { os: windows-2022, platform: win32-x64, llvm: 'true' }
- { os: ubuntu-24.04, platform: linux-x64, llvm: 'false', host-platform: 'openkal-platform@0.23.3' }
- { os: ubuntu-24.04, platform: linux-x64, llvm: 'false', host-platform: 'openkal-platform@0.24.0' }
defaults:
run:
shell: bash
Expand Down Expand Up @@ -454,6 +454,17 @@ jobs:
unzip -l plugin.jar | tee jar.txt | grep -q "webui/files.txt"
for f in $(unzip -p plugin.jar webui/files.txt); do grep -q " webui/$f$" jar.txt; done
ls -l build/distributions/*.zip
- name: The Marketplace submission would name this verified plugin
run: |
set -euo pipefail
version="$(sed -n 's/^version *= *"\(.*\)"/\1/p' mcpp.toml | head -1)"
archive="mide-plugins/clion/build/distributions/mcpp-clion-$version.zip"
test -f "$archive"
digest="$(sha256sum "$archive" | cut -d ' ' -f1)"
python3 .github/scripts/publish-jetbrains.py --verify-only \
--archive "$archive" --xml-id io.github.mcpp-community.mcpp-clion \
--version "$version" --sha256 "$digest" \
--output "$RUNNER_TEMP/jetbrains-archive-verification.json"
# The plugin's icon and its tool window's are the mcpp logo, an embedded image (design 2026-10-29
# §10.4), not text; the VS Code extension's logo is mcpp-vscode 0.9.0's, byte for byte.
- name: The icons are the mcpp logo
Expand All @@ -471,6 +482,14 @@ jobs:
name: mcpp-clion-zip
path: mide-plugins/clion/build/distributions/*.zip

# Both stable families load the exact same packaged ZIP. These are actual
# IDE startup/session checks, in addition to the static Plugin Verifier.
clion-stable:
needs: clion
uses: ./.github/workflows/clion-stable.yml
with:
plugin_artifact: mcpp-clion-zip

# The Neovim plugin (mide-plugins/nvim) in a headless Neovim, against the backend built here: over
# the mock mcpp on the oldest and the newest Neovim it supports, then over the real mcpp, which
# builds the test's project. Neovim comes from xlings (`nvim` in xim-pkgindex), each version
Expand Down
135 changes: 135 additions & 0 deletions .github/workflows/clion-stable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: CLion stable runtime

on:
workflow_call:
inputs:
plugin_artifact:
type: string
required: true
workflow_dispatch:
inputs:
release_tag:
description: Published plugin release to install without rebuilding the backend
type: string
default: v0.2.0

permissions:
contents: read

env:
XLINGS_NON_INTERACTIVE: '1'

jobs:
runtime:
name: packaged plugin on CLion ${{ matrix.version }}
runs-on: ubuntu-24.04
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
version: ['2026.1.5.2', '2026.2.4.1']
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-mcpp
- name: Gradle and a virtual display
run: |
xlings install gradle -y -g
sudo apt-get update -qq
sudo apt-get install -y xvfb
- name: The compiler for actual build, run and test actions
run: |
mcpp toolchain install gcc 16.1.0
mcpp toolchain default gcc@16.1.0
- uses: actions/cache@v5
with:
path: ~/.gradle/caches/modules-2
key: clion-stable-runtime-${{ hashFiles('mide-plugins/clion/integration-tests/*.gradle.kts') }}
- name: The candidate ZIP built once by CI
if: inputs.plugin_artifact != ''
uses: actions/download-artifact@v5
with:
name: ${{ inputs.plugin_artifact }}
path: stable-plugin
- name: The published ZIP for an isolated release check
if: inputs.plugin_artifact == ''
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.release_tag || 'v0.2.0' }}
run: |
set -euo pipefail
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
mkdir -p stable-plugin
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" \
--pattern 'mcpp-clion-*.zip' --dir stable-plugin
- name: The pinned language-service plugin
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
version="$(sed -n 's/^mcpplsPluginVersion *= *//p' mide-plugins/clion/gradle.properties)"
gh release download "v$version" --repo Sunrisepeak/mcpp-language-server \
--pattern "mcppls-clion-$version-linux-x64.zip" --dir stable-plugin
- name: Start the real IDE and complete the plugin startup chain
env:
IDE_VERSION: ${{ matrix.version }}
run: |
set -euo pipefail
plugin="$(find stable-plugin -maxdepth 1 -name 'mcpp-clion-*.zip' -print)"
language="$(find stable-plugin -maxdepth 1 -name 'mcppls-clion-*.zip' -print)"
test -n "$plugin" && test -n "$language"
# Ubuntu's userns AppArmor restriction prevents JCEF's sandbox from
# creating its browser. Permit userns only for this hosted UI check;
# Chromium/JCEF sandbox flags and the tests remain unchanged.
original=$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns)
[[ "$original" =~ ^[01]$ ]]
printf '%s\n' "$original" > "$RUNNER_TEMP/clion-userns-policy"
restore_userns_policy() {
local status=$?
trap - EXIT
if ! sudo sysctl -w "kernel.apparmor_restrict_unprivileged_userns=$original" >/dev/null ||
[ "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns)" != "$original" ]; then
echo '::error::Could not restore the original user namespace policy'
exit 1
fi
exit "$status"
}
trap restore_userns_policy EXIT
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 >/dev/null
unshare -Ur true
xvfb-run -a gradle --no-daemon --max-workers=4 \
-p mide-plugins/clion/integration-tests test \
-PpluginZip="$GITHUB_WORKSPACE/$plugin" \
-PmcpplsZip="$GITHUB_WORKSPACE/$language" \
-PideVersion="$IDE_VERSION" \
-Poutput="$RUNNER_TEMP/clion-stable-result"
- name: Restore the hosted user namespace policy
if: always()
run: |
set -euo pipefail
state="$RUNNER_TEMP/clion-userns-policy"
if [ -f "$state" ]; then
original=$(cat "$state")
[[ "$original" =~ ^[01]$ ]]
sudo sysctl -w "kernel.apparmor_restrict_unprivileged_userns=$original" >/dev/null
test "$(cat /proc/sys/kernel/apparmor_restrict_unprivileged_userns)" = "$original"
fi
# The report contains public versions and assertions only. Private user
# logs and arbitrary IDE process output are not uploaded as artifacts.
- uses: actions/upload-artifact@v5
if: always()
with:
name: clion-stable-${{ matrix.version }}
path: |
${{ runner.temp }}/clion-stable-result/result.json
${{ runner.temp }}/clion-stable-result/checks.json
${{ runner.temp }}/clion-stable-result/ide-errors.ndjson
${{ runner.temp }}/clion-stable-result/settings/result.json
${{ runner.temp }}/clion-stable-result/settings/observations.json
${{ runner.temp }}/clion-stable-result/graphs/result.json
${{ runner.temp }}/clion-stable-result/graphs/checks.json
${{ runner.temp }}/clion-stable-result/graphs-no-jcef/result.json
${{ runner.temp }}/clion-stable-result/graphs-no-jcef/checks.json
if-no-files-found: warn
Loading
Loading