lint: AST rules fail on any file that #includes a standard header #46

Open
opened 2026-10-31 23:02:46 +00:00 by jorijnvdgraaf · 0 comments

Summary

crafter-build lint cannot produce an AST for a file that textually includes a C++ standard header: #include <algorithm>, or a vendored header-only library that does, such as nlohmann/json. Every rule registered with AddAstLintRule then fails on that file and the run exits 1, although the same file builds. libclang is never given libc++'s include directory, which the clang driver finds relative to its own binary.

Line numbers refer to a3fe1bd.

Details

  • implementations/Crafter.Build-Lint.cpp:754 builds libclang's arguments from the file's compile command through CommandToArgs, which drops argv[0], the compiler (:367). :759 adds -resource-dir= from clang++ -print-resource-dir (:712), and :774 calls clang_parseTranslationUnit.
  • The driver puts libc++'s headers on the include path relative to the clang++ it runs as. In the Flatpak, clang++ -stdlib=libc++ -E -v -x c++ /dev/null lists /app/bin/../include/c++/v1 first, then /app/lib/clang/23/include. libclang never learns where that binary is, so the libc++ directory is missing. The explicit -resource-dir covers the builtin headers such as stddef.h, not libc++.
  • Passing the directory by hand confirms it: with -isystem/app/bin/../include/c++/v1 added to the configuration's compileFlags, the reproduction below lints clean and exits 0.
  • The failure says "Build the project first so the module PCMs exist, or pass --no-ast to skip these rules" (:1331). That misleads here, because the PCMs exist. And --no-ast turns the rules off for every file, not only for the ones that fail.

Found in nostro: its four files that include lib/json.hpp fail this way under Crafter.LintRules' lite rules, so its lint can never exit 0. Seen with the Flatpak's stable branch (4.0.0, clang 23); the code above is unchanged on a3fe1bd.

Reproduction

project.cpp:

import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;

extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
    Configuration cfg;
    cfg.path = "./";
    cfg.name = "repro";
    cfg.outputName = "repro";
    ApplyStandardArgs(cfg, args);
    std::array<fs::path, 0> interfaces = {};
    std::array<fs::path, 1> implementations = {"main"};
    cfg.GetInterfacesAndImplementations(interfaces, implementations);
    cfg.AddAstLintRule("reads-ast", [](LintContext& ctx) { (void)ctx.Decls(); });
    return cfg;
}

main.cpp:

#include <algorithm>

int main() {
    return 0;
}

crafter-build builds it. crafter-build lint then prints this and exits 1:

lint: 1 rule(s) needing an AST (reads-ast) could not run on 1 of 2 file(s).
      main.cpp: 'algorithm' file not found
      Build the project first so the module PCMs exist, or pass --no-ast to skip these rules.
Lint clean: 2 files, 1 rules

Suggested fix

Keep the compiler as argv[0] and parse with clang_parseTranslationUnit2FullArgv, so the driver derives its installation directory from the real clang++, the way the build does. That probably makes the explicit -resource-dir unnecessary as well. Alternatively, ask the driver once for its libc++ include directory, the way ClangResourceDir() asks for the resource directory, and pass it as -isystem.

## Summary `crafter-build lint` cannot produce an AST for a file that textually includes a C++ standard header: `#include <algorithm>`, or a vendored header-only library that does, such as nlohmann/json. Every rule registered with `AddAstLintRule` then fails on that file and the run exits 1, although the same file builds. libclang is never given libc++'s include directory, which the clang driver finds relative to its own binary. Line numbers refer to `a3fe1bd`. ## Details - `implementations/Crafter.Build-Lint.cpp:754` builds libclang's arguments from the file's compile command through `CommandToArgs`, which drops argv[0], the compiler (`:367`). `:759` adds `-resource-dir=` from `clang++ -print-resource-dir` (`:712`), and `:774` calls `clang_parseTranslationUnit`. - The driver puts libc++'s headers on the include path relative to the `clang++` it runs as. In the Flatpak, `clang++ -stdlib=libc++ -E -v -x c++ /dev/null` lists `/app/bin/../include/c++/v1` first, then `/app/lib/clang/23/include`. libclang never learns where that binary is, so the libc++ directory is missing. The explicit `-resource-dir` covers the builtin headers such as `stddef.h`, not libc++. - Passing the directory by hand confirms it: with `-isystem/app/bin/../include/c++/v1` added to the configuration's `compileFlags`, the reproduction below lints clean and exits 0. - The failure says "Build the project first so the module PCMs exist, or pass --no-ast to skip these rules" (`:1331`). That misleads here, because the PCMs exist. And `--no-ast` turns the rules off for every file, not only for the ones that fail. Found in nostro: its four files that include `lib/json.hpp` fail this way under Crafter.LintRules' lite rules, so its lint can never exit 0. Seen with the Flatpak's stable branch (4.0.0, clang 23); the code above is unchanged on `a3fe1bd`. ## Reproduction `project.cpp`: ```cpp import std; import Crafter.Build; namespace fs = std::filesystem; using namespace Crafter; extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) { Configuration cfg; cfg.path = "./"; cfg.name = "repro"; cfg.outputName = "repro"; ApplyStandardArgs(cfg, args); std::array<fs::path, 0> interfaces = {}; std::array<fs::path, 1> implementations = {"main"}; cfg.GetInterfacesAndImplementations(interfaces, implementations); cfg.AddAstLintRule("reads-ast", [](LintContext& ctx) { (void)ctx.Decls(); }); return cfg; } ``` `main.cpp`: ```cpp #include <algorithm> int main() { return 0; } ``` `crafter-build` builds it. `crafter-build lint` then prints this and exits 1: ``` lint: 1 rule(s) needing an AST (reads-ast) could not run on 1 of 2 file(s). main.cpp: 'algorithm' file not found Build the project first so the module PCMs exist, or pass --no-ast to skip these rules. Lint clean: 2 files, 1 rules ``` ## Suggested fix Keep the compiler as argv[0] and parse with `clang_parseTranslationUnit2FullArgv`, so the driver derives its installation directory from the real `clang++`, the way the build does. That probably makes the explicit `-resource-dir` unnecessary as well. Alternatively, ask the driver once for its libc++ include directory, the way `ClangResourceDir()` asks for the resource directory, and pass it as `-isystem`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Catcrafts/Crafter.Build#46
No description provided.