diff --git a/AUTHORING_EXTRACTOR_SKILL.md b/AUTHORING_EXTRACTOR_SKILL.md new file mode 100644 index 0000000..98412e6 --- /dev/null +++ b/AUTHORING_EXTRACTOR_SKILL.md @@ -0,0 +1,170 @@ +# Configuring the MSBuild Extractor for Custom Build Systems + +[`microsoft/msbuild-extractor-sample`](https://github.com/microsoft/msbuild-extractor-sample) +generates a `compile_commands.json` (a clang compilation database) from MSBuild C++ projects so +C++ language services can initialize their LSP server and provide code intelligence. For a normal +`.sln`/`.vcxproj` built with an installed Visual Studio it auto-detects the toolchain through +`vswhere.exe` and usually needs no configuration. Other MSBuild setups can still need configuration +(for example multiple Visual Studio installs, a specific platform toolset or SDK, or projects that +must be restored first); the `generate-compile-commands` skill covers those. The setup below is for +a **custom** toolchain: a +hermetic or vendored toolset that ships inside the repo and is activated by a wrapper script, +which the automatic detection won't find on its own. + +## Signs you need a configuration skill + +You probably need one if the default setup does not just work for your repo. The first sign is +visible up front, before you run anything; the rest show up after extraction: + +- Up front, in how the repo builds: it vendors its own toolchain (a checked-in or + package-restored compiler, Windows SDK, and MSBuild/targets) and builds through a wrapper or + environment-activation script rather than an installed Visual Studio, so stock `vswhere` + auto-detection will not match it. +- The extractor finishes but produces an empty or nearly empty `compile_commands.json`. +- Your editor shows red squiggles and errors on code that builds fine normally. +- IntelliSense can't find standard headers, or seems to use the wrong compiler version. +- The optional `--validate` check (it runs the real compiler on the extracted commands) fails on + sources that build fine in your normal build, with a toolchain error rather than an error in + your own code. +- The extractor seems to hang or takes a very long time on a large project. + +These usually mean the extractor found a different toolchain than the one your repo actually +builds with. The fix is to point it at your repo's own build tools (out-of-process mode), which +the example below shows how to do. + +To verify, inspect the generated `compile_commands.json` before assuming the toolchain is wrong: + +- Check it is not empty: it should have roughly one entry per translation unit. In PowerShell, + `(Get-Content .mscppls\compile_commands.json -Raw | ConvertFrom-Json).Count`. +- Open one entry and confirm its `command`/`arguments` reference the compiler you actually build + with (your repo's `cl.exe`, not a system Visual Studio one) and include your project's include + paths and defines. +- In the CLI, run `/lsp show` to confirm the `cpp` server is configured, then ask a navigation + question (for example where a symbol is defined); if it resolves, the database is being used. + +## Settings to capture + +| Flag | Why | +|---|---| +| `--msbuild-path` | Run the repo's own MSBuild out-of-process so its targets and SDK resolve | +| `--vc-targets-path` | Pin the vendored VC targets (use an absolute path) | +| `--cl-path` | Pin the same compiler the repo builds with (use an absolute path) | +| `--use-dev-env` | Read toolchain paths from an activated developer environment instead of relying on `vswhere.exe` auto-detection | +| `-c` / `-a` | The repo's configuration and platform | +| `--msbuild-property BuildProjectReferences=false` | Skip deep reference graphs that otherwise hang | +| `--msbuild-property KEY=VALUE` | Any repo-specific property the build needs | + +Point the extractor at a real `.sln`, `.slnx`, or `.vcxproj` file. + +## Designing the SKILL + +A skill is a folder with a `SKILL.md`: YAML frontmatter (`name`, `description`) plus a body with +the pinned extraction command and any project lookup. Keep it generic and document only what is +different about your repo. The `description` is what makes the assistant auto-invoke the skill, +so phrase it with the words users type (generate, regenerate, or refresh compile commands; set +up C++ IntelliSense). + +## What to put in your SKILL + +These keep the generated database matched to your real build, so encode them in your `SKILL.md`. + +Bake into the extraction command: + +- Use absolute paths for `--vc-targets-path` and `--cl-path`. +- Prefer out-of-process mode (`--msbuild-path`) in a vendored repo; when you have an activated developer environment (for example EWDK), `--use-dev-env` can also pin extraction to the intended toolchain. +- Point at a `.sln` or a specific `.vcxproj`, not a generated or aggregate project file. + +Capture as prerequisites or notes in the skill body: + +- Restore first if the project's targets come from a package: `msbuild /t:restore`. +- Omit `--validate` for IntelliSense setup; it recompiles every file and is slow. +- Always regenerate fresh; never reuse a stale `compile_commands.json`. +- Pin the extractor version and verify its SHA256 (releases ship a self-contained single-file exe). + +## Example: Windows driver samples with the EWDK + +[`microsoft/Windows-driver-samples`](https://github.com/microsoft/Windows-driver-samples) built +with the **Enterprise WDK (EWDK)** is a fully public version of this case. The EWDK is a free, +self-contained command-line toolchain (an ISO you mount) that bundles MSBuild, the compiler, +the Windows SDK, and the WDK, and is activated by `LaunchBuildEnv.cmd`. Because it ships its +own tools, point the extractor at the EWDK explicitly. + +The driver projects pull their build logic in through `$(VCTargetsPath)` (the +`WindowsKernelModeDriver10.0` toolset) and reference no NuGet packages, so the EWDK already +provides everything and there is no `/t:restore` step. + +Where things live (EWDK mounted at `D:\`, repo at `C:\src\Windows-driver-samples`). Folder names +track the EWDK release, so confirm them after mounting. This example is build 28000.1839, which +ships Visual Studio 18 Build Tools, MSVC 14.50.35717, and WDK 10.0.28000.0: + +| Item | Path | +|---|---| +| Activation wrapper | `D:\LaunchBuildEnv.cmd` | +| MSBuild | `D:\Program Files\Microsoft Visual Studio\18\BuildTools\MSBuild\Current\Bin\MSBuild.exe` | +| VC targets | `D:\Program Files\Microsoft Visual Studio\18\BuildTools\MSBuild\Microsoft\VC\v180` | +| Compiler | `D:\Program Files\Microsoft Visual Studio\18\BuildTools\VC\Tools\MSVC\14.50.35717\bin\Hostx64\x64\cl.exe` | +| Example solution | `C:\src\Windows-driver-samples\general\echo\kmdf\kmdfecho.sln` | + +The reliable way to run it is from an activated EWDK shell, so the extractor uses the EWDK +toolchain instead of any Visual Studio that also happens to be installed on the machine. + +A skill that captures it: + +````markdown +--- +name: wdk-samples-generate-compile-commands +description: Generate compile_commands.json for the Windows driver samples built with the EWDK. Use when setting up C++ IntelliSense or extracting compile commands from WDK .vcxproj/.sln files. +--- + +# Generate compile_commands.json for the Windows driver samples (EWDK) + +The EWDK is self-contained and bundles the WDK, so there is no NuGet restore step. Run from an +activated EWDK shell and let the extractor read the toolchain from the environment. + +## Prerequisites + +- Mount the EWDK ISO (for example as `D:`) and start its shell: `D:\LaunchBuildEnv.cmd`. +- Confirm the toolchain is active: `where cl.exe` resolves under the mounted EWDK. + +## Extraction command + +From the activated EWDK shell, in the repo root: + +```powershell +.tools\msbuild-extractor-sample.exe ` + --use-dev-env ` + --solution general\echo\kmdf\kmdfecho.sln ` + -c Debug -a x64 ` + -o compile_commands.json +``` + +`--use-dev-env` reads the toolchain environment the EWDK exports (`VCToolsInstallDir`, +`VCTargetsPath`, `WindowsSdkDir`, `INCLUDE`, `LIB`), which pins extraction to the EWDK. + +## Alternative: explicit paths + +If you cannot use an activated shell, pin the EWDK tools by hand. On a machine that also has +Visual Studio installed, prefer the activated shell so the extractor does not pick up the system +toolchain. + +```powershell +.tools\msbuild-extractor-sample.exe ` + --msbuild-path "D:\Program Files\Microsoft Visual Studio\18\BuildTools\MSBuild\Current\Bin\MSBuild.exe" ` + --solution general\echo\kmdf\kmdfecho.sln ` + -c Debug -a x64 ` + --vc-targets-path "D:\Program Files\Microsoft Visual Studio\18\BuildTools\MSBuild\Microsoft\VC\v180" ` + --cl-path "D:\Program Files\Microsoft Visual Studio\18\BuildTools\VC\Tools\MSVC\14.50.35717\bin\Hostx64\x64\cl.exe" ` + -o compile_commands.json +``` + +## Notes + +- The EWDK bundles the WDK, so the driver `.vcxproj` files resolve their targets without a NuGet restore. +- The version folders (`18`, `v180`, `14.50.35717`, `10.0.28000.0`) track the EWDK build; confirm them after mounting. +- Point at the `.sln` or a specific `.vcxproj`, not a generated or aggregate project file. +```` + +### Invoking the skill to generate compile_commands.json + +- "Generate compile_commands.json for the kmdfecho driver sample" +- "Set up C++ IntelliSense for the Windows driver samples using the EWDK" diff --git a/README.md b/README.md index 0015269..7a47fe6 100644 --- a/README.md +++ b/README.md @@ -4,48 +4,33 @@ _The Microsoft C++ Language Server is currently in preview and may be subject to **Microsoft C++ Language Server** brings the same C++ code intelligence used in Visual Studio and VS Code to GitHub Copilot CLI on Windows, macOS, and Linux. It provides fast, accurate understanding of C++ codebases with features like symbol search and semantic navigation. +# Prerequisites + +Before you get started, make sure you have: + +- An active [GitHub Copilot](https://github.com/features/copilot) subscription +- [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli/set-up-copilot-cli/install-copilot-cli) +- [npm](https://www.npmjs.com/package/npm?activeTab=readme) + # πŸš€ Quick start -1. Run `npm install -g @microsoft/cpp-language-server`. -2. Run `mscppls --accept-eula --login` to accept the [license terms](https://www.npmjs.com/package/@microsoft/cpp-language-server?activeTab=code) and login to GitHub. An active GitHub Copilot subscription is required. -3. Create a [`compile_commands.json` file](https://clang.llvm.org/docs/JSONCompilationDatabase.html) for your project. For CMake projects, try [adding `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON`](https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html) during configuration, which will create a `compile_commands.json` in the CMake binary (output) directory, or use [this skill](./skills/setup-cpp-language-server/SKILL.md). For MSBuild (vcxproj) projects, see [this sample application](https://github.com/microsoft/msbuild-extractor-sample) to extract `compile_commands.json` from C++ MSBuild projects. -4. [Create `.github/lsp.json` to configure GitHub Copilot CLI](https://github.com/github/copilot-cli?tab=readme-ov-file#-configuring-lsp-servers) to use the language server. +1. Install the [`cpp-language-server` plugin from the copilot-plugins marketplace](https://github.com/github/copilot-plugins). From within GitHub Copilot CLI, run: -```json -{ - "lspServers": { - "cpp": { - "command": "mscppls", - "args": ["--lsp-config", ".mscppls/cpp-lsp.json"], - "fileExtensions": { - ".cpp": "cpp", - ".cxx": "cpp", - ".c": "cpp", - ".cc": "cpp", - ".hpp": "cpp", - ".hxx": "cpp", - ".hh": "cpp", - ".h": "cpp" - }, - "requestTimeoutMs": 1000000 - } - } -} -``` + ```text + /plugin install cpp-language-server@copilot-plugins + ``` -5. Create `.mscppls/cpp-lsp.json` to set the path to `compile_commands.json` and the project root directory. All paths in this example are relative to `.mscppls`. Adjust the `compileCommands` path to match your project's build output directory. + This bundles the language server and auto-updates with the latest version, so you don't need to install the npm package manually. -```json -{ - "version": 1, - "repositoryPath": "../", - "compileCommands": "../build/compile_commands.json" -} -``` +2. Run `npx @microsoft/cpp-language-server --accept-eula --login` to accept the [license terms](https://www.npmjs.com/package/@microsoft/cpp-language-server?activeTab=code) and login to GitHub. An active GitHub Copilot subscription is required. +3. Create a [`compile_commands.json` file](https://clang.llvm.org/docs/JSONCompilationDatabase.html) for your project. If your CMake or MSBuild (vcxproj) project doesn't already have a `compile_commands.json`, run the [`generate-compile-commands` skill](./plugins/cpp-language-server/skills/generate-compile-commands/SKILL.md) in GitHub Copilot CLI with a prompt like "regenerate compile commands" or "load project" to generate one and configure the language server. If you don't want to use the skill, you can [add `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON`](https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html) during configuration for CMake projects, or follow [this sample application](https://github.com/microsoft/msbuild-extractor-sample) to generate the file for MSBuild projects. -6. Launch GitHub Copilot CLI from your project root directory. -7. Within GitHub Copilot CLI, run `/lsp show`. You should see a "cpp" server running. -8. Use GitHub Copilot CLI like normal, now with enhanced C++ capabilities. To nudge the agent to use the tools, try adding phrases like "use LSP tools" to your prompt. + > [!NOTE] + > For custom build systems, we recommend [following the guidance provided to generate a project-specific skill](./AUTHORING_EXTRACTOR_SKILL.md) so the steps are reproducible for your team across each build. + +4. Launch GitHub Copilot CLI from your project root directory. +5. Within GitHub Copilot CLI, run `/lsp show`. You should see a "cpp" server running. +6. Use GitHub Copilot CLI like normal, now with enhanced C++ capabilities. To nudge the agent to use the tools, try adding phrases like "use LSP tools" to your prompt. # πŸ“’ Reporting feedback @@ -91,26 +76,94 @@ server will prompt with alternative options. # Logging in to GitHub -Before using the Microsoft C++ Language Server, you must accept the end-user license agreement (EULA), which is distributed as [`EULA/LICENSE.txt`](https://www.npmjs.com/package/@microsoft/cpp-language-server?activeTab=code) in the npm package. Accept the agreement by running `mscppls --accept-eula`. You only need to accept the EULA once. +Before using the Microsoft C++ Language Server, you must accept the end-user license agreement (EULA), which is distributed as [`EULA/LICENSE.txt`](https://www.npmjs.com/package/@microsoft/cpp-language-server?activeTab=code) in the npm package. Accept the agreement by running `npx @microsoft/cpp-language-server --accept-eula` (or `mscppls --accept-eula` if installed directly). You only need to accept the EULA once. -Using the Microsoft C++ Language Server requires an active GitHub Copilot subscription. Before using the language server for the first time, log in to GitHub by running `mscppls --login` and following the on-screen instructions. +Using the Microsoft C++ Language Server requires an active GitHub Copilot subscription. Before using the language server for the first time, log in to GitHub by running `npx @microsoft/cpp-language-server --login` (or `mscppls --login` if installed directly) and following the on-screen instructions. ## Alternative authentication methods -By default, the language server stores your GitHub tokens in system secret storage. On Linux, this requires `libsecret`. If you are running the language server in a constrained environment where system secret storage is unavailable, run `mscppls --login --allow-plaintext-secret-storage` to allow storing the tokens in plaintext. +By default, the language server stores your GitHub tokens in system secret storage. On Linux, this requires `libsecret`. If you are running the language server in a constrained environment where system secret storage is unavailable, run `npx @microsoft/cpp-language-server --login --allow-plaintext-secret-storage` (or `mscppls --login --allow-plaintext-secret-storage` if installed directly) to allow storing the tokens in plaintext. Alternatively, you can independently generate a GitHub token (such as a PAT) and save it in the `MSCPPLS_GITHUB_TOKEN` environment variable. The token does not need to have any scopes. +### Using the GitHub CLI (GitHub Enterprise Cloud) + +If no token is found in system secret storage or the `MSCPPLS_GITHUB_TOKEN` environment variable, the language server will fall back to the [GitHub CLI](https://cli.github.com/) and run `gh auth token` to obtain a token for the current session. This requires the GitHub CLI (`gh`) to already be installed and on your `PATH`, and that you have authenticated with it (for example, via `gh auth login`). The token is used only for the current session and is not stored by the language server. + +This is the recommended way to use the language server with **GitHub Enterprise Cloud**. Authenticate the GitHub CLI against your enterprise host and then point the language server at the same host with `--login-hostname`: + +``` +gh auth login --hostname myenterprise.ghe.com +mscppls --login-hostname myenterprise.ghe.com +``` + +The `--login-hostname` value is passed through to `gh auth token --hostname `. If omitted, the GitHub CLI's default host (`github.com`) is used. Classic personal access tokens (those beginning with `ghp_`) returned by the GitHub CLI are rejected. + +`--login-hostname` can be added to the `args` in `lsp.json` to pass it every time the language server launches. + # Configuration The Microsoft C++ Language Server depends on 3 configuration files: -1. `.github/lsp.json` [configures GitHub Copilot CLI](https://github.com/github/copilot-cli?tab=readme-ov-file#-configuring-lsp-servers) to use the Microsoft C++ Language Server for C++ files, and sets the command line arguments passed to `mscppls`. See the quick start example for how to create this file. -2. `.mscppls/cpp-lsp.json` sets the path to the project root and the path to the `compile_commands.json` file. See the quick start example for how to set the `repositoryPath` and `compileCommands` properties in this file. `repositoryPath` and `compileCommands` can be relative or absolute paths. If relative, they are resolved relative to the directory containing `cpp-lsp.json`. By changing the `--lsp-config` argument in `.github/lsp.json`, `cpp-lsp.json` can be stored at any user-defined path. `version` must always be `1`. +1. `.github/lsp.json` [configures GitHub Copilot CLI](https://github.com/github/copilot-cli?tab=readme-ov-file#-configuring-lsp-servers) to use the Microsoft C++ Language Server for C++ files, and sets the command line arguments passed to `mscppls`. +2. `.mscppls/cpp-lsp.json` is optional and sets the path to the project root and the path to the `compile_commands.json` file. `repositoryPath` and `compileCommands` can be relative or absolute paths. If relative, they are resolved relative to the directory containing `cpp-lsp.json`. By changing the `--lsp-config` argument in `.github/lsp.json`, `cpp-lsp.json` can be stored at any user-defined path. `version` must always be `1`. 3. [`compile_commands.json` specifies the command line to build each target](https://clang.llvm.org/docs/JSONCompilationDatabase.html) in the project. Details for how to generate this file depend on the build system the project uses. By default, logs and caches are stored in a workspace-specific directory under `$TEMP/mscppls`. The log directory can be overridden with the `--log-dir` argument. +## Customizing Copilot CLI launch flags + +To customize the command-line flags that GitHub Copilot CLI passes to the Microsoft C++ Language Server, edit [`.github/lsp.json`](https://github.com/github/copilot-cli?tab=readme-ov-file#-configuring-lsp-servers). + +The server can be launched with additional flags using either: + +- `npx @microsoft/cpp-language-server [additional flags]` +- `mscppls [additional flags]` (if installed globally via npm) + +If you install the package globally, npm adds the `mscppls` executable to your `PATH`. + +## Minimal configuration (automatic discovery) + +If you omit the `--lsp-config` argument (or pass `--allow-missing-lsp-config`), the language server automatically searches each workspace folder for a single `compile_commands.json` and infers the repository root from the workspace folder. In this mode you don't need a `cpp-lsp.json` file or a `repositoryPath`: + +```json +{ + "lspServers": { + "cpp": { + "command": "mscppls", + "args": [], + "fileExtensions": { + ".cpp": "cpp", + ".c": "cpp", + ".h": "cpp", + ".hpp": "cpp" + }, + "requestTimeoutMs": 1000000 + } + } +} +``` + +The discovered configuration is cached, so later startups skip the search. Discovery fails if no `compile_commands.json` is found or if more than one is found; in that case, use an explicit `cpp-lsp.json`. + +## Excluding files and customizing indexed directories + +The language server always parses and indexes every translation unit listed in `compile_commands.json`. These translation units are **not** affected by the exclude settings below. On top of that, the directories containing those translation units are scanned to discover additional files (such as headers) to index for browsing and symbol search, and this scan applies a small set of default excludes. The settings in `cpp-lsp.json` customize only this additional directory scan: + +- **Add exclude globs** via `filesExclude` (hides files from the browse/symbol-index scan) and `searchExclude` (excludes files from search). These apply only to the directory scan; they do not remove the translation units listed in `compile_commands.json`. Your entries merge on top of the defaults; set a pattern to `false` to un-exclude a default. +- **Include additional directories** in the scan via `browse.path`. The scanned directories are always derived from the `compile_commands.json` entries; `browse.path` specifies further directories to index/parse on top of those. Entries support `${workspaceFolder}` substitution (resolves to `repositoryPath`). + +```json +{ + "version": 1, + "repositoryPath": "../", + "compileCommands": "../build/compile_commands.json", + "filesExclude": { "**/out": true, "**/third_party": true }, + "searchExclude": { "**/*.generated.h": true }, + "browse": { "path": ["${workspaceFolder}/src"] } +} +``` + ## Creating compile_commands.json for CMake-based projects CMake projects that use the Ninja or Makefile generators can easily create a `compile_commands.json` by setting the [`CMAKE_EXPORT_COMPILE_COMMANDS` variable](https://cmake.org/cmake/help/latest/variable/CMAKE_EXPORT_COMPILE_COMMANDS.html) during configuration. For CMake projects that typically use other generators, first check if it is possible as a one-off to configure the project using the Ninja or Makefile generator, as this is the easiest way to get `compile_commands.json`. @@ -127,21 +180,25 @@ For now, refer to [this sample application](https://github.com/microsoft/msbuild Refer to your build system vendor's documentation. +For custom or non-standard builds, we recommend capturing the steps to generate `compile_commands.json` in a project-specific skill so the process is reproducible for you and your team. Once you've worked out the commands needed to produce the file, save them as a skill (for example, in your project's `skills/` directory) following the [GitHub Copilot CLI skills documentation](https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/create-skills). The [`setup-cpp-language-server` skill](./skills/setup-cpp-language-server/SKILL.md) is a good starting template to adapt. + # Command line options for `mscppls` -| Option | Description | -| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--help` or `-h` | Shows a help message with a description of all options. | -| `--version` | Shows version information and exits. | -| `--accept-eula` | Permanently accepts the end-user license agreement (EULA). The EULA only needs to be accepted once. However, it is safe to redundantly include this option with any other option, which can be helpful in automated environments. | -| `--stderr` | Do not redirect stderr to `/dev/null`. Useful for debugging. | -| `--login` | Interactively log in to GitHub. | -| `--force-login` | Force interactive login, even if the user has already logged in. | -| `--allow-plaintext-secret-storage` | Allow storing credentials in plaintext if secure system storage is unavailable. | -| `--log-dir ` | Specify the directory for log files. If unspecified, the system temp directory is used. | -| `--log-level ` | Set the logging verbosity from 0 (errors only) to 9 (verbose). | -| `--lsp-config ` | Path to the `cpp-lsp.json` file. If relative, the path will be resolved relative to the current working directory of the `mscppls` process, which is typically the project root when launched from GitHub Copilot CLI. | -| `--disable-telemetry` | Permanently disables sending telemetry data. | +| Option | Description | +| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--help` or `-h` | Shows a help message with a description of all options. | +| `--version` | Shows version information and exits. | +| `--accept-eula` | Permanently accepts the end-user license agreement (EULA). The EULA only needs to be accepted once. However, it is safe to redundantly include this option with any other option, which can be helpful in automated environments. | +| `--stderr` | Do not redirect stderr to `/dev/null`. Useful for debugging. | +| `--login` | Interactively log in to GitHub. | +| `--force-login` | Force interactive login, even if the user has already logged in. | +| `--allow-plaintext-secret-storage` | Allow storing credentials in plaintext if secure system storage is unavailable. | +| `--login-hostname ` | GitHub hostname to use when obtaining a token from the GitHub CLI (`gh auth token`). Enables GitHub Enterprise Cloud support; requires the GitHub CLI to be installed and authenticated. If unspecified, the GitHub CLI's default host is used. | +| `--log-dir ` | Specify the directory for log files. If unspecified, the system temp directory is used. | +| `--log-level ` | Set the logging verbosity from 0 (errors only) to 9 (verbose). | +| `--lsp-config ` | Optional path to the `cpp-lsp.json` file. If relative, the path will be resolved relative to the current working directory of the `mscppls` process, which is typically the project root when launched from GitHub Copilot CLI. | +| `--allow-missing-lsp-config` | Enables fallback to automatic `compile_commands.json` discovery when `--lsp-config` is specified but the configuration file does not exist. | +| `--disable-telemetry` | Permanently disables sending telemetry data. | # Supported LSP features @@ -165,6 +222,7 @@ Refer to your build system vendor's documentation. | Go to Declaration | βœ… | | | Go to Type Definition | βœ… | | | Call Hierarchy | βœ… | | +| Pull Diagnostics | βœ… | | # Data collection diff --git a/plugins/cpp-language-server/.github/plugin/plugin.json b/plugins/cpp-language-server/.github/plugin/plugin.json index 6dd636e..364f693 100644 --- a/plugins/cpp-language-server/.github/plugin/plugin.json +++ b/plugins/cpp-language-server/.github/plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft-07/schema", "name": "cpp-language-server", "description": "Navigate and interact with your C++ code using the Microsoft C++ Language Server.", - "version": "0.1.0", + "version": "0.1.1", "license": "See EULA/LICENSE.txt in @microsoft/cpp-language-server NPM package", "repository": "https://github.com/microsoft/cpp-language-server", "keywords": [ @@ -13,7 +13,9 @@ "lsp", "language-server" ], - "skills": [], + "skills": [ + "./skills/generate-compile-commands" + ], "lspServers": { "cpp": { "command": "npx", diff --git a/plugins/cpp-language-server/lsp.json b/plugins/cpp-language-server/lsp.json new file mode 100644 index 0000000..78aa42c --- /dev/null +++ b/plugins/cpp-language-server/lsp.json @@ -0,0 +1,24 @@ +{ + "lspServers": { + "cpp": { + "command": "npx", + "args": [ + "@microsoft/cpp-language-server" + ], + "env": { + "MSCPPLS_PLUGIN_SOURCE": "public" + }, + "fileExtensions": { + ".cpp": "cpp", + ".cxx": "cpp", + ".c": "cpp", + ".cc": "cpp", + ".hpp": "cpp", + ".hxx": "cpp", + ".hh": "cpp", + ".h": "cpp" + }, + "requestTimeoutMs": 1000000 + } + } +} \ No newline at end of file diff --git a/plugins/cpp-language-server/skills/generate-compile-commands/SKILL.md b/plugins/cpp-language-server/skills/generate-compile-commands/SKILL.md new file mode 100644 index 0000000..5a8ad22 --- /dev/null +++ b/plugins/cpp-language-server/skills/generate-compile-commands/SKILL.md @@ -0,0 +1,187 @@ +--- +name: generate-compile-commands +description: Generate compile_commands.json (clang compilation database) for the C++ language server. Covers MSBuild (.sln, .slnx, .vcxproj) via microsoft/msbuild-extractor-sample and CMake projects via CMAKE_EXPORT_COMPILE_COMMANDS. Use whenever the user asks to "regenerate compile commands", "regenerate the project", "reload the project", "load the project", "refresh compile commands", or otherwise needs a fresh compilation database for mscppls. +--- + +# Generate compile_commands.json + +The C++ language server needs a `compile_commands.json` (clang compilation database) to understand a project. How you produce one depends on the build system: CMake can export it directly, MSBuild needs an external extractor. + +Always regenerate `compile_commands.json` when this skill runs, even if one already exists for the project. For MSBuild, write it to `.mscppls\compile_commands.json` at the workspace root (the examples below do this); mscppls auto-discovers any `compile_commands.json` within the workspace, so no `cpp-lsp.json` is required. Add `.mscppls/` to `.gitignore` so it does not get committed. mscppls hot-reloads the file content; `/restart` is only needed after changing the plugin's `lsp.json` or the workspace `cpp-lsp.json` (those are read once at LSP startup). + +## CMake projects + +CMake writes `compile_commands.json` itself; no external extractor needed. Enable it at configure time: + +```bash +cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON +cmake --build build +``` + +Or persist it in the project so every configure picks it up: + +```cmake +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +``` + +The file lands at `/compile_commands.json` (whatever directory you passed to `-B`). Point the workspace `cpp-lsp.json` at it: + +```json +{ + "version": 1, + "repositoryPath": "../", + "compileCommands": "../build/compile_commands.json" +} +``` + +CMake regenerates the file on the next configure (e.g. running `cmake -B build` again, or `cmake --build build` after editing `CMakeLists.txt`). The refresh story for CMake is "re-run cmake configure"; the MSBuild-specific sections below do not apply. + +## MSBuild projects + +For MSBuild (`.sln`, `.slnx`, `.vcxproj`), generate `compile_commands.json` with [`microsoft/msbuild-extractor-sample`](https://github.com/microsoft/msbuild-extractor-sample). Extraction is design-time: it evaluates projects and runs `GetClCommandLines`, it does **not** compile anything. + +### Get the extractor + +Download the pinned release and verify its SHA256 before trusting it. The binary is self-contained, with no .NET runtime needed. + +```powershell +$version = 'v0.2.0' +$expected = '01a5e2d399c3cb84d3136d0b6ef7b84a05900838ddc3b1f710b9394c8c2936ce' +$exe = ".tools\msbuild-extractor-sample.exe" + +if (-not (Test-Path $exe) -or (Get-FileHash $exe -Algorithm SHA256).Hash -ne $expected.ToUpper()) { + New-Item -ItemType Directory -Path ".tools" -Force | Out-Null + $tmp = "$exe.download" + Invoke-WebRequest -Uri "https://github.com/microsoft/msbuild-extractor-sample/releases/download/$version/msbuild-extractor-sample.exe" -OutFile $tmp + $actual = (Get-FileHash $tmp -Algorithm SHA256).Hash + if ($actual -ne $expected.ToUpper()) { + Remove-Item $tmp -Force + throw "SHA256 mismatch`n expected: $expected`n actual: $actual" + } + Move-Item $tmp $exe -Force +} +``` + +When upgrading the pinned version, get the new SHA256 from the `digest` field of the asset at `https://api.github.com/repos/microsoft/msbuild-extractor-sample/releases/latest`. + +### Run it + +For a typical project on a workstation with Visual Studio installed, the extractor auto-detects Visual Studio via `vswhere.exe` and resolves `VCTargetsPath`, `VCToolsInstallDir`, and `cl.exe` automatically. Solution-based extraction is the simplest entry point: + +```powershell +New-Item -ItemType Directory -Path .mscppls -Force | Out-Null +.tools\msbuild-extractor-sample.exe ` + --solution myapp.sln ` + -c Debug -a x64 ` + -o .mscppls\compile_commands.json +``` + +`-c` is `--configuration` (default `Debug`), `-a` is `--platform` (e.g. `x64`, `Win32`, `ARM64`), `-o` is `--output`. `--solution` accepts `.sln` and `.slnx`; it is repeatable for cross-solution work. `--project` works the same way for individual `.vcxproj` files (also repeatable, deduplicates entries). + +#### From a Developer Command Prompt or activated vcvars shell + +If you launched a Developer Command Prompt / Developer PowerShell for VS, or ran `vcvarsall.bat x64`, the toolchain env vars (`VCINSTALLDIR`, `VCToolsInstallDir`, `INCLUDE`, `LIB`, etc.) are already set in the current shell. Pass `--use-dev-env` to make the extractor honor them rather than re-resolving via `vswhere`: + +```powershell +.tools\msbuild-extractor-sample.exe ` + --use-dev-env ` + --solution myapp.sln ` + -c Debug -a x64 ` + -o .mscppls\compile_commands.json +``` + +This guarantees the LSP indexes against the exact same toolchain the developer is building with. Use it whenever the project depends on a specific VS version, SDK, or side-by-side toolchain that the default `vswhere` heuristic might not pick. + +#### Multiple VS installs + +If `vswhere` picks the wrong one, list and select explicitly: + +```powershell +.tools\msbuild-extractor-sample.exe --list-instances +.tools\msbuild-extractor-sample.exe --vs-instance --solution myapp.sln -c Debug -a x64 +``` + +#### Multi-configuration projects + +Prefer the built-in flags over manual merging; they produce one IntelliSense-friendly entry per source file across all configurations: + +```powershell +.tools\msbuild-extractor-sample.exe ` + --solution myapp.sln ` + --all-configurations --merge --deduplicate ` + -o .mscppls\compile_commands.json +``` + +Skip `--validate` during normal LSP setup. It compiles every TU with `cl.exe /c` and is slow. Reach for it only when debugging the extractor itself. + +### Advanced: out-of-process mode for vendored or hermetic-toolchain repos + +Some repos vendor a hermetic toolchain (MSBuild, the C++ compiler, Windows SDK headers, targets files) into the repo itself, for example as NuGet packages, a mounted toolchain ISO, or a `.tools\` directory. Builds are driven by a wrapper or environment-activation script (`build.cmd`, `init.cmd`, `LaunchBuildEnv.cmd`, etc.) that sets `PATH`, `VCTargetsPath`, `INCLUDE`, and `LIB` to point at the vendored versions. A fully public example is [`microsoft/Windows-driver-samples`](https://github.com/microsoft/Windows-driver-samples) built with the Enterprise WDK (EWDK), a self-contained command-line toolchain you mount and activate with `LaunchBuildEnv.cmd`. + +If you let the extractor's default **in-process** MSBuild evaluation run in such a repo: + +- It loads the MSBuild API from the running .NET host, which then probes the system Visual Studio install for targets, not the repo's vendored MSBuild. +- The compile commands point at the system `cl.exe`, which is a different version than the one the repo actually builds with. The LSP then fails to find the right intrinsics, conformance flags, or module `.ifc` files and reports phantom errors. +- Tightly integrated environments may fail outright with `REGDB_E_INVALIDVALUE` because the in-proc API tries to read VS COM registration that the vendored toolchain doesn't provide. + +The fix is **out-of-process mode**: spawn the repo's own MSBuild.exe via `--msbuild-path`, and pin the targets and compiler paths to match what the repo's normal build uses. + +| Flag | Why | +|---|---| +| `--msbuild-path ` | Drive the repo's own MSBuild (e.g. `build.cmd`) out-of-proc, so vendored targets/SDKs resolve correctly | +| `--vc-targets-path ` | Point at the vendored VC targets so MSBuild does not probe a global VS | +| `--cl-path ` | Pin the LSP to the same compiler version the repo builds with. Critical for matching intrinsics, std-lib paths, and module IFCs | +| `--msbuild-property BuildProjectReferences=false` | Skip transitive project-reference evaluation; vendored-toolchain repos often have deep graphs where this otherwise hangs | +| `--msbuild-property =` | Some repos need extra properties to evaluate correctly. Check the repo's build docs | + +Example (the Windows driver samples built with the Enterprise WDK; the version folders track the EWDK build, here `18` / `v180` / `14.50.35717`, so confirm them after mounting): + +```powershell +.tools\msbuild-extractor-sample.exe ` + --msbuild-path "D:\Program Files\Microsoft Visual Studio\18\BuildTools\MSBuild\Current\Bin\MSBuild.exe" ` + --solution general\echo\kmdf\kmdfecho.sln ` + -c Debug -a x64 ` + --vc-targets-path "D:\Program Files\Microsoft Visual Studio\18\BuildTools\MSBuild\Microsoft\VC\v180" ` + --cl-path "D:\Program Files\Microsoft Visual Studio\18\BuildTools\VC\Tools\MSVC\14.50.35717\bin\Hostx64\x64\cl.exe" ` + -o .mscppls\compile_commands.json +``` + +When the toolchain is already activated in the current shell (for example after running the EWDK's `LaunchBuildEnv.cmd`), pass `--use-dev-env` instead of the explicit `--msbuild-path`, `--vc-targets-path`, and `--cl-path`, and the extractor reads those paths from the environment. + +The extractor cannot consume `dirs.proj` or aggregate `.proj` files; point it at a real leaf `.vcxproj` (or `.sln`). + +If the repo needs initialization for its props, targets, and dependencies to resolve, initialize it once, then run the extractor. + +> **Note:** Verify initialization at most once; avoid re-checking the same state (for example a build environment variable) or re-requesting approval in a loop. +> If you can't confirm initialization, initialize the repo (or ask the user once), then run the extractor. Use `--msbuild-path` to select the intended MSBuild for vendored toolchains, but don’t treat it as a substitute for repo initialization. + +### Manual merge fallback + +`--all-configurations --merge --deduplicate` handles almost every multi-config case. Reach for a manual merge only when projects genuinely need conflicting `--platform`, `--configuration`, or `--msbuild-property` flags and have to be extracted in separate invocations. Write each invocation's output to a distinct file under `.mscppls\` (e.g. `.mscppls\compile_commands_debug.json`), then merge: + +```powershell +$all = @{} +Get-ChildItem .mscppls\compile_commands_*.json | ForEach-Object { + (Get-Content $_ -Raw | ConvertFrom-Json) | ForEach-Object { + $key = $_.file.ToLowerInvariant() + if (-not $all.ContainsKey($key)) { $all[$key] = $_ } + } +} +ConvertTo-Json -InputObject @($all.Values) -Depth 6 | Set-Content .mscppls\compile_commands.json -Encoding UTF8 +``` + +`@($all.Values)` forces an array even with a single entry; otherwise `ConvertTo-Json` emits a bare object and the file is not a valid compilation database. + +## Troubleshooting + +These cover the MSBuild extractor path. For CMake, build-system errors come from `cmake` itself. + +| Symptom | Cause | Fix | +|---|---|---| +| `REGDB_E_INVALIDVALUE`, or compile commands point at the wrong `cl.exe` | In-proc MSBuild picked up a system VS install instead of the repo's vendored toolchain | Switch to out-of-process mode with `--msbuild-path` and pin `--vc-targets-path` / `--cl-path` | +| Extractor hangs | Transitive project-reference evaluation on a deep build graph | Add `--msbuild-property BuildProjectReferences=false` | +| `--validate` reports missing module `.ifc` | Module producer not built | Build dependencies first, or just skip `--validate` | +| LSP doesn't see newly extracted entries | mscppls observer is still hashing the new file | Wait a few seconds; mscppls hot-reloads, so do **not** `/restart` | +| `vswhere` picks the wrong VS install | Multiple VS instances present | `--list-instances` then `--vs-instance ` | + +For extractor diagnostics, re-run the same command and read its stderr inline; don't redirect to a file.