diff --git a/en/clice/design/command-resolve.md b/en/clice/design/command-resolve.md index 7b4ab6131..4d422d70c 100644 --- a/en/clice/design/command-resolve.md +++ b/en/clice/design/command-resolve.md @@ -135,6 +135,8 @@ CDB loading uses simdjson for streaming JSON parsing, processing entries one by 6. Parse and classify each option once into the structured configuration, absolutizing relative include paths against `directory` 7. Deduplicate identical configurations into shared `ConfigID`s +A database written inside Cygwin or MSYS2, by CMake run from their shell or a tool like it, spells its paths POSIX-style: `/cygdrive/c/proj` or `/c/proj`, and `/usr/bin/c++.exe` for the compiler. On Windows, the mount table of the Cygwin or MSYS2 runtime that wrote them maps them to Windows paths: the entry's directory and file, its compiler and the values of its path options. That runtime is the first on `PATH` whose table maps the first such entry's directory, and its compiler when it is spelled so, to files that exist: another runtime's table maps the directory under its own root, and Git for Windows' MSYS2, which maps an MSYS2 installation's drive paths alike, lacks its `/usr/bin/c++.exe`. Its `command` strings split as sh splits them. When no runtime on `PATH` maps the database, it is not read, and the user is told to put the right one there. + The parsing process also recognizes the precompiled headers CMake passes to cc1 directly: a unit's `-Xclang -include-pch -Xclang ` and the `-Xclang -emit-pch` of the entry building it become structured options that no compile renders. The PCH on disk may come from another compiler, so clice builds its own from the command of the entry writing it (CMake's `cmake_pch.hxx.cxx`, also found through the `.gch` GCC writes beside a forced header), and every unit reading that PCH loads clice's build instead: the open file's preamble PCH chains onto it, and the background index parses over it. A header the PCH holds, opened on its own, compiles without it: the PCH's guards would skip the header's own text. The forced header the PCH stands for goes from its command too, as any forced include the compile enters a header through does (see [Compilation Context](compilation-context.md#automatic-self-containedness-detection)). ### Toolchain Probing @@ -148,6 +150,8 @@ Different compiler families use different probing strategies: Asked with the command's own flags, the driver answers for the command, and a GCC answers only when that compile succeeds: a flag that switches one of its defaults off (`-fno-stack-protector`), a `--sysroot` or a `--specs` file shows in its answer. CPU and ABI names clang does not know (GCC's `-mtune=intel`, `-mabi=lp64` on Darwin) are dropped from the cc1 command, and GCC's two-core form `-mcpu=cortex-a72.cortex-a53` keeps its first core. A universal build (`-arch arm64 -arch x86_64`) is read for its first architecture. +**Cygwin and MSYS2 compilers** on Windows (Cygwin's and MSYS2's own GCC, and toolchains built on Cygwin such as Andes' RISC-V GCC) answer as any GCC-style driver does, but in POSIX paths (`/usr/include`, `/cygdrive/c/...`), which clice's clang cannot open. The mount table of the runtime the driver loads maps them, read from the runtime's own `mount` for each probe. Of the runtimes (`cygwin1.dll`, `msys-2.0.dll`) beside the driver and on `PATH`, in the order Windows looks for a program's DLLs, that is the first whose table maps the program of the driver's job to a file that exists: taking the first alone would pick Git for Windows' MSYS2 where it comes ahead on `PATH` of the Cygwin a toolchain outside it loads. An assumed mapping would get them wrong, since Cygwin mounts `/usr/lib` at `/lib`, MSYS2 its drives at `/c`, and a user's `/etc/fstab` adds mounts of its own. The driver itself is found as Cygwin finds it: by a name without `.exe`, and through a Cygwin symlink (CMake records `/usr/bin/cc`, a link to `gcc.exe`). Such a compiler starts only with its runtime's `bin` directory on `PATH`; without it, the query fails with an error that says so, and [Editor Setup](../guide/editors.md#compiler-environment) sets it per editor. + **MSVC / ClangCL**: cl or clang-cl is asked once to preprocess (`/EP`) a file naming `_MSVC_LANG` and `_MSC_FULL_VER`. They tell the C++ standard it compiles in, since what `/std:c++latest` means moves from one version to the next, and cl's version, where clang's own detection would take the newest toolset installed. Clang's driver in MSVC-compatible mode (`--driver-mode=cl`) then builds the cc1 command in that standard, with cl's version as `-fms-compatibility-version`. clang-cl's version is the one that same detection finds from the toolset its flags select (`/winsysroot`). cl hears only the command's `/std:`: the other flags are spelled for clang's cl mode, which cl rejects, and a standard or version the command sets in clang's spelling (a rule appending `-std=c++23`) stays clang's. **nvcc**: nvcc's dry run names the CUDA root, the host compiler and the macros nvcc defines. The host compiler is asked as a GCC-style driver is, in the input's language, with the command's `--sysroot` but none of its other flags: a GCC host pins the target and the search list as above, a clang host needs nothing. Clang's driver then builds the cc1 command with nvcc's macros, in CUDA mode for a CUDA input. @@ -194,6 +198,10 @@ After the four tiers are concatenated, deduplication begins from the Angled tier Compiler driver behavior is affected by the name used to invoke it. For example, `/usr/bin/clang++` is typically a symlink to `/usr/lib/llvm-20/bin/clang`, but invoking it as `clang++` automatically enables C++ mode and links C++ libraries. Using `realpath` to resolve to the actual path before identification would lose the semantic information carried by the invocation name. Similarly, if `arm-none-eabi-g++` were resolved to some generic GCC binary path, the cross-compilation context would be lost. +- **Why are no paths mapped from Windows to POSIX for a Cygwin compiler?** + + The compiler runs only to answer how it compiles, on an empty file of the command's language, and a Cygwin program takes Windows paths as arguments itself. clice compiles with its own clang, which opens the Windows paths the editor names: no path clice hands the compiler, and no diagnostic clice reports, passes through the POSIX side. Only the compiler's answers and a database written inside Cygwin are POSIX, and those are mapped. + - **How are files without a CDB entry handled?** A header first looks for a source file that includes it through the dependency graph and borrows that file's command (see [Compilation Context](compilation-context.md)). Otherwise the first matching rule with a `default_command` supplies the command — the way to describe a project whose files all share one set of flags, or a scratch directory. A file with none of those borrows a nearby unit's command (see [Inference](#inference)); only one with no compatible unit either gets the builtin command: `clang` or `clang++ -std=c++20` based on the file's language, with the resource dir injected, so basic semantic analysis remains available and a guidance note explains that the command was guessed. Saving such a source in the editor makes it a [provisional member](#provisional-members). @@ -204,6 +212,8 @@ After the four tiers are concatenated, deduplication begins from the Angled tier - **Only the editor's saves add files to the build.** A source that appears on disk any other way — a checkout, a copy in a file manager, a generator — and that no database lists borrows a command for the editor but stays outside the build (not indexed, providing no module, hosting no header) until it is saved in the editor once or a regenerated database lists it. Provisional members are recorded per build configuration. +- **Cygwin symlinks among the headers are not followed.** A header Cygwin installs as a symlink (ncurses' `ncurses.h` in its `/usr/include`) is a file only Cygwin programs follow: clice's clang reads the link itself. Only the driver is followed through such a link. + - **Incomplete support for some compiler families.** A driver that answers the probe with neither a clang job nor a GCC one -- Intel's classic `icc`, other vendors' compilers -- gets clang's own defaults for the running machine, and the toolchain error says so. NVCC has dedicated probing (parsing `nvcc --dryrun` output into a clang CUDA invocation), but only with a GCC or Clang host compiler -- nvcc driving MSVC `cl` is not supported, and multi-architecture commands parse the newest architecture only. - **GCC-only language parses as clang sees it.** Code that only GCC compiles -- GCC-only builtins and extensions, `__float128` on targets where clang lacks it, nested functions -- shows clang's errors, and a CPU name only GCC knows loses the macros of its features. The headers only GCC's private include directory holds (`omp.h`, `quadmath.h`) are not found: that directory comes with GCC's versions of clang's builtin headers. diff --git a/en/clice/guide/editors.md b/en/clice/guide/editors.md index 8ffa8e8a0..04ad0f067 100644 --- a/en/clice/guide/editors.md +++ b/en/clice/guide/editors.md @@ -7,6 +7,24 @@ All setups assume: - The `clice` executable is on your `PATH` (or use an absolute path in the snippets below). - Your project provides a `compile_commands.json` (by default clice searches the workspace root, then each of its immediate subdirectories). +## Compiler Environment + +clice asks the compiler of each command how it compiles (its target, its system headers, its default standard), and runs it in the environment the editor starts clice with. A compiler that needs more gets it from the editor's setting for clice's environment: a Cygwin or MSYS2 compiler on Windows, for one, needs its runtime's `bin` directory on `PATH`. + +- VS Code: `clice.serverEnv`, whose values name VS Code's own variables as `${env:NAME}`, as `terminal.integrated.env.*` does: + + ```json + { "clice.serverEnv": { "PATH": "C:\\cygwin64\\bin;${env:PATH}" } } + ``` + +- Neovim: `cmd_env`: + + ```lua + vim.lsp.config('clice', { cmd_env = { PATH = 'C:\\cygwin64\\bin;' .. vim.env.PATH } }) + ``` + +- Zed: `lsp.clice.binary.env`, whose values are taken as written. + ## Official Plugins ### Visual Studio Code diff --git a/zh/clice/design/command-resolve.md b/zh/clice/design/command-resolve.md index 1a7206abf..f57fbb008 100644 --- a/zh/clice/design/command-resolve.md +++ b/zh/clice/design/command-resolve.md @@ -135,6 +135,8 @@ CDB 加载使用 simdjson 进行流式 JSON 解析,逐条处理记录: 6. 将每个选项解析、分类一次并存入结构化配置,同时以 `directory` 为基准将相对包含路径转换为绝对路径 7. 对相同配置进行去重,使其共享 `ConfigID` +在 Cygwin 或 MSYS2 内部写出的数据库(由在它们的 shell 中运行的 CMake 或类似的工具写出)按 POSIX 风格书写路径:`/cygdrive/c/proj` 或 `/c/proj`,编译器则写作 `/usr/bin/c++.exe`。在 Windows 上,写出这些路径的 Cygwin 或 MSYS2 运行时用它的挂载表(mount table)把它们映射为 Windows 路径,包括条目的目录和文件、它的编译器,以及它的路径选项的值。这个运行时是 `PATH` 上第一个满足以下条件的运行时:它的挂载表能把首个此类条目的目录映射到实际存在的文件,该条目的编译器若也按这种风格书写,同样要映射到实际存在的文件。其他运行时的挂载表会把该目录映射到各自的根目录之下;Git for Windows 的 MSYS2 对盘符路径的映射虽与独立安装的 MSYS2 相同,却没有后者的 `/usr/bin/c++.exe`。这种数据库的 `command` 字符串按 sh 的规则拆分。如果 `PATH` 上没有运行时能映射该数据库,clice 不读取它,并提示用户把正确的运行时加入其中。 + 解析过程还会识别 CMake 直接传给 cc1 的预编译头文件:翻译单元的 `-Xclang -include-pch -Xclang `,以及构建该 PCH 的条目中的 `-Xclang -emit-pch`,都会成为结构化选项,任何编译都不会把它们渲染到命令中。磁盘上的 PCH 可能出自其他编译器,因此 clice 会用写出它的那个条目的命令自行构建一份(在 CMake 中即 `cmake_pch.hxx.cxx` 的条目;GCC 在强制包含的头文件旁写出的 `.gch` 同样能用来找到该条目),读取该 PCH 的每个翻译单元都改为加载 clice 构建的版本:打开文件的 Preamble PCH 以链式 PCH 的方式接在它之后,后台索引也在它之上解析。PCH 所含的头文件单独打开时,编译不使用该 PCH:否则 PCH 中的头文件保护会跳过该头文件自身的正文。与编译借以进入头文件的其他强制包含一样,PCH 所代表的那个强制包含的头文件也会从命令中去掉(见[编译上下文](compilation-context.md#automatic-self-containedness-detection))。 ### 工具链探测 @@ -148,6 +150,8 @@ CDB 加载使用 simdjson 进行流式 JSON 解析,逐条处理记录: 询问时带着命令自身的标志,所以驱动程序回答的就是这条命令,而 GCC 只在这次编译成功时才会给出回答:关闭某项默认设置的标志(`-fno-stack-protector`)、`--sysroot` 或 `--specs` 文件都会体现在回答中。clang 不认识的 CPU 和 ABI 名称(GCC 的 `-mtune=intel`、Darwin 上的 `-mabi=lp64`)会从 cc1 命令中去掉,GCC 的双核写法 `-mcpu=cortex-a72.cortex-a53` 只保留第一个核。通用构建(`-arch arm64 -arch x86_64`)按其中第一个架构读取。 +**Cygwin 和 MSYS2 编译器**:Windows 上的这类编译器(Cygwin 和 MSYS2 自带的 GCC,以及基于 Cygwin 构建的工具链,例如 Andes 的 RISC-V GCC)像任何 GCC 风格的驱动程序一样作答,只是给出的是 POSIX 路径(`/usr/include`、`/cygdrive/c/...`),clice 的 clang 无法打开。这些路径由驱动程序加载的运行时用其挂载表映射,每次探测都从该运行时自己的 `mount` 读取挂载表。在驱动程序旁边和 `PATH` 上的运行时(`cygwin1.dll`、`msys-2.0.dll`)中,按 Windows 查找程序 DLL 的顺序,取第一个能把驱动程序所跑作业的程序映射到实际存在的文件的那个:只取第一个运行时的话,若 Git for Windows 的 MSYS2 在 `PATH` 上排在工具链所加载的、位于其外的 Cygwin 之前,就会选错。假定一套映射会出错:Cygwin 把 `/usr/lib` 挂载在 `/lib`,MSYS2 把驱动器挂载在 `/c` 这样的路径上,用户的 `/etc/fstab` 还会加上自己的挂载。驱动程序本身按 Cygwin 的方式查找:名称可以不带 `.exe`,也可以经由 Cygwin 的符号链接找到(CMake 记录的是 `/usr/bin/cc`,它是指向 `gcc.exe` 的链接)。这样的编译器只有在其运行时的 `bin` 目录位于 `PATH` 中时才能启动;否则查询会失败,错误信息会说明这一点,[编辑器设置](../guide/editors.md#compiler-environment)介绍了如何在各个编辑器中设置它。 + **MSVC / ClangCL**:先让 cl 或 clang-cl 预处理(`/EP`)一个写着 `_MSVC_LANG` 和 `_MSC_FULL_VER` 的文件,只问一次。由此得知它编译所用的 C++ 标准,因为 `/std:c++latest` 的含义随版本而变;还得知 cl 的版本,因为 clang 自己的探测会取已安装的最新工具集。随后由处于 MSVC 兼容模式(`--driver-mode=cl`)的 Clang 驱动构建 cc1 命令,使用这个标准,并把 cl 的版本作为 `-fms-compatibility-version`。clang-cl 的版本则是同一套探测从它的参数所选的工具集(`/winsysroot`)中得出的版本。cl 只会收到命令里的 `/std:`:其余参数是按 clang 的 cl 模式写的,cl 会拒绝;命令用 clang 写法设定的标准或版本(例如规则追加的 `-std=c++23`)仍以 clang 的读法为准。 **nvcc**:nvcc 的 dry run 会给出 CUDA 根目录、宿主编译器以及 nvcc 定义的宏。宿主编译器按 GCC 风格驱动程序的方式询问,使用输入文件的语言,带上命令的 `--sysroot`,但不带命令的其他标志:GCC 宿主编译器像上文那样固定目标和搜索列表,clang 宿主编译器则什么都不需要。随后由 Clang 驱动带上 nvcc 的宏构建 cc1 命令,输入为 CUDA 文件时使用 CUDA 模式。 @@ -194,6 +198,10 @@ CDB 加载使用 simdjson 进行流式 JSON 解析,逐条处理记录: 编译器驱动的行为受调用名称影响。例如,`/usr/bin/clang++` 通常是 `/usr/lib/llvm-20/bin/clang` 的符号链接,但以 `clang++` 名义调用时会自动启用 C++ 模式并链接 C++ 库。如果在识别前用 `realpath` 解析到实际路径,会丢失调用名称携带的语义信息。类似地,如果 `arm-none-eabi-g++` 被解析成某个通用 GCC 二进制的路径,交叉编译的上下文就会丢失。 +- **为什么对 Cygwin 编译器不把路径从 Windows 映射到 POSIX?** + + 编译器运行只是为了回答它如何编译,处理的是命令所用语言的一个空文件;而且 Cygwin 程序本身就接受 Windows 路径作为参数。clice 用自己的 clang 编译,它直接打开编辑器给出的 Windows 路径:clice 交给编译器的路径、clice 报告的诊断,都不经过 POSIX 一侧。只有编译器的回答和在 Cygwin 内部写出的数据库使用 POSIX 路径,这两者都会被映射。 + - **没有 CDB 条目的文件怎么处理?** 头文件会先通过依赖图寻找包含它的源文件,并借用该文件的命令(参见 [编译上下文](compilation-context.md))。否则,由第一条带 `default_command` 的匹配规则提供命令——要描述所有文件共用同一套标志的项目,或是一个临时目录,用的就是这种方式。这些都没有的文件会借用附近翻译单元的命令(参见[推断](#inference));只有连可借用的翻译单元也没有的文件才使用内置命令:按文件的语言选择 `clang` 或 `clang++ -std=c++20`,并注入资源目录(resource dir),这样基本的语义分析仍然可用,同时会有一条提示说明该命令是猜测得到的。在编辑器中保存这样的源文件,会让它成为[临时成员](#provisional-members)。 @@ -204,6 +212,8 @@ CDB 加载使用 simdjson 进行流式 JSON 解析,逐条处理记录: - **只有编辑器中的保存才会把文件加入构建。** 以其他方式出现在磁盘上的源文件——检出、在文件管理器中复制、由生成器产生——如果没有数据库列出它,会为编辑器借用一条命令,但仍留在构建之外(不被索引、不提供模块、不作为头文件的宿主),直到它在编辑器中被保存一次,或重新生成的数据库列出了它。临时成员按构建配置分别记录。 +- **不跟随头文件中的 Cygwin 符号链接。** Cygwin 以符号链接形式安装的头文件(例如 ncurses 的 `ncurses.h`,位于 Cygwin 的 `/usr/include` 中)只有 Cygwin 程序才会跟随:clice 的 clang 读到的是链接本身。clice 只对驱动程序跟随这种链接。 + - **部分编译器家族支持不完整。** 对探测既不给出 clang 任务、也不给出 GCC 任务的驱动程序——Intel 的经典编译器 `icc`、其他厂商的编译器——会使用 clang 针对当前机器的默认设置,工具链错误会说明这一点。NVCC 已有专门的探测逻辑(解析 `nvcc --dryrun` 输出并将其转换为 clang CUDA 调用),但仅支持以 GCC 或 Clang 作为宿主编译器——不支持由 nvcc 驱动 MSVC `cl`,多架构命令也只解析最新的架构。 - **只有 GCC 接受的语言特性按 clang 的理解解析。** 只有 GCC 能编译的代码——GCC 专有的内建函数和扩展、在 clang 不支持它的目标上使用 `__float128`、嵌套函数——会显示 clang 的报错;只有 GCC 认识的 CPU 名称会丢失其特性对应的宏。只存在于 GCC 私有 include 目录中的头文件(`omp.h`、`quadmath.h`)找不到:那个目录里同时还有 GCC 版本的 clang 内置头文件。 diff --git a/zh/clice/guide/editors.md b/zh/clice/guide/editors.md index 8fc158778..c252a551e 100644 --- a/zh/clice/guide/editors.md +++ b/zh/clice/guide/editors.md @@ -7,6 +7,24 @@ clice 实现了 [Language Server Protocol](https://microsoft.github.io/language- - `clice` 可执行文件位于 `PATH` 中(或在下面的代码片段中使用绝对路径)。 - 你的项目提供 `compile_commands.json`(clice 默认先搜索工作区根目录,再搜索其每个直接子目录)。 +## 编译器环境 + +clice 会询问每条命令的编译器如何编译(它的目标、系统头文件和默认标准),并在编辑器启动 clice 时所用的环境中运行编译器。需要更多环境的编译器,可以通过编辑器中设置 clice 环境的选项获得:例如,Windows 上的 Cygwin 或 MSYS2 编译器需要其运行时的 `bin` 目录位于 `PATH` 中。 + +- VS Code:`clice.serverEnv`,其值用 `${env:NAME}` 引用 VS Code 自身的环境变量,与 `terminal.integrated.env.*` 的写法相同: + + ```json + { "clice.serverEnv": { "PATH": "C:\\cygwin64\\bin;${env:PATH}" } } + ``` + +- Neovim:`cmd_env`: + + ```lua + vim.lsp.config('clice', { cmd_env = { PATH = 'C:\\cygwin64\\bin;' .. vim.env.PATH } }) + ``` + +- Zed:`lsp.clice.binary.env`,其值按原样使用。 + ## 官方插件 ### Visual Studio Code