Compare commits
30 Commits
folding-ra
...
server-plu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8168577b | ||
|
|
60cab39f92 | ||
|
|
67bedd1ae0 | ||
|
|
7ab8ad9513 | ||
|
|
c11178fd77 | ||
|
|
150f47c590 | ||
|
|
b7987fded3 | ||
|
|
44a4bd4107 | ||
|
|
044e4c4b27 | ||
|
|
68eb63ba04 | ||
|
|
39d3648fdd | ||
|
|
6640c05d66 | ||
|
|
69ac764ef2 | ||
|
|
04c6ca5337 | ||
|
|
2408978d2d | ||
|
|
74f75f107f | ||
|
|
0e2e487bc9 | ||
|
|
6be48bccd2 | ||
|
|
4262734d21 | ||
|
|
ed0d7db3db | ||
|
|
824b305f93 | ||
|
|
f89793c66d | ||
|
|
4425fb5244 | ||
|
|
25a85a3b8e | ||
|
|
0f95344abe | ||
|
|
3511915886 | ||
|
|
6e50451c43 | ||
|
|
99d9363b95 | ||
|
|
7533d4d15e | ||
|
|
a118c16e96 |
@@ -100,7 +100,7 @@ SortIncludes: true
|
|||||||
SortUsingDeclarations: Never
|
SortUsingDeclarations: Never
|
||||||
IncludeBlocks: Regroup
|
IncludeBlocks: Regroup
|
||||||
IncludeCategories:
|
IncludeCategories:
|
||||||
- Regex: '^["<](spdlog|toml\+\+|coraing|cpptrace|flatbuffers|kota)/'
|
- Regex: '^["<](spdlog|toml\+\+|coraing|cpptrace|flatbuffers)/'
|
||||||
Priority: 30
|
Priority: 30
|
||||||
SortPriority: 31
|
SortPriority: 31
|
||||||
|
|
||||||
@@ -139,15 +139,3 @@ KeepEmptyLines:
|
|||||||
AtEndOfFile: false
|
AtEndOfFile: false
|
||||||
AtStartOfBlock: false
|
AtStartOfBlock: false
|
||||||
AtStartOfFile: false
|
AtStartOfFile: false
|
||||||
|
|
||||||
StatementMacros:
|
|
||||||
- DECO_CFG_START
|
|
||||||
- DECO_CFG
|
|
||||||
- DECO_CFG_END
|
|
||||||
- DecoKV
|
|
||||||
- DecoFlag
|
|
||||||
- DecoComma
|
|
||||||
- DecoInput
|
|
||||||
- DecoPack
|
|
||||||
- DecoKVStyled
|
|
||||||
- DecoMulti
|
|
||||||
|
|||||||
50
.clang-tidy
50
.clang-tidy
@@ -1,50 +0,0 @@
|
|||||||
---
|
|
||||||
Checks: >
|
|
||||||
-*,
|
|
||||||
bugprone-*,
|
|
||||||
modernize-*,
|
|
||||||
performance-*,
|
|
||||||
readability-*,
|
|
||||||
-modernize-use-trailing-return-type,
|
|
||||||
-readability-magic-numbers,
|
|
||||||
-readability-else-after-return,
|
|
||||||
-readability-braces-around-statements,
|
|
||||||
-readability-avoid-const-params-in-decls,
|
|
||||||
-readability-named-parameter,
|
|
||||||
-readability-implicit-bool-conversion,
|
|
||||||
-readability-use-anyofallof,
|
|
||||||
-bugprone-easily-swappable-parameters,
|
|
||||||
-bugprone-exception-escape,
|
|
||||||
-bugprone-narrowing-conversions,
|
|
||||||
-modernize-use-nodiscard,
|
|
||||||
|
|
||||||
WarningsAsErrors: ""
|
|
||||||
|
|
||||||
HeaderFilterRegex: "(src|tests)/.*"
|
|
||||||
|
|
||||||
CheckOptions:
|
|
||||||
# Naming conventions matching project style
|
|
||||||
- key: readability-identifier-naming.ClassCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.StructCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.EnumCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.EnumConstantCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.TemplateParameterCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.TypeAliasCase
|
|
||||||
value: CamelCase
|
|
||||||
- key: readability-identifier-naming.FunctionCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.MethodCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.VariableCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.ParameterCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.MemberCase
|
|
||||||
value: lower_case
|
|
||||||
- key: readability-identifier-naming.NamespaceCase
|
|
||||||
value: lower_case
|
|
||||||
@@ -1,268 +0,0 @@
|
|||||||
# clice — Project Guide
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
clice is a next-generation C++ language server (LSP) built on LLVM/Clang, targeting modern C++ (C++20/23). It uses a multi-process architecture with a master server coordinating stateless and stateful workers.
|
|
||||||
|
|
||||||
## Core Correction Patterns — Lessons from Past Interactions
|
|
||||||
|
|
||||||
The following patterns were extracted from extensive real-world collaboration. These are recurring mistakes that MUST be avoided. Read them carefully — they represent hard-won lessons, not hypothetical concerns.
|
|
||||||
|
|
||||||
### Pattern 1: Misjudging Real-World Priorities
|
|
||||||
|
|
||||||
AI tends to optimize whatever metric looks most impressive, rather than what actually matters in the user's real scenario.
|
|
||||||
|
|
||||||
**Example**: During performance optimization, the AI proudly reported "hot cache is 4-7x faster!" — but the function in question runs at LSP server startup, which is ALWAYS a cold start. Optimizing hot cache was completely meaningless.
|
|
||||||
|
|
||||||
**Rule**: Before optimizing or analyzing anything, first understand the REAL usage scenario. Ask yourself: "When does this code actually run? What does the user actually experience?" Do not chase metrics that look good on paper but are irrelevant in practice.
|
|
||||||
|
|
||||||
### Pattern 2: Pushing Without Local Verification
|
|
||||||
|
|
||||||
The most common and most damaging pattern. AI proposes a fix, pushes it immediately, CI fails, then another fix, push, fail again — wasting CI cycles and the user's time.
|
|
||||||
|
|
||||||
**Rule**: NEVER push code that you haven't verified locally. Before every push:
|
|
||||||
|
|
||||||
- Build locally with the same configuration CI uses.
|
|
||||||
- Run the relevant tests locally and confirm they pass.
|
|
||||||
- If you cannot reproduce the CI environment locally, say so — do not just "try and see."
|
|
||||||
- "It compiles" is NOT sufficient. Tests must pass.
|
|
||||||
|
|
||||||
### Pattern 3: Superficial Refactoring
|
|
||||||
|
|
||||||
When asked to refactor, AI tends to do mechanical code movement (copy functions from A to B) without understanding the deeper design intent (ownership, responsibility boundaries, API cleanliness).
|
|
||||||
|
|
||||||
**Example**: When splitting `MasterServer` into `Workspace` and `Session`, the AI moved functions but kept ugly APIs like `f(path_id, sessions_map)` instead of the clean `f(Session&)` that the refactoring was meant to achieve.
|
|
||||||
|
|
||||||
**Rule**: When refactoring, understand the WHY. Ask: "What design problem is this refactoring solving?" If you're just moving code around without improving the abstractions, you're not refactoring — you're rearranging deck chairs.
|
|
||||||
|
|
||||||
### Pattern 4: Fixing Only the Immediate Instance, Not the Pattern
|
|
||||||
|
|
||||||
When given a cleanup instruction, AI applies it to the single file or function currently being discussed, ignoring all other occurrences in the project.
|
|
||||||
|
|
||||||
**Example**: User says "remove decorative `===` comment separators." AI removes them from `workspace.cpp` only. User has to say: "The other files too!"
|
|
||||||
|
|
||||||
**Rule**: When given a cleanup or style instruction, apply it project-wide. Use `Grep` to find ALL occurrences and fix them all in one pass. Think: "Where else does this pattern appear?"
|
|
||||||
|
|
||||||
### Pattern 5: Never Skip, Disable, or Work Around Failing Tests
|
|
||||||
|
|
||||||
When stuck on a difficult bug (especially flaky CI, race conditions, platform-specific issues), AI may propose marking tests as `continue-on-error`, skipping them, or adding `expected-failure` annotations to make CI green.
|
|
||||||
|
|
||||||
**Rule**: This is ABSOLUTELY FORBIDDEN. If a test fails, fix the root cause. There are ZERO exceptions. Skipping a test to make CI green is not "fixing" — it is hiding a bug. If you ever find yourself thinking "maybe we should just skip this test," stop and reconsider your approach entirely.
|
|
||||||
|
|
||||||
### Pattern 6: Excessive Confirmation Seeking vs. Premature Execution
|
|
||||||
|
|
||||||
AI oscillates between two extremes: asking "should I do X?" for every trivial decision, or silently executing major changes without confirmation.
|
|
||||||
|
|
||||||
**Rule**: Calibrate based on reversibility and impact:
|
|
||||||
|
|
||||||
- **Small, reversible changes** (formatting, renaming a local variable, adding a test): just do it.
|
|
||||||
- **Architecture decisions, API changes, large refactors**: propose the plan first, wait for confirmation.
|
|
||||||
- **Pushing to remote, creating PRs, modifying CI**: always confirm.
|
|
||||||
- When the user says "go ahead" or "do it," execute fully without asking again mid-way.
|
|
||||||
|
|
||||||
## Code Reuse & Understanding Before Implementation
|
|
||||||
|
|
||||||
**This is the single most important rule in this project.** Before writing ANY new code, you MUST thoroughly read and understand the existing codebase first. This project has a rich set of utilities, abstractions, and patterns already in place — duplicating them wastes effort and creates maintenance burden.
|
|
||||||
|
|
||||||
Concrete requirements:
|
|
||||||
|
|
||||||
1. **Read before you write.** Before implementing a feature or fix, explore the relevant modules in `src/`. Search for existing helpers, utilities, and patterns that solve the same or similar problems. Use `Grep`, `Glob`, and `Agent` tools to investigate thoroughly — do not assume something doesn't exist just because you haven't seen it yet.
|
|
||||||
|
|
||||||
2. **Reuse existing infrastructure.** This project already has:
|
|
||||||
- A `Lexer` class (`src/syntax/lexer.h`) — do not hand-write token scanning logic.
|
|
||||||
- A `PositionMapper` for source location conversion — do not reimplement offset-to-line/column math.
|
|
||||||
- `CompilationUnitRef` methods (`decompose_location`, `decompose_range`, `file_path`, `directives`, etc.) — use them instead of raw Clang APIs.
|
|
||||||
- `SemanticVisitor` for AST traversal — extend it, do not write custom recursive AST walkers.
|
|
||||||
- `Tester` framework for unit tests with VFS, annotation support, and multi-phase compilation — use it, do not create ad-hoc test setups.
|
|
||||||
- Utility functions in `src/support/` — check there before writing new helpers.
|
|
||||||
|
|
||||||
3. **Follow established patterns.** When adding a new feature (e.g., a new LSP request handler), look at how 2-3 existing features of the same kind are implemented. Match their structure: same file organization, same function signatures, same error handling patterns. If every other feature in `src/feature/` follows a certain pattern, yours should too.
|
|
||||||
|
|
||||||
4. **Do not reinvent what the project already has.** If you find yourself writing a helper function that feels generic (string manipulation, path handling, JSON serialization, source range conversion), STOP and search the codebase first. There is a high probability it already exists. Creating duplicates leads to inconsistencies and bugs when one copy gets updated but the other doesn't.
|
|
||||||
|
|
||||||
5. **When in doubt, ask.** If you're unsure whether an existing utility covers your use case or whether to extend an existing abstraction vs. create a new one, ask the user rather than guessing.
|
|
||||||
|
|
||||||
## Source Layout
|
|
||||||
|
|
||||||
- `src/server/` — LSP server core: master server, compiler, indexer, stateful/stateless workers
|
|
||||||
- `src/feature/` — LSP feature implementations: hover, completion, document links, semantic tokens, etc.
|
|
||||||
- `src/compile/` — Compilation orchestration: compilation unit, directives, diagnostics
|
|
||||||
- `src/index/` — Symbol indexing: TUIndex, ProjectIndex, MergedIndex, include graph
|
|
||||||
- `src/semantic/` — Semantic analysis: symbol kinds, relations, AST visitor, template resolver
|
|
||||||
- `src/syntax/` — Lexer, scanner, token types, dependency graph
|
|
||||||
- `src/command/` — CLI parsing, compilation database, toolchain detection
|
|
||||||
- `src/support/` — Utilities: logging, filesystem, JSON, string helpers
|
|
||||||
|
|
||||||
## Build System
|
|
||||||
|
|
||||||
- Uses **pixi** for environment management and **CMake + Ninja** for building.
|
|
||||||
- Two build types: `Debug` and `RelWithDebInfo` (default).
|
|
||||||
- Build output goes to `build/[type]/`.
|
|
||||||
- See `/build`, `/test`, `/format` commands for common operations.
|
|
||||||
|
|
||||||
## Commit Message Format
|
|
||||||
|
|
||||||
Use **conventional commits** — enforced by CI:
|
|
||||||
|
|
||||||
```
|
|
||||||
<type>(<scope>): <short description>
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Types**: `feat`, `fix`, `refactor`, `chore`, `docs`, `ci`, `test`
|
|
||||||
- **Scopes**: match `src/` subdirectories or feature names, e.g. `completion`, `server`, `index`, `tests`, `document links`
|
|
||||||
- Keep the subject line under 70 characters.
|
|
||||||
|
|
||||||
## Tests
|
|
||||||
|
|
||||||
Three types of tests, all must pass before committing:
|
|
||||||
|
|
||||||
- **Unit tests** (`tests/unit/`): C++ tests using the project's own test framework. Test names should be at most 4 words.
|
|
||||||
- **Integration tests** (`tests/integration/`): Python pytest tests that start a real clice server and communicate via LSP.
|
|
||||||
- **Smoke tests** (`tests/smoke/`): Replay recorded LSP sessions via `tests/replay.py`.
|
|
||||||
|
|
||||||
### Integration Test Style
|
|
||||||
|
|
||||||
- Keep tests concise. Do NOT write large comment blocks explaining the test layout or expected behavior.
|
|
||||||
- Use descriptive test function names and short inline comments only where logic is non-obvious.
|
|
||||||
|
|
||||||
## Pre-PR Review
|
|
||||||
|
|
||||||
Before opening a PR, launch **3 parallel subagents** to review the diff independently:
|
|
||||||
|
|
||||||
1. **Correctness reviewer**: Check for logic errors, edge cases, undefined behavior, and off-by-one mistakes.
|
|
||||||
2. **Style reviewer**: Verify the code follows this project's naming conventions, coding style, and CLAUDE.md rules.
|
|
||||||
3. **Test reviewer**: Confirm test coverage is adequate — new functionality has tests, edge cases are covered, and no existing tests were broken or weakened.
|
|
||||||
|
|
||||||
Each agent should read the full diff (`git diff main...HEAD`) and report issues. Fix all reported issues before opening the PR.
|
|
||||||
|
|
||||||
## Pre-commit Checklist
|
|
||||||
|
|
||||||
Before committing code, you MUST:
|
|
||||||
|
|
||||||
1. **Run `pixi run format`** to format all source files.
|
|
||||||
2. **Pass all three types of tests:**
|
|
||||||
- Unit tests: `pixi run unit-test [type]`
|
|
||||||
- Integration tests: `pixi run integration-test [type]`
|
|
||||||
- Smoke tests: `pixi run smoke-test [type]`
|
|
||||||
3. **All test failures must be fixed before committing.** This is a HARD REQUIREMENT with NO exceptions:
|
|
||||||
- If a test fails, it MUST be fixed before you commit. Do NOT commit with known failures.
|
|
||||||
- Do NOT skip, disable, or mark tests as expected-failure to work around breakage.
|
|
||||||
- Do NOT argue "this test was already broken before my changes" — if it fails on your branch, it is YOUR responsibility to fix it before committing. The main branch CI is green; any failure on your branch is caused by your changes, period.
|
|
||||||
- Do NOT defer fixing to a follow-up PR. Fix it NOW, in this branch, before committing.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## C++ Coding Style
|
|
||||||
|
|
||||||
### Template & Type Traits
|
|
||||||
|
|
||||||
- Do NOT blindly add `std::remove_cvref_t` on every template parameter. Understand C++ template argument deduction rules:
|
|
||||||
- `template<typename T> void f(T x)` — `T` is always deduced as a non-reference, non-cv-qualified type. No need for `remove_cvref_t`.
|
|
||||||
- `template<typename T> void f(T& x)` — `T` is deduced as the referred-to type (possibly cv-qualified, but never a reference). No need for `remove_cvref_t` to strip references.
|
|
||||||
- `template<typename T> void f(const T& x)` — `T` is deduced as a non-const, non-reference type. No need for `remove_cvref_t`.
|
|
||||||
- `template<typename T> void f(T&& x)` — **forwarding reference**: `T` CAN be deduced as an lvalue reference (e.g., `int&`). This is the ONLY case where `std::remove_cvref_t<T>` is needed to get the bare type.
|
|
||||||
- Class template parameters and return types are also never deduced as references; don't add `remove_cvref_t` on them either.
|
|
||||||
|
|
||||||
### Type Traits & Concepts (C++20/23)
|
|
||||||
|
|
||||||
- This project targets C++20/23. Use variable templates directly for type traits — do NOT use the old pattern of wrapping a class template static member in a variable template. Prefer:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// Good: directly specialize a variable template
|
|
||||||
template<typename T>
|
|
||||||
inline constexpr bool is_my_type_v = false;
|
|
||||||
|
|
||||||
template<>
|
|
||||||
inline constexpr bool is_my_type_v<MyType> = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// Bad: unnecessary class template wrapper
|
|
||||||
template<typename T>
|
|
||||||
struct is_my_type : std::false_type {};
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct is_my_type<MyType> : std::true_type {};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
inline constexpr bool is_my_type_v = is_my_type<T>::value;
|
|
||||||
```
|
|
||||||
|
|
||||||
- When defining a concept that checks a type trait, do NOT add `std::remove_cvref_t` unless you specifically intend the concept to see through references/cv-qualifiers. If the concept is meant for a bare type, just use `T` directly — the caller is responsible for passing the right type.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// Good
|
|
||||||
template<typename T>
|
|
||||||
concept MyTrait = is_my_type_v<T>;
|
|
||||||
|
|
||||||
// Bad: unnecessary remove_cvref_t
|
|
||||||
template<typename T>
|
|
||||||
concept MyTrait = is_my_type_v<std::remove_cvref_t<T>>;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Naming Conventions
|
|
||||||
|
|
||||||
- **Variables, member fields, function names**: `snake_case`. Class member fields do NOT use any special suffix/prefix (no trailing `_`, no `m_` prefix).
|
|
||||||
- **Class names, template parameter names, enum names**: `PascalCase`. Exception: some class names also use `snake_case` — follow the existing style in the project.
|
|
||||||
- **Enum values**: `PascalCase`.
|
|
||||||
|
|
||||||
### String Literals
|
|
||||||
|
|
||||||
- Prefer C++11 raw string literals `R"(...)"` over escaped strings. Avoid `\"`, `\\`, `\n` in string literals when a raw literal is cleaner.
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
- **Prefer `if` with init-statements to tightly scope error variables**, but avoid them when they compromise code readability or flatten control flow.
|
|
||||||
- **Omit redundant conditions:** If the error type provides an `operator bool` or evaluates implicitly (e.g., standard error codes, custom error wrappers), omit the redundant condition check.
|
|
||||||
- **Avoid forced `else` branches:** If scoping the variable inside the `if` requires you to introduce an `else` block for the success path (especially when returning early on error), declare the variable in the local scope instead to keep the control flow flat.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
// Good: Omit redundant condition when the type has operator bool
|
|
||||||
if (auto err = foo()) {
|
|
||||||
/* handle error */
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bad: Redundant condition check
|
|
||||||
if (auto err = foo(); err) {
|
|
||||||
/* handle error */
|
|
||||||
}
|
|
||||||
|
|
||||||
// Good: Use init-statement when a custom condition is required,
|
|
||||||
// AND the variable isn't needed outside the if-statement
|
|
||||||
if (auto result = foo(); !result.has_value()) {
|
|
||||||
/* handle error */
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Scope and Control Flow Considerations ---
|
|
||||||
|
|
||||||
// Bad: Using init-statement forces an 'else' block because 'result'
|
|
||||||
// goes out of scope, leading to nested/redundant code.
|
|
||||||
if (auto result = get_data(); !result.has_value()) {
|
|
||||||
return result.error();
|
|
||||||
} else {
|
|
||||||
process(result.value()); // Success path is forced into a nested block
|
|
||||||
}
|
|
||||||
|
|
||||||
// Good: Declare as a regular local variable to allow early exit
|
|
||||||
// and keep the success path un-nested (flat control flow).
|
|
||||||
auto result = get_data();
|
|
||||||
if (!result.has_value()) {
|
|
||||||
return result.error();
|
|
||||||
}
|
|
||||||
process(result.value());
|
|
||||||
```
|
|
||||||
|
|
||||||
### Style
|
|
||||||
|
|
||||||
- Prefer `[[maybe_unused]]` over `(void)` for intentionally unused variables or parameters.
|
|
||||||
|
|
||||||
### Modern C++ Usage
|
|
||||||
|
|
||||||
- Use C++20/23 APIs whenever possible. Do NOT use `<iostream>` facilities (`std::cout`, `std::cin`, `std::cerr`, etc.). Also do NOT use C-style I/O (`printf`, `fprintf`, etc.).
|
|
||||||
- Prefer `std::ranges` / `std::views` APIs over raw loops and traditional `<algorithm>` calls.
|
|
||||||
- If the project depends on LLVM, prefer LLVM's efficient data structures (e.g., `llvm::SmallVector`, `llvm::DenseMap`, `llvm::StringMap`, `llvm::StringRef`) over their `std` counterparts when appropriate.
|
|
||||||
|
|
||||||
### Parameter Passing Preferences
|
|
||||||
|
|
||||||
- For string parameters, prefer `llvm::StringRef` > `std::string_view` > `const std::string&`.
|
|
||||||
- For array/span parameters, prefer `llvm::ArrayRef` > `std::span` > `const std::vector&`.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
Build the project. Accepts an optional argument for build type: `Debug` or `RelWithDebInfo` (default).
|
|
||||||
|
|
||||||
Available build commands:
|
|
||||||
|
|
||||||
- CMake configure only: `pixi run cmake-config [type]`
|
|
||||||
- CMake build only (skip configure): `pixi run cmake-build [type]`
|
|
||||||
- Full build (configure + build): `pixi run build [type]`
|
|
||||||
- Build a specific target: `pixi run cmake-build [type]` then `cmake --build build/[type] --target [target]`
|
|
||||||
|
|
||||||
Common targets: `clice`, `unit_tests`
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
- `/build` — full build RelWithDebInfo
|
|
||||||
- `/build Debug` — full build Debug
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Format all project source files.
|
|
||||||
|
|
||||||
Run: `pixi run format`
|
|
||||||
|
|
||||||
Formats C++, Python, Lua, JS/TS, Markdown, JSON, TOML, and YAML files.
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
Run tests. Accepts an optional argument for build type: `Debug` or `RelWithDebInfo` (default).
|
|
||||||
|
|
||||||
Available test commands:
|
|
||||||
|
|
||||||
- Unit tests: `pixi run unit-test [type]`
|
|
||||||
- Integration tests: `pixi run integration-test [type]`
|
|
||||||
- Smoke tests: `pixi run smoke-test [type]`
|
|
||||||
- All tests (unit + integration): `pixi run test [type]`
|
|
||||||
|
|
||||||
Filtering specific tests:
|
|
||||||
|
|
||||||
- Unit tests: `pixi run unit-test [type] --test-filter=SuiteName.CaseName`
|
|
||||||
- Integration tests: `pixi run pytest tests/integration -k "test_name" --executable=./build/[type]/bin/clice`
|
|
||||||
- Smoke tests: `pixi run python tests/replay.py tests/smoke/specific.jsonl --clice=./build/[type]/bin/clice`
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
- `/test` — run all tests (RelWithDebInfo)
|
|
||||||
- `/test Debug` — run all tests (Debug)
|
|
||||||
@@ -1,7 +1,2 @@
|
|||||||
chat:
|
chat:
|
||||||
auto_reply: false
|
auto_reply: false
|
||||||
reviews:
|
|
||||||
auto_review:
|
|
||||||
enabled: true
|
|
||||||
summary:
|
|
||||||
enabled: false
|
|
||||||
|
|||||||
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -1,9 +1,2 @@
|
|||||||
# SCM syntax highlighting & preventing 3-way merges
|
# SCM syntax highlighting & preventing 3-way merges
|
||||||
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
|
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
|
||||||
|
|
||||||
# Force LF line endings for test data so that byte offsets from clang
|
|
||||||
# (which reads from disk) match the content sent by didOpen in tests.
|
|
||||||
tests/data/** text eol=lf
|
|
||||||
|
|
||||||
# Treat trace files as binary to suppress text diffs
|
|
||||||
tests/smoke/*.jsonl linguist-generated=true binary
|
|
||||||
|
|||||||
2
.github/actions/setup-pixi/action.yml
vendored
2
.github/actions/setup-pixi/action.yml
vendored
@@ -13,7 +13,7 @@ runs:
|
|||||||
- name: Setup Pixi
|
- name: Setup Pixi
|
||||||
uses: prefix-dev/setup-pixi@v0.9.3
|
uses: prefix-dev/setup-pixi@v0.9.3
|
||||||
with:
|
with:
|
||||||
pixi-version: v0.67.0
|
pixi-version: v0.62.0
|
||||||
environments: ${{ inputs.environments }}
|
environments: ${{ inputs.environments }}
|
||||||
activate-environment: true
|
activate-environment: true
|
||||||
cache: true
|
cache: true
|
||||||
|
|||||||
44
.github/workflows/benchmark.yml
vendored
44
.github/workflows/benchmark.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: benchmark
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
benchmark:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-24.04, macos-15, windows-2025]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-pixi
|
|
||||||
|
|
||||||
- name: Build scan_benchmark
|
|
||||||
run: |
|
|
||||||
pixi run cmake-config RelWithDebInfo ON -- -DCLICE_ENABLE_BENCHMARK=ON
|
|
||||||
cmake --build build/RelWithDebInfo --target scan_benchmark
|
|
||||||
|
|
||||||
- name: Clone LLVM
|
|
||||||
run: git clone --depth 1 https://github.com/llvm/llvm-project.git
|
|
||||||
|
|
||||||
- name: Generate CDB
|
|
||||||
run: |
|
|
||||||
cmake -B llvm-build -G Ninja \
|
|
||||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
|
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$(pwd)/cmake/toolchain.cmake" \
|
|
||||||
-DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra;lld;lldb;mlir;polly;flang;bolt" \
|
|
||||||
-DLLVM_ENABLE_RUNTIMES="compiler-rt;libcxx;libcxxabi;libunwind" \
|
|
||||||
llvm-project/llvm
|
|
||||||
|
|
||||||
- name: Run benchmark
|
|
||||||
run: ./build/RelWithDebInfo/bin/scan_benchmark --runs 20 llvm-build/compile_commands.json
|
|
||||||
|
|
||||||
- name: Stop sccache server
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
run: pixi run -- sccache --stop-server || true
|
|
||||||
345
.github/workflows/build-llvm.yml
vendored
345
.github/workflows/build-llvm.yml
vendored
@@ -1,22 +1,6 @@
|
|||||||
name: build llvm
|
name: build llvm
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
llvm_version:
|
|
||||||
description: "LLVM version to build (e.g., 21.1.8)"
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
skip_upload:
|
|
||||||
description: "Skip upload and PR creation (build-only mode)"
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
skip_pr:
|
|
||||||
description: "Skip PR creation (upload only, no PR)"
|
|
||||||
required: false
|
|
||||||
type: boolean
|
|
||||||
default: false
|
|
||||||
pull_request:
|
pull_request:
|
||||||
# if you want to run this workflow, change the branch name to main,
|
# if you want to run this workflow, change the branch name to main,
|
||||||
# if you want to turn off it, change it to non existent branch.
|
# if you want to turn off it, change it to non existent branch.
|
||||||
@@ -28,7 +12,9 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# Native builds
|
- os: windows-2025
|
||||||
|
llvm_mode: Debug
|
||||||
|
lto: OFF
|
||||||
- os: windows-2025
|
- os: windows-2025
|
||||||
llvm_mode: RelWithDebInfo
|
llvm_mode: RelWithDebInfo
|
||||||
lto: OFF
|
lto: OFF
|
||||||
@@ -53,42 +39,6 @@ jobs:
|
|||||||
- os: macos-15
|
- os: macos-15
|
||||||
llvm_mode: RelWithDebInfo
|
llvm_mode: RelWithDebInfo
|
||||||
lto: ON
|
lto: ON
|
||||||
|
|
||||||
# Cross-compilation builds
|
|
||||||
# macOS x64 (from arm64 macos-15)
|
|
||||||
- os: macos-15
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
lto: OFF
|
|
||||||
target_triple: x86_64-apple-darwin
|
|
||||||
- os: macos-15
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
lto: ON
|
|
||||||
target_triple: x86_64-apple-darwin
|
|
||||||
|
|
||||||
# Linux aarch64 (from x64 ubuntu-24.04)
|
|
||||||
- os: ubuntu-24.04
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
lto: OFF
|
|
||||||
target_triple: aarch64-linux-gnu
|
|
||||||
pixi_env: cross-linux-aarch64
|
|
||||||
- os: ubuntu-24.04
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
lto: ON
|
|
||||||
target_triple: aarch64-linux-gnu
|
|
||||||
pixi_env: cross-linux-aarch64
|
|
||||||
|
|
||||||
# Windows arm64 (from x64 windows-2025)
|
|
||||||
- os: windows-2025
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
lto: OFF
|
|
||||||
target_triple: aarch64-pc-windows-msvc
|
|
||||||
pixi_env: cross-windows-arm64
|
|
||||||
- os: windows-2025
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
lto: ON
|
|
||||||
target_triple: aarch64-pc-windows-msvc
|
|
||||||
pixi_env: cross-windows-arm64
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -117,91 +67,49 @@ jobs:
|
|||||||
free -h
|
free -h
|
||||||
df -h
|
df -h
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-pixi
|
- name: Setup Pixi
|
||||||
|
uses: prefix-dev/setup-pixi@v0.9.3
|
||||||
with:
|
with:
|
||||||
environments: ${{ matrix.pixi_env || 'package' }}
|
pixi-version: v0.59.0
|
||||||
|
environments: package
|
||||||
|
activate-environment: true
|
||||||
|
cache: true
|
||||||
|
locked: true
|
||||||
|
|
||||||
- name: Clone llvm-project
|
- name: Clone llvm-project (21.1.4)
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ inputs.llvm_version || '21.1.8' }}"
|
git clone --branch llvmorg-21.1.4 --depth 1 https://github.com/llvm/llvm-project.git .llvm
|
||||||
echo "Cloning LLVM ${VERSION}..."
|
|
||||||
git clone --branch "llvmorg-${VERSION}" --depth 1 https://github.com/llvm/llvm-project.git .llvm
|
|
||||||
|
|
||||||
- name: Validate distribution components
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
python3 scripts/validate-llvm-components.py \
|
|
||||||
--llvm-src=.llvm \
|
|
||||||
--components-file=scripts/llvm-components.json
|
|
||||||
|
|
||||||
- name: Build LLVM (install-distribution)
|
- name: Build LLVM (install-distribution)
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ENV="${{ matrix.pixi_env || 'package' }}"
|
pixi run build-llvm --llvm-src=.llvm --mode="${{ matrix.llvm_mode }}" --lto="${{ matrix.lto }}" --build-dir=build
|
||||||
EXTRA_ARGS=""
|
|
||||||
if [[ -n "${{ matrix.target_triple }}" ]]; then
|
|
||||||
EXTRA_ARGS="--target-triple=${{ matrix.target_triple }}"
|
|
||||||
fi
|
|
||||||
pixi run -e "$ENV" build-llvm \
|
|
||||||
--llvm-src=.llvm \
|
|
||||||
--mode="${{ matrix.llvm_mode }}" \
|
|
||||||
--lto="${{ matrix.lto }}" \
|
|
||||||
--build-dir=build \
|
|
||||||
${EXTRA_ARGS}
|
|
||||||
|
|
||||||
- name: Build clice using installed LLVM
|
- name: Build clice using installed LLVM
|
||||||
if: ${{ !matrix.target_triple }}
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
pixi run cmake-config ${{ matrix.llvm_mode }} ON -- \
|
cmake -B build -G Ninja \
|
||||||
"-DCLICE_ENABLE_LTO=${{ matrix.lto }}" \
|
-DCMAKE_BUILD_TYPE=${{ matrix.llvm_mode }} \
|
||||||
"-DLLVM_INSTALL_PATH=.llvm/build-install"
|
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake \
|
||||||
pixi run cmake-build ${{ matrix.llvm_mode }}
|
-DCLICE_ENABLE_TEST=ON \
|
||||||
|
-DCLICE_CI_ENVIRONMENT=ON \
|
||||||
- name: Build clice using installed LLVM (cross-compile)
|
-DCLICE_ENABLE_LTO=${{ matrix.lto }} \
|
||||||
if: ${{ matrix.target_triple }}
|
-DLLVM_INSTALL_PATH=".llvm/build-install"
|
||||||
shell: bash
|
cmake --build build
|
||||||
run: |
|
|
||||||
ENV="${{ matrix.pixi_env || 'package' }}"
|
|
||||||
pixi run -e "$ENV" cmake-config ${{ matrix.llvm_mode }} ON -- \
|
|
||||||
"-DCLICE_ENABLE_LTO=${{ matrix.lto }}" \
|
|
||||||
"-DCLICE_TARGET_TRIPLE=${{ matrix.target_triple }}" \
|
|
||||||
"-DLLVM_INSTALL_PATH=.llvm/build-install"
|
|
||||||
pixi run -e "$ENV" cmake-build ${{ matrix.llvm_mode }}
|
|
||||||
|
|
||||||
- name: Verify cross-compiled binary architecture
|
|
||||||
if: ${{ matrix.target_triple && runner.os != 'Windows' }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
BINARY="build/${{ matrix.llvm_mode }}/bin/clice"
|
|
||||||
echo "Binary info:"
|
|
||||||
file "$BINARY"
|
|
||||||
case "${{ matrix.target_triple }}" in
|
|
||||||
aarch64-linux-gnu) file "$BINARY" | grep -q "aarch64" ;;
|
|
||||||
x86_64-apple-darwin) file "$BINARY" | grep -q "x86_64" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Upload cross-compiled clice for functional test
|
|
||||||
if: ${{ matrix.target_triple && matrix.lto == 'OFF' }}
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cross-clice-${{ matrix.target_triple }}-${{ matrix.llvm_mode }}
|
|
||||||
path: |
|
|
||||||
build/${{ matrix.llvm_mode }}/bin/
|
|
||||||
build/${{ matrix.llvm_mode }}/lib/
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
if: ${{ !matrix.target_triple }}
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: pixi run test ${{ matrix.llvm_mode }}
|
run: |
|
||||||
|
EXE_EXT=""
|
||||||
|
if [[ "${{ runner.os }}" == "Windows" ]]; then
|
||||||
|
EXE_EXT=".exe"
|
||||||
|
fi
|
||||||
|
./build/bin/unit_tests${EXE_EXT} --test-dir="./tests/data"
|
||||||
|
uv run --project tests pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice${EXE_EXT}
|
||||||
|
|
||||||
# Prune is only supported for native builds (requires linking clice to test).
|
|
||||||
# Cross-compiled targets reuse the native prune manifest of the same OS.
|
|
||||||
- name: Prune LLVM static libraries (Debug/RelWithDebInfo no LTO)
|
- name: Prune LLVM static libraries (Debug/RelWithDebInfo no LTO)
|
||||||
if: (!matrix.target_triple) && (matrix.llvm_mode == 'Debug' || (matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'))
|
if: matrix.llvm_mode == 'Debug' || (matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF')
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
MANIFEST="pruned-libs-${{ matrix.os }}.json"
|
MANIFEST="pruned-libs-${{ matrix.os }}.json"
|
||||||
@@ -209,13 +117,13 @@ jobs:
|
|||||||
python3 scripts/prune-llvm-bin.py \
|
python3 scripts/prune-llvm-bin.py \
|
||||||
--action discover \
|
--action discover \
|
||||||
--install-dir ".llvm/build-install/lib" \
|
--install-dir ".llvm/build-install/lib" \
|
||||||
--build-dir "build/${{ matrix.llvm_mode }}" \
|
--build-dir "build" \
|
||||||
--max-attempts 60 \
|
--max-attempts 60 \
|
||||||
--sleep-seconds 60 \
|
--sleep-seconds 60 \
|
||||||
--manifest "${MANIFEST}"
|
--manifest "${MANIFEST}"
|
||||||
|
|
||||||
- name: Upload pruned-libs manifest
|
- name: Upload pruned-libs manifest
|
||||||
if: (!matrix.target_triple) && matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'
|
if: matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: llvm-pruned-libs-${{ matrix.os }}
|
name: llvm-pruned-libs-${{ matrix.os }}
|
||||||
@@ -223,8 +131,8 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
|
|
||||||
- name: Apply pruned-libs manifest (RelWithDebInfo + LTO, native only)
|
- name: Apply pruned-libs manifest (RelWithDebInfo + LTO)
|
||||||
if: (!matrix.target_triple) && matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'ON'
|
if: matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'ON'
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
@@ -234,27 +142,7 @@ jobs:
|
|||||||
--action apply \
|
--action apply \
|
||||||
--manifest "${MANIFEST}" \
|
--manifest "${MANIFEST}" \
|
||||||
--install-dir ".llvm/build-install/lib" \
|
--install-dir ".llvm/build-install/lib" \
|
||||||
--build-dir "build/${{ matrix.llvm_mode }}" \
|
--build-dir "build" \
|
||||||
--gh-run-id "${{ github.run_id }}" \
|
|
||||||
--gh-artifact "llvm-pruned-libs-${{ matrix.os }}" \
|
|
||||||
--gh-download-dir "artifacts" \
|
|
||||||
--max-attempts 60 \
|
|
||||||
--sleep-seconds 60
|
|
||||||
|
|
||||||
# For cross-compiled LTO builds, apply the native prune manifest.
|
|
||||||
# The unused library set is arch-independent (same API surface).
|
|
||||||
- name: Apply pruned-libs manifest (cross-compile + LTO)
|
|
||||||
if: matrix.target_triple && matrix.lto == 'ON'
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
MANIFEST="pruned-libs-${{ matrix.os }}.json"
|
|
||||||
python3 scripts/prune-llvm-bin.py \
|
|
||||||
--action apply \
|
|
||||||
--manifest "${MANIFEST}" \
|
|
||||||
--install-dir ".llvm/build-install/lib" \
|
|
||||||
--build-dir "build/${{ matrix.llvm_mode }}" \
|
|
||||||
--gh-run-id "${{ github.run_id }}" \
|
--gh-run-id "${{ github.run_id }}" \
|
||||||
--gh-artifact "llvm-pruned-libs-${{ matrix.os }}" \
|
--gh-artifact "llvm-pruned-libs-${{ matrix.os }}" \
|
||||||
--gh-download-dir "artifacts" \
|
--gh-download-dir "artifacts" \
|
||||||
@@ -269,35 +157,23 @@ jobs:
|
|||||||
MODE_TAG="debug"
|
MODE_TAG="debug"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Determine arch/platform/toolchain from target triple or runner OS
|
ARCH="x64"
|
||||||
if [[ -n "${{ matrix.target_triple }}" ]]; then
|
PLATFORM="linux"
|
||||||
case "${{ matrix.target_triple }}" in
|
TOOLCHAIN="gnu"
|
||||||
x86_64-apple-darwin)
|
if [[ "${{ matrix.os }}" == windows-* ]]; then
|
||||||
ARCH="x64"; PLATFORM="macos"; TOOLCHAIN="clang" ;;
|
PLATFORM="windows"
|
||||||
aarch64-linux-gnu)
|
TOOLCHAIN="msvc"
|
||||||
ARCH="aarch64"; PLATFORM="linux"; TOOLCHAIN="gnu" ;;
|
elif [[ "${{ matrix.os }}" == macos-* ]]; then
|
||||||
aarch64-pc-windows-msvc)
|
ARCH="arm64"
|
||||||
ARCH="aarch64"; PLATFORM="windows"; TOOLCHAIN="msvc" ;;
|
PLATFORM="macos"
|
||||||
esac
|
TOOLCHAIN="clang"
|
||||||
else
|
|
||||||
ARCH="x64"
|
|
||||||
PLATFORM="linux"
|
|
||||||
TOOLCHAIN="gnu"
|
|
||||||
if [[ "${{ matrix.os }}" == windows-* ]]; then
|
|
||||||
PLATFORM="windows"
|
|
||||||
TOOLCHAIN="msvc"
|
|
||||||
elif [[ "${{ matrix.os }}" == macos-* ]]; then
|
|
||||||
ARCH="arm64"
|
|
||||||
PLATFORM="macos"
|
|
||||||
TOOLCHAIN="clang"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SUFFIX=""
|
SUFFIX=""
|
||||||
if [[ "${{ matrix.lto }}" == "ON" ]]; then
|
if [[ "${{ matrix.lto }}" == "ON" ]]; then
|
||||||
SUFFIX="-lto"
|
SUFFIX="-lto"
|
||||||
fi
|
fi
|
||||||
if [[ "${{ matrix.llvm_mode }}" == "Debug" && "${{ matrix.os }}" != windows-* ]]; then
|
if [[ "${{ matrix.llvm_mode }}" == "Debug" ]]; then
|
||||||
SUFFIX="${SUFFIX}-asan"
|
SUFFIX="${SUFFIX}-asan"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -313,134 +189,3 @@ jobs:
|
|||||||
name: ${{ env.LLVM_INSTALL_ARCHIVE }}
|
name: ${{ env.LLVM_INSTALL_ARCHIVE }}
|
||||||
path: ${{ env.LLVM_INSTALL_ARCHIVE }}
|
path: ${{ env.LLVM_INSTALL_ARCHIVE }}
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
test-cross:
|
|
||||||
needs: build
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: macos-15-intel
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
target_triple: x86_64-apple-darwin
|
|
||||||
- os: ubuntu-24.04-arm
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
target_triple: aarch64-linux-gnu
|
|
||||||
- os: windows-11-arm
|
|
||||||
llvm_mode: RelWithDebInfo
|
|
||||||
target_triple: aarch64-pc-windows-msvc
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-pixi
|
|
||||||
with:
|
|
||||||
environments: test-run
|
|
||||||
|
|
||||||
- name: Download cross-compiled clice
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cross-clice-${{ matrix.target_triple }}-${{ matrix.llvm_mode }}
|
|
||||||
path: build/${{ matrix.llvm_mode }}/
|
|
||||||
|
|
||||||
- name: Make binaries executable
|
|
||||||
if: runner.os != 'Windows'
|
|
||||||
run: chmod +x build/${{ matrix.llvm_mode }}/bin/*
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: pixi run -e test-run test ${{ matrix.llvm_mode }}
|
|
||||||
|
|
||||||
upload:
|
|
||||||
needs: build
|
|
||||||
if: ${{ !cancelled() && inputs.llvm_version && !inputs.skip_upload }}
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Download all build artifacts
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
run: scripts/download-llvm.sh "${{ github.run_id }}"
|
|
||||||
|
|
||||||
- name: Upload to clice-llvm
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.UPLOAD_LLVM }}
|
|
||||||
TARGET_REPO: clice-io/clice-llvm
|
|
||||||
run: python3 scripts/upload-llvm.py "${{ inputs.llvm_version }}" "${TARGET_REPO}" "${{ github.run_id }}"
|
|
||||||
|
|
||||||
- name: Save manifest for update-clice job
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: llvm-manifest-final
|
|
||||||
path: artifacts/llvm-manifest.json
|
|
||||||
if-no-files-found: error
|
|
||||||
compression-level: 0
|
|
||||||
|
|
||||||
update-clice:
|
|
||||||
needs: upload
|
|
||||||
if: ${{ !inputs.skip_pr }}
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Download manifest
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: llvm-manifest-final
|
|
||||||
path: .
|
|
||||||
|
|
||||||
- name: Update manifest and version
|
|
||||||
run: |
|
|
||||||
python3 scripts/update-llvm-version.py \
|
|
||||||
--version "${{ inputs.llvm_version }}" \
|
|
||||||
--manifest-src llvm-manifest.json \
|
|
||||||
--manifest-dest config/llvm-manifest.json \
|
|
||||||
--package-cmake cmake/package.cmake
|
|
||||||
|
|
||||||
- name: Create or update PR
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
VERSION="${{ inputs.llvm_version }}"
|
|
||||||
BRANCH="chore/update-llvm-${VERSION}"
|
|
||||||
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
||||||
RELEASE_URL="https://github.com/clice-io/clice-llvm/releases/tag/${VERSION}"
|
|
||||||
|
|
||||||
git config user.name "github-actions[bot]"
|
|
||||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
git checkout -b "${BRANCH}"
|
|
||||||
git add config/llvm-manifest.json cmake/package.cmake
|
|
||||||
git commit -m "chore: update LLVM to ${VERSION}"
|
|
||||||
git push --force-with-lease origin "${BRANCH}"
|
|
||||||
|
|
||||||
# Check if PR already exists for this branch
|
|
||||||
EXISTING_PR=$(gh pr list --head "${BRANCH}" --json number --jq '.[0].number // empty')
|
|
||||||
|
|
||||||
BODY="$(cat <<EOF
|
|
||||||
## Summary
|
|
||||||
- Update LLVM prebuilt binaries to version ${VERSION}
|
|
||||||
- Updated \`config/llvm-manifest.json\` with new SHA256 hashes
|
|
||||||
- Updated \`cmake/package.cmake\` version string
|
|
||||||
|
|
||||||
**Artifacts:** [clice-llvm release](${RELEASE_URL})
|
|
||||||
**Build:** [workflow run](${RUN_URL})
|
|
||||||
|
|
||||||
> Auto-generated by build-llvm workflow
|
|
||||||
EOF
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [[ -n "${EXISTING_PR}" ]]; then
|
|
||||||
echo "Updating existing PR #${EXISTING_PR}"
|
|
||||||
gh pr edit "${EXISTING_PR}" --body "${BODY}"
|
|
||||||
else
|
|
||||||
gh pr create \
|
|
||||||
--title "chore: update LLVM to ${VERSION}" \
|
|
||||||
--body "${BODY}" \
|
|
||||||
--base main
|
|
||||||
fi
|
|
||||||
|
|||||||
21
.github/workflows/check-format.yml
vendored
21
.github/workflows/check-format.yml
vendored
@@ -14,12 +14,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
environments: format
|
environments: format
|
||||||
|
|
||||||
- name: Validate update-llvm-version.py can still patch package.cmake
|
|
||||||
run: |
|
|
||||||
python3 scripts/update-llvm-version.py --check \
|
|
||||||
--manifest-dest config/llvm-manifest.json \
|
|
||||||
--package-cmake cmake/package.cmake
|
|
||||||
|
|
||||||
- name: Run formatter
|
- name: Run formatter
|
||||||
run: pixi run format
|
run: pixi run format
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -30,11 +24,10 @@ jobs:
|
|||||||
args: --lint ./docs
|
args: --lint ./docs
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Check diff
|
- name: Suggest changes
|
||||||
run: |
|
if: github.event_name == 'pull_request'
|
||||||
if ! git diff --quiet; then
|
uses: reviewdog/action-suggester@v1.24.0
|
||||||
echo "::error::Formatting changes detected. Please run 'pixi run format' and commit the result."
|
with:
|
||||||
git --no-pager diff --stat
|
tool_name: "fmt"
|
||||||
git --no-pager diff
|
fail_level: any
|
||||||
exit 1
|
filter_mode: nofilter
|
||||||
fi
|
|
||||||
|
|||||||
10
.github/workflows/deploy-docs.yml
vendored
10
.github/workflows/deploy-docs.yml
vendored
@@ -20,11 +20,9 @@ jobs:
|
|||||||
run: pixi run build-docs
|
run: pixi run build-docs
|
||||||
|
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
uses: peaceiris/actions-gh-pages@v4
|
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
with:
|
with:
|
||||||
personal_token: ${{ secrets.PUBLISH_DOCS }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
external_repository: clice-io/docs
|
publish_dir: docs/.vitepress/dist
|
||||||
publish_dir: ./docs/.vitepress/dist
|
cname: clice.io
|
||||||
destination_dir: clice
|
|
||||||
keep_files: true
|
|
||||||
|
|||||||
60
.github/workflows/main.yml
vendored
60
.github/workflows/main.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
clice: ${{ steps.filter.outputs.clice }}
|
clice: ${{ steps.filter.outputs.clice }}
|
||||||
vscode: ${{ steps.filter.outputs.vscode }}
|
vscode: ${{ steps.filter.outputs.vscode }}
|
||||||
cmake: ${{ steps.filter.outputs.cmake }}
|
cmake: ${{ steps.filter.outputs.cmake }}
|
||||||
|
xmake: ${{ steps.filter.outputs.xmake }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dorny/paths-filter@v3
|
- uses: dorny/paths-filter@v3
|
||||||
@@ -46,34 +46,21 @@ jobs:
|
|||||||
- 'tests/**'
|
- 'tests/**'
|
||||||
- 'config/**'
|
- 'config/**'
|
||||||
- '.github/workflows/test-cmake.yml'
|
- '.github/workflows/test-cmake.yml'
|
||||||
|
xmake:
|
||||||
conventional-commit:
|
- 'xmake.lua'
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
- 'src/**'
|
||||||
runs-on: ubuntu-latest
|
- 'include/**'
|
||||||
steps:
|
- 'tests/**'
|
||||||
- name: Check conventional commit format
|
- 'config/**'
|
||||||
env:
|
- '.github/workflows/test-xmake.yml'
|
||||||
IS_PR: ${{ github.event_name == 'pull_request' }}
|
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
||||||
COMMIT_MSG: ${{ github.event.head_commit.message }}
|
|
||||||
run: |
|
|
||||||
pattern='^(feat|fix|refactor|chore|build|ci|docs|test|perf|style|revert)(\(.+\))?: .+'
|
|
||||||
if [[ "$IS_PR" == "true" ]]; then
|
|
||||||
subject="$PR_TITLE"
|
|
||||||
label="PR title"
|
|
||||||
else
|
|
||||||
subject=$(echo "$COMMIT_MSG" | head -n1)
|
|
||||||
label="Commit message"
|
|
||||||
fi
|
|
||||||
if [[ ! "$subject" =~ $pattern ]]; then
|
|
||||||
echo "::error::$label must follow conventional commit format: type(scope)?: description"
|
|
||||||
echo " Valid types: feat, fix, refactor, chore, build, ci, docs, test, perf, style, revert"
|
|
||||||
echo " Got: '$subject'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
format:
|
format:
|
||||||
needs: changes
|
needs: changes
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
checks: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
if: ${{ needs.changes.outputs.format == 'true' }}
|
if: ${{ needs.changes.outputs.format == 'true' }}
|
||||||
uses: ./.github/workflows/check-format.yml
|
uses: ./.github/workflows/check-format.yml
|
||||||
|
|
||||||
@@ -83,12 +70,11 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
uses: ./.github/workflows/deploy-docs.yml
|
uses: ./.github/workflows/deploy-docs.yml
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
# clice:
|
clice:
|
||||||
# needs: changes
|
needs: changes
|
||||||
# if: ${{ needs.changes.outputs.clice == 'true' }}
|
if: ${{ needs.changes.outputs.clice == 'true' }}
|
||||||
# uses: ./.github/workflows/publish-clice.yml
|
uses: ./.github/workflows/publish-clice.yml
|
||||||
|
|
||||||
vscode:
|
vscode:
|
||||||
needs: changes
|
needs: changes
|
||||||
@@ -100,6 +86,11 @@ jobs:
|
|||||||
if: ${{ needs.changes.outputs.cmake == 'true' }}
|
if: ${{ needs.changes.outputs.cmake == 'true' }}
|
||||||
uses: ./.github/workflows/test-cmake.yml
|
uses: ./.github/workflows/test-cmake.yml
|
||||||
|
|
||||||
|
xmake:
|
||||||
|
needs: changes
|
||||||
|
if: ${{ needs.changes.outputs.xmake == 'true' }}
|
||||||
|
uses: ./.github/workflows/test-xmake.yml
|
||||||
|
|
||||||
release-clice:
|
release-clice:
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -117,17 +108,16 @@ jobs:
|
|||||||
checks-passed:
|
checks-passed:
|
||||||
if: ${{ always() && !startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ always() && !startsWith(github.ref, 'refs/tags/') }}
|
||||||
needs:
|
needs:
|
||||||
- conventional-commit
|
|
||||||
- format
|
- format
|
||||||
- deploy
|
- deploy
|
||||||
# - clice
|
- clice
|
||||||
- vscode
|
- vscode
|
||||||
- cmake
|
- cmake
|
||||||
|
- xmake
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check results
|
- name: Check results
|
||||||
uses: re-actors/alls-green@release/v1
|
uses: re-actors/alls-green@release/v1
|
||||||
with:
|
with:
|
||||||
allowed-skips: conventional-commit,format,deploy,clice,vscode,cmake
|
allowed-skips: format,deploy,clice,vscode,cmake,xmake
|
||||||
jobs: ${{ toJSON(needs) }}
|
jobs: ${{ toJSON(needs) }}
|
||||||
|
|||||||
65
.github/workflows/publish-clice.yml
vendored
65
.github/workflows/publish-clice.yml
vendored
@@ -9,7 +9,6 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# Native builds
|
|
||||||
- os: windows-2025
|
- os: windows-2025
|
||||||
artifact_name: clice.zip
|
artifact_name: clice.zip
|
||||||
asset_name: clice-x64-windows-msvc.zip
|
asset_name: clice-x64-windows-msvc.zip
|
||||||
@@ -28,63 +27,43 @@ jobs:
|
|||||||
symbol_artifact_name: clice-symbol.tar.gz
|
symbol_artifact_name: clice-symbol.tar.gz
|
||||||
symbol_asset_name: clice-arm64-macos-darwin-symbol.tar.gz
|
symbol_asset_name: clice-arm64-macos-darwin-symbol.tar.gz
|
||||||
|
|
||||||
# Cross-compilation builds
|
|
||||||
- os: macos-15
|
|
||||||
target_triple: x86_64-apple-darwin
|
|
||||||
pixi_env: cross-macos-x64
|
|
||||||
artifact_name: clice.tar.gz
|
|
||||||
asset_name: clice-x86_64-macos-darwin.tar.gz
|
|
||||||
symbol_artifact_name: clice-symbol.tar.gz
|
|
||||||
symbol_asset_name: clice-x86_64-macos-darwin-symbol.tar.gz
|
|
||||||
|
|
||||||
- os: ubuntu-24.04
|
|
||||||
target_triple: aarch64-linux-gnu
|
|
||||||
pixi_env: cross-linux-aarch64
|
|
||||||
artifact_name: clice.tar.gz
|
|
||||||
asset_name: clice-aarch64-linux-gnu.tar.gz
|
|
||||||
symbol_artifact_name: clice-symbol.tar.gz
|
|
||||||
symbol_asset_name: clice-aarch64-linux-gnu-symbol.tar.gz
|
|
||||||
|
|
||||||
- os: windows-2025
|
|
||||||
target_triple: aarch64-pc-windows-msvc
|
|
||||||
pixi_env: cross-windows-arm64
|
|
||||||
artifact_name: clice.zip
|
|
||||||
asset_name: clice-aarch64-windows-msvc.zip
|
|
||||||
symbol_artifact_name: clice-symbol.zip
|
|
||||||
symbol_asset_name: clice-aarch64-windows-msvc-symbol.zip
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup xmake
|
||||||
|
uses: xmake-io/github-action-setup-xmake@v1
|
||||||
|
with:
|
||||||
|
xmake-version: 3.0.5
|
||||||
|
actions-cache-folder: ".xmake-cache"
|
||||||
|
actions-cache-key: ${{ matrix.os }}
|
||||||
|
package-cache: true
|
||||||
|
package-cache-key: ${{ matrix.os }}-pkg-release-v1
|
||||||
|
build-cache: true
|
||||||
|
build-cache-key: ${{ matrix.os }}-build-release-v1
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-pixi
|
- uses: ./.github/actions/setup-pixi
|
||||||
with:
|
with:
|
||||||
environments: ${{ matrix.pixi_env || 'package' }}
|
environments: package
|
||||||
|
|
||||||
- name: Package (native)
|
- name: Remove ci llvm toolchain on Windows
|
||||||
if: ${{ !matrix.target_triple }}
|
if: runner.os == 'Windows'
|
||||||
run: pixi run package
|
|
||||||
|
|
||||||
- name: Package (cross-compile)
|
|
||||||
if: ${{ matrix.target_triple }}
|
|
||||||
run: |
|
run: |
|
||||||
ENV="${{ matrix.pixi_env }}"
|
# @see https://github.com/xmake-io/xmake/issues/7158
|
||||||
pixi run -e "$ENV" package-config -- \
|
xmake lua os.rmdir "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/Llvm"
|
||||||
"-DCLICE_TARGET_TRIPLE=${{ matrix.target_triple }}"
|
xmake lua os.rmdir "C:/Program Files/LLVM"
|
||||||
pixi run -e "$ENV" cmake-build
|
|
||||||
|
- name: Package
|
||||||
|
run: pixi run package
|
||||||
|
|
||||||
- name: Upload Main Package to Release
|
- name: Upload Main Package to Release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
file: build/RelWithDebInfo/${{ matrix.artifact_name }}
|
file: build/xpack/clice/${{ matrix.artifact_name }}
|
||||||
asset_name: ${{ matrix.asset_name }}
|
asset_name: ${{ matrix.asset_name }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -94,7 +73,7 @@ jobs:
|
|||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
file: build/RelWithDebInfo/${{ matrix.symbol_artifact_name }}
|
file: build/xpack/clice/${{ matrix.symbol_artifact_name }}
|
||||||
asset_name: ${{ matrix.symbol_asset_name }}
|
asset_name: ${{ matrix.symbol_asset_name }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
125
.github/workflows/test-cmake.yml
vendored
125
.github/workflows/test-cmake.yml
vendored
@@ -3,148 +3,27 @@ name: cmake
|
|||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
# Native builds
|
|
||||||
- os: windows-2025
|
- os: windows-2025
|
||||||
build_type: RelWithDebInfo
|
build_type: RelWithDebInfo
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
- os: ubuntu-24.04
|
|
||||||
build_type: RelWithDebInfo
|
|
||||||
- os: macos-15
|
- os: macos-15
|
||||||
build_type: Debug
|
build_type: Debug
|
||||||
- os: macos-15
|
|
||||||
build_type: RelWithDebInfo
|
|
||||||
# Cross-compile (build only; tests run on native runners)
|
|
||||||
- os: macos-15
|
|
||||||
build_type: RelWithDebInfo
|
|
||||||
target_triple: x86_64-apple-darwin
|
|
||||||
build_only: true
|
|
||||||
- os: ubuntu-24.04
|
|
||||||
build_type: RelWithDebInfo
|
|
||||||
target_triple: aarch64-linux-gnu
|
|
||||||
build_only: true
|
|
||||||
pixi_env: cross-linux-aarch64
|
|
||||||
- os: windows-2025
|
|
||||||
build_type: RelWithDebInfo
|
|
||||||
target_triple: aarch64-pc-windows-msvc
|
|
||||||
build_only: true
|
|
||||||
pixi_env: cross-windows-arm64
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-pixi
|
- uses: ./.github/actions/setup-pixi
|
||||||
with:
|
|
||||||
environments: ${{ matrix.pixi_env || 'default' }}
|
|
||||||
|
|
||||||
- name: Restore compiler cache
|
- name: Build
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{ runner.os == 'Windows' && '.cache/sccache' || '.cache/ccache' }}
|
|
||||||
key: ${{ runner.os }}-${{ matrix.build_type }}-${{ matrix.target_triple || 'native' }}-ccache-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-${{ matrix.build_type }}-${{ matrix.target_triple || 'native' }}-ccache-
|
|
||||||
|
|
||||||
- name: Zero cache stats
|
|
||||||
run: |
|
|
||||||
ENV="${{ matrix.pixi_env || 'default' }}"
|
|
||||||
if [ "$RUNNER_OS" = "Windows" ]; then
|
|
||||||
pixi run -e "$ENV" -- sccache --stop-server || true
|
|
||||||
pixi run -e "$ENV" -- sccache --zero-stats || true
|
|
||||||
else
|
|
||||||
pixi run -e "$ENV" -- ccache --zero-stats || true
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build (native)
|
|
||||||
if: ${{ !matrix.target_triple }}
|
|
||||||
run: pixi run build ${{ matrix.build_type }} ON
|
run: pixi run build ${{ matrix.build_type }} ON
|
||||||
|
|
||||||
- name: Build (cross-compile)
|
- name: Test
|
||||||
if: ${{ matrix.target_triple }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
ENV="${{ matrix.pixi_env || 'default' }}"
|
|
||||||
pixi run -e "$ENV" cmake-config ${{ matrix.build_type }} OFF -- \
|
|
||||||
"-DCLICE_TARGET_TRIPLE=${{ matrix.target_triple }}"
|
|
||||||
pixi run -e "$ENV" cmake-build ${{ matrix.build_type }}
|
|
||||||
|
|
||||||
- name: Upload cross-compiled binaries
|
|
||||||
if: ${{ matrix.build_only }}
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cross-build-${{ matrix.target_triple }}
|
|
||||||
path: |
|
|
||||||
build/${{ matrix.build_type }}/bin/
|
|
||||||
build/${{ matrix.build_type }}/lib/
|
|
||||||
if-no-files-found: error
|
|
||||||
retention-days: 1
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
if: ${{ !matrix.build_only }}
|
|
||||||
run: pixi run test ${{ matrix.build_type }}
|
run: pixi run test ${{ matrix.build_type }}
|
||||||
|
|
||||||
- name: Print cache stats and stop server
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
ENV="${{ matrix.pixi_env || 'default' }}"
|
|
||||||
if [ "$RUNNER_OS" = "Windows" ]; then
|
|
||||||
pixi run -e "$ENV" -- sccache --show-stats
|
|
||||||
pixi run -e "$ENV" -- sccache --stop-server || true
|
|
||||||
else
|
|
||||||
pixi run -e "$ENV" -- ccache --show-stats
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
test-cross:
|
|
||||||
needs: build
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- os: macos-15-intel
|
|
||||||
build_type: RelWithDebInfo
|
|
||||||
target_triple: x86_64-apple-darwin
|
|
||||||
- os: ubuntu-24.04-arm
|
|
||||||
build_type: RelWithDebInfo
|
|
||||||
target_triple: aarch64-linux-gnu
|
|
||||||
- os: windows-11-arm
|
|
||||||
build_type: RelWithDebInfo
|
|
||||||
target_triple: aarch64-pc-windows-msvc
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: ./.github/actions/setup-pixi
|
|
||||||
with:
|
|
||||||
environments: test-run
|
|
||||||
|
|
||||||
- name: Download cross-compiled binaries
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: cross-build-${{ matrix.target_triple }}
|
|
||||||
path: build/${{ matrix.build_type }}/
|
|
||||||
|
|
||||||
- name: Make binaries executable
|
|
||||||
if: runner.os != 'Windows'
|
|
||||||
run: chmod +x build/${{ matrix.build_type }}/bin/*
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: pixi run -e test-run test ${{ matrix.build_type }}
|
|
||||||
|
|||||||
42
.github/workflows/test-xmake.yml
vendored
Normal file
42
.github/workflows/test-xmake.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: xmake
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [windows-2025, ubuntu-24.04, macos-15]
|
||||||
|
build_type: [debug, releasedbg]
|
||||||
|
exclude:
|
||||||
|
- os: windows-2025
|
||||||
|
build_type: debug
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup xmake
|
||||||
|
uses: xmake-io/github-action-setup-xmake@v1
|
||||||
|
with:
|
||||||
|
xmake-version: 3.0.5
|
||||||
|
actions-cache-folder: ".xmake-cache"
|
||||||
|
actions-cache-key: ${{ matrix.os }}
|
||||||
|
package-cache: true
|
||||||
|
package-cache-key: ${{ matrix.os }}-pixi
|
||||||
|
build-cache: true
|
||||||
|
build-cache-key: ${{ matrix.os }}-${{ matrix.build_type }}
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup-pixi
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pixi run xmake ${{ matrix.build_type }}
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: pixi run xmake-test
|
||||||
|
|
||||||
|
- name: Remove llvm package (Linux)
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
run: xmake require --uninstall clice-llvm
|
||||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -35,7 +35,7 @@
|
|||||||
*build*/
|
*build*/
|
||||||
temp/
|
temp/
|
||||||
.cache/
|
.cache/
|
||||||
|
.xmake/
|
||||||
.llvm*/
|
.llvm*/
|
||||||
.clice/
|
.clice/
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
@@ -56,19 +56,13 @@ __pycache__/
|
|||||||
tests/unit/Local/
|
tests/unit/Local/
|
||||||
|
|
||||||
# IDEs & Editors
|
# IDEs & Editors
|
||||||
/.vscode/*
|
/.vscode/
|
||||||
!/.vscode/launch.json
|
|
||||||
!/.vscode/tasks.json
|
|
||||||
.vs/
|
.vs/
|
||||||
.idea/
|
.idea/
|
||||||
|
.claude
|
||||||
.clangd
|
.clangd
|
||||||
|
|
||||||
# pixi environments
|
# pixi environments
|
||||||
.env
|
.env
|
||||||
.pixi/*
|
.pixi/*
|
||||||
!.pixi/config.toml
|
!.pixi/config.toml
|
||||||
|
|
||||||
.codex/
|
|
||||||
.claude/*
|
|
||||||
!.claude/CLAUDE.md
|
|
||||||
!.claude/commands/
|
|
||||||
|
|||||||
83
.vscode/launch.json
vendored
83
.vscode/launch.json
vendored
@@ -1,83 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"type": "lldb",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Debug clice",
|
|
||||||
"program": "${workspaceFolder}/build/Debug/bin/clice",
|
|
||||||
"args": ["--mode=socket", "--port=50051"],
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "lldb",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Debug clice (socket, RelWithDebInfo)",
|
|
||||||
"program": "${workspaceFolder}/build/RelWithDebInfo/bin/clice",
|
|
||||||
"args": ["--mode=socket", "--port=50051"],
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "VSCode Extension (pipe)",
|
|
||||||
"type": "extensionHost",
|
|
||||||
"request": "launch",
|
|
||||||
"args": [
|
|
||||||
"--disable-extension=llvm-vs-code-extensions.vscode-clangd",
|
|
||||||
"--disable-extension=ms-vscode.cpptools",
|
|
||||||
"--disable-extension=ms-vscode.cpptools-extension-pack",
|
|
||||||
"--extensionDevelopmentPath=${workspaceFolder}/editors/vscode"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"CLICE_MODE": "pipe"
|
|
||||||
},
|
|
||||||
"outFiles": ["${workspaceFolder}/editors/vscode/dist/**/*.js"],
|
|
||||||
"preLaunchTask": "npm: watch vscode ext"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "VSCode Extension (socket)",
|
|
||||||
"type": "extensionHost",
|
|
||||||
"request": "launch",
|
|
||||||
"args": [
|
|
||||||
"--disable-extension=llvm-vs-code-extensions.vscode-clangd",
|
|
||||||
"--disable-extension=ms-vscode.cpptools",
|
|
||||||
"--disable-extension=ms-vscode.cpptools-extension-pack",
|
|
||||||
"--extensionDevelopmentPath=${workspaceFolder}/editors/vscode"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"CLICE_MODE": "socket"
|
|
||||||
},
|
|
||||||
"outFiles": ["${workspaceFolder}/editors/vscode/dist/**/*.js"],
|
|
||||||
"preLaunchTask": "npm: watch vscode ext"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "lldb",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Unit Test",
|
|
||||||
"program": "${workspaceFolder}/build/Debug/bin/unit_tests",
|
|
||||||
"args": ["--test-dir=./tests/data", "--test-filter=${input:filter}"],
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "lldb",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Release Unit Test",
|
|
||||||
"program": "${workspaceFolder}/build/RelWithDebInfo/bin/unit_tests",
|
|
||||||
"args": ["--test-dir=./tests/data", "--test-filter=${input:filter}"],
|
|
||||||
"cwd": "${workspaceFolder}"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"compounds": [
|
|
||||||
{
|
|
||||||
"name": "clice + VSCode Extension (socket)",
|
|
||||||
"configurations": ["Debug clice", "VSCode Extension (socket)"],
|
|
||||||
"stopAll": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inputs": [
|
|
||||||
{
|
|
||||||
"id": "filter",
|
|
||||||
"type": "promptString",
|
|
||||||
"description": "Unit Test Filter"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
42
.vscode/tasks.json
vendored
42
.vscode/tasks.json
vendored
@@ -1,42 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "npm: install vscode deps",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "pnpm",
|
|
||||||
"args": ["install"],
|
|
||||||
"options": {
|
|
||||||
"cwd": "${workspaceFolder}/editors/vscode"
|
|
||||||
},
|
|
||||||
"problemMatcher": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "npm: watch vscode ext",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "pnpm",
|
|
||||||
"args": ["run", "watch"],
|
|
||||||
"options": {
|
|
||||||
"cwd": "${workspaceFolder}/editors/vscode"
|
|
||||||
},
|
|
||||||
"dependsOn": "npm: install vscode deps",
|
|
||||||
"problemMatcher": "$ts-webpack-watch",
|
|
||||||
"isBackground": true,
|
|
||||||
"presentation": {
|
|
||||||
"reveal": "never",
|
|
||||||
"group": "watchers"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "npm: package vscode ext",
|
|
||||||
"type": "shell",
|
|
||||||
"command": "pnpm",
|
|
||||||
"args": ["run", "package"],
|
|
||||||
"options": {
|
|
||||||
"cwd": "${workspaceFolder}/editors/vscode"
|
|
||||||
},
|
|
||||||
"dependsOn": "npm: install vscode deps",
|
|
||||||
"problemMatcher": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
245
CMakeLists.txt
245
CMakeLists.txt
@@ -13,45 +13,14 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
|
|||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
|
||||||
|
|
||||||
option(CLICE_ENABLE_LTO "Enable ThinLTO for all targets" OFF)
|
option(CLICE_ENABLE_LTO "Enable ThinLTO for all targets" OFF)
|
||||||
option(CLICE_USE_LIBCXX "Use libc++ instead of libstdc++" OFF)
|
|
||||||
option(CLICE_OFFLINE_BUILD "Disable network downloads during configuration" OFF)
|
|
||||||
option(CLICE_ENABLE_TEST "Build unit tests" OFF)
|
|
||||||
option(CLICE_CI_ENVIRONMENT "Enable CI-specific configuration" OFF)
|
|
||||||
option(CLICE_ENABLE_BENCHMARK "Build benchmarks" OFF)
|
|
||||||
option(CLICE_RELEASE "Enable release packaging (LTO + strip + pack)" OFF)
|
|
||||||
|
|
||||||
# Global flags that apply to all targets (including FetchContent dependencies).
|
|
||||||
if(NOT MSVC)
|
|
||||||
add_compile_options(-ffunction-sections -fdata-sections)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
# https://conda-forge.org/docs/maintainer/knowledge_base/#newer-c-features-with-old-sdk
|
|
||||||
add_compile_definitions(_LIBCPP_DISABLE_AVAILABILITY=1)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
|
|
||||||
CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,/OPT:REF")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,/OPT:REF")
|
|
||||||
elseif(APPLE)
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,-dead_strip")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,-dead_strip")
|
|
||||||
else()
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -static-libstdc++ -static-libgcc -Wl,--gc-sections")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,--gc-sections")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
# Make sure all third libraries are affected by ABI related options
|
||||||
if(CLICE_USE_LIBCXX)
|
if(CLICE_USE_LIBCXX)
|
||||||
string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++")
|
string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++")
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -stdlib=libc++")
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " -stdlib=libc++")
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -stdlib=libc++")
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -stdlib=libc++")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(CLICE_RELEASE)
|
|
||||||
set(CLICE_ENABLE_LTO ON)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CLICE_ENABLE_LTO)
|
if(CLICE_ENABLE_LTO)
|
||||||
string(APPEND CMAKE_C_FLAGS " -flto=thin")
|
string(APPEND CMAKE_C_FLAGS " -flto=thin")
|
||||||
string(APPEND CMAKE_CXX_FLAGS " -flto=thin")
|
string(APPEND CMAKE_CXX_FLAGS " -flto=thin")
|
||||||
@@ -60,12 +29,17 @@ if(CLICE_ENABLE_LTO)
|
|||||||
string(APPEND CMAKE_MODULE_LINKER_FLAGS " -flto=thin")
|
string(APPEND CMAKE_MODULE_LINKER_FLAGS " -flto=thin")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
add_compile_options(-fsanitize=address)
|
add_compile_options(-fsanitize=address)
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
if(NOT WIN32)
|
||||||
# clang-cl (MSVC frontend): manually link ASan runtime since clang-cl
|
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
|
||||||
# doesn't handle -fsanitize=address linking automatically.
|
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND ${CMAKE_CXX_COMPILER} --print-resource-dir
|
COMMAND ${CMAKE_CXX_COMPILER} --print-resource-dir
|
||||||
OUTPUT_VARIABLE CLANG_RESOURCE_DIR
|
OUTPUT_VARIABLE CLANG_RESOURCE_DIR
|
||||||
@@ -75,47 +49,27 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|||||||
link_directories(${ASAN_LIB_PATH})
|
link_directories(${ASAN_LIB_PATH})
|
||||||
|
|
||||||
set(ASAN_LINK_FLAGS "")
|
set(ASAN_LINK_FLAGS "")
|
||||||
|
|
||||||
list(APPEND ASAN_LINK_FLAGS "clang_rt.asan_dynamic-x86_64.lib")
|
list(APPEND ASAN_LINK_FLAGS "clang_rt.asan_dynamic-x86_64.lib")
|
||||||
list(APPEND ASAN_LINK_FLAGS "/wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib")
|
list(APPEND ASAN_LINK_FLAGS "/wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib")
|
||||||
|
|
||||||
foreach(flag ${ASAN_LINK_FLAGS})
|
foreach(flag ${ASAN_LINK_FLAGS})
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " ${flag}")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${flag}")
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " ${flag}")
|
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${flag}")
|
||||||
string(APPEND CMAKE_MODULE_LINKER_FLAGS " ${flag}")
|
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${flag}")
|
||||||
endforeach()
|
endforeach()
|
||||||
else()
|
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
|
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
|
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(APPLE)
|
||||||
# Disable Identical COMDAT Folding in Debug to avoid ASan ODR false positives.
|
# https://conda-forge.org/docs/maintainer/knowledge_base/#newer-c-features-with-old-sdk
|
||||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,/OPT:NOICF")
|
string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_DISABLE_AVAILABILITY=1")
|
||||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,/OPT:NOICF")
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include("${PROJECT_SOURCE_DIR}/cmake/package.cmake")
|
include("${PROJECT_SOURCE_DIR}/cmake/package.cmake")
|
||||||
|
|
||||||
# Project-specific options (not applied to third-party deps).
|
|
||||||
add_library(clice_options INTERFACE)
|
add_library(clice_options INTERFACE)
|
||||||
|
|
||||||
if(MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
|
|
||||||
CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
|
|
||||||
target_compile_options(clice_options INTERFACE /GR- /EHs-c- /Zc:preprocessor)
|
|
||||||
else()
|
|
||||||
target_compile_options(clice_options INTERFACE
|
|
||||||
-fno-rtti
|
|
||||||
-fno-exceptions
|
|
||||||
-Wno-deprecated-declarations
|
|
||||||
$<$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang>:-Wno-undefined-inline>
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
target_link_libraries(clice_options INTERFACE version ntdll)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CLICE_ENABLE_TEST)
|
if(CLICE_ENABLE_TEST)
|
||||||
target_compile_definitions(clice_options INTERFACE CLICE_ENABLE_TEST=1)
|
target_compile_definitions(clice_options INTERFACE CLICE_ENABLE_TEST=1)
|
||||||
endif()
|
endif()
|
||||||
@@ -124,54 +78,123 @@ if(CLICE_CI_ENVIRONMENT)
|
|||||||
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT=1)
|
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(FBS_SCHEMA_FILE "${PROJECT_SOURCE_DIR}/src/index/schema.fbs")
|
if(WIN32)
|
||||||
set(GENERATED_HEADER "${PROJECT_BINARY_DIR}/generated/schema_generated.h")
|
target_link_libraries(clice_options INTERFACE version ntdll)
|
||||||
|
|
||||||
if(CMAKE_CROSSCOMPILING)
|
|
||||||
find_program(FLATC_EXECUTABLE flatc REQUIRED)
|
|
||||||
set(FLATC_CMD "${FLATC_EXECUTABLE}")
|
|
||||||
else()
|
|
||||||
set(FLATC_CMD "$<TARGET_FILE:flatc>")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
|
||||||
|
target_link_options(clice_options INTERFACE
|
||||||
|
-fuse-ld=lld-link
|
||||||
|
-Wl,/OPT:REF
|
||||||
|
#,/OPT:NOICF
|
||||||
|
)
|
||||||
|
elseif(APPLE)
|
||||||
|
target_link_options(clice_options INTERFACE
|
||||||
|
-fuse-ld=lld
|
||||||
|
-Wl,-dead_strip
|
||||||
|
)
|
||||||
|
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
|
target_link_options(clice_options INTERFACE
|
||||||
|
-fuse-ld=lld
|
||||||
|
-static-libstdc++ -static-libgcc
|
||||||
|
-Wl,--gc-sections
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
|
||||||
|
CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
|
||||||
|
target_compile_options(clice_options INTERFACE
|
||||||
|
/GR-
|
||||||
|
/EHsc-
|
||||||
|
/Zc:preprocessor
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
target_compile_options(clice_options INTERFACE
|
||||||
|
-fno-rtti
|
||||||
|
-fno-exceptions
|
||||||
|
-Wno-deprecated-declarations
|
||||||
|
-Wno-undefined-inline
|
||||||
|
-ffunction-sections
|
||||||
|
-fdata-sections
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(FBS_SCHEMA_FILE "${CMAKE_CURRENT_SOURCE_DIR}/include/Index/schema.fbs")
|
||||||
|
set(GENERATED_HEADER "${CMAKE_CURRENT_BINARY_DIR}/generated/schema_generated.h")
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT "${GENERATED_HEADER}"
|
OUTPUT ${GENERATED_HEADER}
|
||||||
COMMAND ${FLATC_CMD} --cpp -o "${PROJECT_BINARY_DIR}/generated" "${FBS_SCHEMA_FILE}"
|
COMMAND $<TARGET_FILE:flatc> --cpp -o ${CMAKE_CURRENT_BINARY_DIR}/generated ${FBS_SCHEMA_FILE}
|
||||||
DEPENDS "${FBS_SCHEMA_FILE}"
|
DEPENDS ${FBS_SCHEMA_FILE}
|
||||||
COMMENT "Generating C++ header from ${FBS_SCHEMA_FILE}"
|
COMMENT "Generating C++ header from ${FBS_SCHEMA_FILE}"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(generate_flatbuffers_schema DEPENDS "${GENERATED_HEADER}")
|
add_custom_target(
|
||||||
|
generate_flatbuffers_schema
|
||||||
file(GLOB_RECURSE CLICE_CORE_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/*.cpp")
|
DEPENDS ${GENERATED_HEADER}
|
||||||
add_library(clice-core STATIC ${CLICE_CORE_SOURCES})
|
|
||||||
add_library(clice::core ALIAS clice-core)
|
|
||||||
add_dependencies(clice-core generate_flatbuffers_schema)
|
|
||||||
|
|
||||||
target_include_directories(clice-core PUBLIC
|
|
||||||
"${PROJECT_SOURCE_DIR}/src"
|
|
||||||
"${PROJECT_BINARY_DIR}/generated"
|
|
||||||
)
|
)
|
||||||
target_link_libraries(clice-core PUBLIC
|
|
||||||
|
set(CONFIG_SOURCE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/config/clang-tidy-config.h")
|
||||||
|
set(CONFIG_GENERATED_FILE "${CMAKE_CURRENT_BINARY_DIR}/generated/clang-tidy-config.h")
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${CONFIG_GENERATED_FILE}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CONFIG_SOURCE_FILE} ${CONFIG_GENERATED_FILE}
|
||||||
|
DEPENDS ${CONFIG_SOURCE_FILE}
|
||||||
|
COMMENT "Generating C++ header from ${CONFIG_SOURCE_FILE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_target(
|
||||||
|
generate_config
|
||||||
|
DEPENDS ${CONFIG_GENERATED_FILE}
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(clice_core_deps INTERFACE)
|
||||||
|
target_include_directories(clice_core_deps INTERFACE
|
||||||
|
"${PROJECT_SOURCE_DIR}/include"
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/generated"
|
||||||
|
)
|
||||||
|
target_link_libraries(clice_core_deps INTERFACE
|
||||||
clice_options
|
clice_options
|
||||||
llvm-libs
|
libuv::libuv
|
||||||
spdlog::spdlog
|
spdlog::spdlog
|
||||||
|
tomlplusplus::tomlplusplus
|
||||||
roaring::roaring
|
roaring::roaring
|
||||||
flatbuffers
|
flatbuffers
|
||||||
kota::ipc::lsp
|
llvm-libs
|
||||||
kota::codec::toml
|
|
||||||
simdjson::simdjson
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_library(clice_builtin_api INTERFACE)
|
||||||
|
target_link_libraries(clice_builtin_api INTERFACE clice_core_deps)
|
||||||
|
|
||||||
|
include("${PROJECT_SOURCE_DIR}/cmake/builtin-libraries.cmake")
|
||||||
|
|
||||||
|
clice_include_builtin_library_modules()
|
||||||
|
|
||||||
|
file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/AST/*.cpp"
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/Async/*.cpp"
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/Basic/*.cpp"
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/Compiler/*.cpp"
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/Index/*.cpp"
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/Feature/*.cpp"
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/Server/*.cpp"
|
||||||
|
"${PROJECT_SOURCE_DIR}/src/Support/*.cpp"
|
||||||
|
)
|
||||||
|
add_library(clice-core STATIC "${CLICE_SOURCES}")
|
||||||
|
add_dependencies(clice-core generate_flatbuffers_schema generate_config)
|
||||||
|
target_link_libraries(clice-core PUBLIC clice_core_deps)
|
||||||
|
clice_finalize_builtin_libraries(TARGET clice-core)
|
||||||
|
|
||||||
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
|
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
|
||||||
target_link_libraries(clice PRIVATE clice::core kota::deco)
|
target_link_libraries(clice PRIVATE clice-core)
|
||||||
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
add_custom_target(copy_clang_resource ALL
|
message(STATUS "Copying resource directory for development build")
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
file(
|
||||||
"${LLVM_INSTALL_PATH}/lib/clang"
|
COPY "${LLVM_INSTALL_PATH}/lib/clang"
|
||||||
"${PROJECT_BINARY_DIR}/lib/clang"
|
DESTINATION "${PROJECT_BINARY_DIR}/lib"
|
||||||
COMMENT "Copying clang resource directory"
|
|
||||||
)
|
)
|
||||||
install(
|
install(
|
||||||
DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang"
|
DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang"
|
||||||
@@ -180,35 +203,11 @@ install(
|
|||||||
|
|
||||||
if(CLICE_ENABLE_TEST)
|
if(CLICE_ENABLE_TEST)
|
||||||
file(GLOB_RECURSE CLICE_TEST_SOURCES CONFIGURE_DEPENDS
|
file(GLOB_RECURSE CLICE_TEST_SOURCES CONFIGURE_DEPENDS
|
||||||
"${PROJECT_SOURCE_DIR}/tests/unit/*/*_tests.cpp"
|
"${PROJECT_SOURCE_DIR}/tests/unit/*/*.cpp")
|
||||||
)
|
|
||||||
set(CLICE_TEST_SUPPORT_SOURCES
|
|
||||||
"${PROJECT_SOURCE_DIR}/tests/unit/test/annotation.cpp"
|
|
||||||
"${PROJECT_SOURCE_DIR}/tests/unit/test/tester.cpp"
|
|
||||||
)
|
|
||||||
|
|
||||||
add_executable(unit_tests
|
add_executable(unit_tests
|
||||||
|
"${CLICE_TEST_SOURCES}"
|
||||||
"${PROJECT_SOURCE_DIR}/tests/unit/unit_tests.cc"
|
"${PROJECT_SOURCE_DIR}/tests/unit/unit_tests.cc"
|
||||||
${CLICE_TEST_SOURCES}
|
|
||||||
${CLICE_TEST_SUPPORT_SOURCES}
|
|
||||||
)
|
)
|
||||||
target_include_directories(unit_tests PRIVATE
|
target_include_directories(unit_tests PUBLIC "${PROJECT_SOURCE_DIR}")
|
||||||
"${PROJECT_SOURCE_DIR}/src"
|
target_link_libraries(unit_tests PRIVATE clice-core cpptrace::cpptrace)
|
||||||
"${PROJECT_SOURCE_DIR}/tests/unit"
|
|
||||||
)
|
|
||||||
target_link_libraries(unit_tests PRIVATE clice::core kota::zest kota::deco)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CLICE_ENABLE_BENCHMARK)
|
|
||||||
add_executable(scan_benchmark
|
|
||||||
"${PROJECT_SOURCE_DIR}/benchmarks/scan_benchmark.cpp"
|
|
||||||
)
|
|
||||||
target_include_directories(scan_benchmark PRIVATE
|
|
||||||
"${PROJECT_SOURCE_DIR}/src"
|
|
||||||
)
|
|
||||||
target_link_libraries(scan_benchmark PRIVATE clice::core kota::deco)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CLICE_RELEASE)
|
|
||||||
include("${PROJECT_SOURCE_DIR}/cmake/release.cmake")
|
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||

|

|
||||||
[](https://github.com/clice-io/clice/blob/main/LICENSE)
|
[](https://github.com/clice-io/clice/blob/main/LICENSE)
|
||||||
[](https://github.com/clice-io/clice/actions)
|
[](https://github.com/clice-io/clice/actions)
|
||||||
[](https://docs.clice.io/clice/)
|
[](https://clice.io)
|
||||||
[](https://deepwiki.com/clice-io/clice)
|
[](https://deepwiki.com/clice-io/clice)
|
||||||
[](https://discord.gg/PA3UxW2VA3)
|
[](https://discord.gg/PA3UxW2VA3)
|
||||||
|
|
||||||
@@ -35,4 +35,4 @@ Download the latest `clice` binary from the [releases page](https://github.com/c
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
To learn more about building, installing, and configuring clice, or to dive deep into its features and architecture, please visit our official documentation at [**clice.io**](https://docs.clice.io/clice/).
|
To learn more about building, installing, and configuring clice, or to dive deep into its features and architecture, please visit our official documentation at [**clice.io**](https://clice.io/).
|
||||||
|
|||||||
@@ -1,411 +0,0 @@
|
|||||||
/// Benchmark for scan_dependency_graph on a real compilation database.
|
|
||||||
///
|
|
||||||
/// Usage:
|
|
||||||
/// scan_benchmark [OPTIONS] <compile_commands.json>
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ./build/RelWithDebInfo/bin/scan_benchmark \
|
|
||||||
/// /home/ykiko/C++/clice/.llvm/build-debug/compile_commands.json
|
|
||||||
///
|
|
||||||
/// ./build/RelWithDebInfo/bin/scan_benchmark --log-level info --export graph.json \
|
|
||||||
/// /home/ykiko/C++/clice/.llvm/build-debug/compile_commands.json
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <fstream>
|
|
||||||
#include <map>
|
|
||||||
#include <numeric>
|
|
||||||
#include <print>
|
|
||||||
#include <set>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
#include "command/command.h"
|
|
||||||
#include "support/filesystem.h"
|
|
||||||
#include "support/logging.h"
|
|
||||||
#include "support/path_pool.h"
|
|
||||||
#include "syntax/dependency_graph.h"
|
|
||||||
|
|
||||||
#include "kota/codec/json/serializer.h"
|
|
||||||
#include "kota/deco/deco.h"
|
|
||||||
#include "llvm/Support/FileSystem.h"
|
|
||||||
|
|
||||||
using namespace clice;
|
|
||||||
|
|
||||||
struct BenchmarkOptions {
|
|
||||||
DecoKV(names = {"--log-level"}; help = "Log level: trace, debug, info, warn, error, off";
|
|
||||||
required = false;)
|
|
||||||
<std::string> log_level = "off";
|
|
||||||
|
|
||||||
DecoKV(names = {"--export"}; help = "Export dependency graph as JSON to this path";
|
|
||||||
required = false;)
|
|
||||||
<std::string> export_path;
|
|
||||||
|
|
||||||
DecoKV(names = {"--runs"}; help = "Number of cold start iterations"; required = false;)
|
|
||||||
<int> runs = 20;
|
|
||||||
|
|
||||||
DecoFlag(names = {"-h", "--help"}; help = "Show help message"; required = false;)
|
|
||||||
help;
|
|
||||||
|
|
||||||
DecoInput(meta_var = "CDB"; help = "Path to compile_commands.json"; required = false;)
|
|
||||||
<std::string> cdb_path;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FileNode {
|
|
||||||
std::string path;
|
|
||||||
std::string module_name;
|
|
||||||
std::vector<std::string> includes;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GraphExport {
|
|
||||||
std::vector<FileNode> files;
|
|
||||||
};
|
|
||||||
|
|
||||||
void export_graph_json(const PathPool& path_pool,
|
|
||||||
const DependencyGraph& graph,
|
|
||||||
llvm::StringRef output_path) {
|
|
||||||
// Build reverse module map: path_id -> module_name.
|
|
||||||
llvm::DenseMap<std::uint32_t, llvm::StringRef> path_to_module;
|
|
||||||
for(auto& [name, path_ids]: graph.modules()) {
|
|
||||||
for(auto path_id: path_ids) {
|
|
||||||
path_to_module[path_id] = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GraphExport export_data;
|
|
||||||
for(std::uint32_t id = 0; id < path_pool.paths.size(); id++) {
|
|
||||||
auto inc_ids = graph.get_all_includes(id);
|
|
||||||
if(inc_ids.empty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileNode node;
|
|
||||||
node.path = path_pool.paths[id].str();
|
|
||||||
|
|
||||||
auto mod_it = path_to_module.find(id);
|
|
||||||
if(mod_it != path_to_module.end()) {
|
|
||||||
node.module_name = mod_it->second.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto flagged_id: inc_ids) {
|
|
||||||
auto raw_id = flagged_id & DependencyGraph::PATH_ID_MASK;
|
|
||||||
node.includes.push_back(path_pool.paths[raw_id].str());
|
|
||||||
}
|
|
||||||
|
|
||||||
export_data.files.push_back(std::move(node));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto json = kota::codec::json::to_json(export_data);
|
|
||||||
if(!json) {
|
|
||||||
std::println(stderr, "Failed to serialize dependency graph");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ofstream out(output_path.str());
|
|
||||||
if(!out) {
|
|
||||||
std::println(stderr, "Failed to open output file: {}", output_path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out << *json;
|
|
||||||
std::println("Graph exported to {} ({} files)", output_path, export_data.files.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_report(const ScanReport& report) {
|
|
||||||
std::println("===============================================================");
|
|
||||||
std::println(" Dependency Scan Report");
|
|
||||||
std::println("===============================================================");
|
|
||||||
|
|
||||||
// Timing.
|
|
||||||
std::println("");
|
|
||||||
std::println(" Time: {}ms", report.elapsed_ms);
|
|
||||||
std::println(" Waves: {}", report.waves);
|
|
||||||
|
|
||||||
// File counts.
|
|
||||||
std::println("");
|
|
||||||
std::println(" Files");
|
|
||||||
std::println(" Source files (from CDB): {}", report.source_files);
|
|
||||||
std::println(" Header files (discovered): {}", report.header_files);
|
|
||||||
std::println(" Total: {}", report.total_files);
|
|
||||||
std::println(" Modules: {}", report.modules);
|
|
||||||
|
|
||||||
// Include edges.
|
|
||||||
std::println("");
|
|
||||||
std::println(" Include Edges");
|
|
||||||
std::println(" Total: {}", report.total_edges);
|
|
||||||
std::println(" Unconditional: {}", report.unconditional_edges);
|
|
||||||
std::println(" Conditional: {} (inside #if/#ifdef)", report.conditional_edges);
|
|
||||||
|
|
||||||
// Resolution accuracy.
|
|
||||||
std::println("");
|
|
||||||
std::println(" Resolution");
|
|
||||||
std::println(" #include directives: {}", report.includes_found);
|
|
||||||
std::println(" Resolved: {}", report.includes_resolved);
|
|
||||||
auto unresolved_count = report.includes_found - report.includes_resolved;
|
|
||||||
std::println(" Unresolved: {}", unresolved_count);
|
|
||||||
if(report.includes_found > 0) {
|
|
||||||
double rate = 100.0 * static_cast<double>(report.includes_resolved) /
|
|
||||||
static_cast<double>(report.includes_found);
|
|
||||||
std::println(" Accuracy: {:.1f}%", rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wall-clock phase breakdown.
|
|
||||||
std::println("");
|
|
||||||
std::println(" Phase Breakdown (wall-clock)");
|
|
||||||
std::println(" Config extraction: {}ms (prewarm={}ms, loop={}ms)",
|
|
||||||
report.config_ms,
|
|
||||||
report.prewarm_ms,
|
|
||||||
report.config_loop_ms);
|
|
||||||
std::println(" Dir cache pre-pop: {}ms (overlapped with Phase 1)", report.dir_cache_ms);
|
|
||||||
std::println(" Phase 1 (read+scan, parallel): {}ms", report.phase1_ms);
|
|
||||||
std::println(" Phase 2 (include resolve): {}ms", report.phase2_ms);
|
|
||||||
std::println(" Phase 3 (graph build): {}ms", report.phase3_ms);
|
|
||||||
|
|
||||||
// Per-wave breakdown.
|
|
||||||
if(!report.wave_stats.empty()) {
|
|
||||||
std::println("");
|
|
||||||
std::println(" Per-Wave Breakdown");
|
|
||||||
std::println(" {:>5s} {:>8s} {:>8s} {:>8s} {:>8s} {:>8s} {:>10s} {:>10s}",
|
|
||||||
"Wave",
|
|
||||||
"Files",
|
|
||||||
"P1(ms)",
|
|
||||||
"P2(ms)",
|
|
||||||
"Next",
|
|
||||||
"Prefetch",
|
|
||||||
"DirList",
|
|
||||||
"DirHits");
|
|
||||||
for(std::size_t i = 0; i < report.wave_stats.size(); i++) {
|
|
||||||
auto& ws = report.wave_stats[i];
|
|
||||||
std::println(" {:>5} {:>8} {:>8} {:>8} {:>8} {:>8} {:>10} {:>10}",
|
|
||||||
i,
|
|
||||||
ws.files,
|
|
||||||
ws.phase1_ms,
|
|
||||||
ws.phase2_ms,
|
|
||||||
ws.next_files,
|
|
||||||
ws.prefetch_count,
|
|
||||||
ws.dir_listings,
|
|
||||||
ws.dir_hits);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 2 breakdown.
|
|
||||||
if(report.p2_resolve_us > 0) {
|
|
||||||
auto other_us = report.phase2_ms * 1000 - report.p2_resolve_us;
|
|
||||||
std::println("");
|
|
||||||
std::println(" Phase 2 Breakdown (single-threaded)");
|
|
||||||
std::println(" resolve_include: {:.1f}ms", report.p2_resolve_us / 1000.0);
|
|
||||||
std::println(" Other (cache lookup, intern, graph): {:.1f}ms", other_us / 1000.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cumulative I/O statistics.
|
|
||||||
std::println("");
|
|
||||||
std::println(" I/O Statistics (cumulative across threads)");
|
|
||||||
std::println(" File read: {:.1f}ms (sum of all threads)", report.read_us / 1000.0);
|
|
||||||
std::println(" Lexer scan: {:.1f}ms (sum of all threads)", report.scan_us / 1000.0);
|
|
||||||
std::println(" Filesystem: {:.1f}ms ({} readdir calls, {} dir cache hits)",
|
|
||||||
report.fs_us / 1000.0,
|
|
||||||
report.dir_listings,
|
|
||||||
report.dir_hits);
|
|
||||||
std::println(" File lookups: {}", report.fs_lookups);
|
|
||||||
std::println(" Include cache hits: {}", report.include_cache_hits);
|
|
||||||
std::println(" Scan result cache hits: {}", report.scan_cache_hits);
|
|
||||||
if(report.dir_listings + report.dir_hits > 0) {
|
|
||||||
double hit_rate = 100.0 * static_cast<double>(report.dir_hits) /
|
|
||||||
static_cast<double>(report.dir_listings + report.dir_hits);
|
|
||||||
std::println(" Dir cache hit rate: {:.1f}%", hit_rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::println("");
|
|
||||||
std::println("===============================================================");
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, const char** argv) {
|
|
||||||
auto args = kota::deco::util::argvify(argc, argv);
|
|
||||||
auto result = kota::deco::cli::parse<BenchmarkOptions>(args);
|
|
||||||
|
|
||||||
if(!result.has_value()) {
|
|
||||||
std::println(stderr, "Error: {}", result.error().message);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& opts = result->options;
|
|
||||||
|
|
||||||
if(opts.help.value_or(false) || !opts.cdb_path.has_value()) {
|
|
||||||
std::ostringstream oss;
|
|
||||||
kota::deco::cli::write_usage_for<BenchmarkOptions>(oss, "scan_benchmark [OPTIONS] <cdb>");
|
|
||||||
std::print("{}", oss.str());
|
|
||||||
return opts.help.value_or(false) ? 0 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure logging.
|
|
||||||
auto level = spdlog::level::from_str(*opts.log_level);
|
|
||||||
clice::logging::options.level = level;
|
|
||||||
clice::logging::stderr_logger("scan_benchmark", clice::logging::options);
|
|
||||||
|
|
||||||
// resource_dir() is self-initializing (lazy static) — no setup needed.
|
|
||||||
|
|
||||||
auto& cdb_path = *opts.cdb_path;
|
|
||||||
auto hw_threads = std::thread::hardware_concurrency();
|
|
||||||
auto runs = *opts.runs;
|
|
||||||
if(runs <= 0) {
|
|
||||||
std::println(stderr, "Error: --runs must be positive (got {})", runs);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set UV_THREADPOOL_SIZE if not already set.
|
|
||||||
// Use at least libuv's default (4) so low-core CI runners don't regress.
|
|
||||||
if(!std::getenv("UV_THREADPOOL_SIZE")) {
|
|
||||||
auto pool_size = std::max(hw_threads, 4u);
|
|
||||||
static std::string env = "UV_THREADPOOL_SIZE=" + std::to_string(pool_size);
|
|
||||||
putenv(env.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::println("Hardware threads: {}", hw_threads);
|
|
||||||
std::println("UV_THREADPOOL_SIZE: {}", std::getenv("UV_THREADPOOL_SIZE"));
|
|
||||||
std::println("Log level: {}", *opts.log_level);
|
|
||||||
std::println("CDB: {}", cdb_path);
|
|
||||||
std::println("");
|
|
||||||
|
|
||||||
// Load compilation database.
|
|
||||||
auto t0 = std::chrono::steady_clock::now();
|
|
||||||
|
|
||||||
CompilationDatabase cdb;
|
|
||||||
auto count = cdb.load(cdb_path);
|
|
||||||
|
|
||||||
auto t1 = std::chrono::steady_clock::now();
|
|
||||||
auto load_ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count();
|
|
||||||
|
|
||||||
std::println("CDB loaded: {} entries in {}ms", count, load_ms);
|
|
||||||
|
|
||||||
{
|
|
||||||
std::set<const CompilationInfo*> unique_contexts;
|
|
||||||
std::set<const CanonicalCommand*> unique_canonicals;
|
|
||||||
std::map<const CanonicalCommand*, int> canonical_hist;
|
|
||||||
for(auto& entry: cdb.get_entries()) {
|
|
||||||
unique_contexts.insert(entry.info.ptr);
|
|
||||||
unique_canonicals.insert(entry.info->canonical.ptr);
|
|
||||||
canonical_hist[entry.info->canonical.ptr]++;
|
|
||||||
}
|
|
||||||
double dedup_ratio =
|
|
||||||
unique_contexts.empty() ? 0.0 : static_cast<double>(count) / unique_contexts.size();
|
|
||||||
std::println(
|
|
||||||
"Context dedup: {} files -> {} unique contexts ({:.1f}x), {} unique canonicals",
|
|
||||||
count,
|
|
||||||
unique_contexts.size(),
|
|
||||||
dedup_ratio,
|
|
||||||
unique_canonicals.size());
|
|
||||||
|
|
||||||
// If canonical dedup is poor, dump diagnostics.
|
|
||||||
if(unique_canonicals.size() > 200) {
|
|
||||||
// Sort canonicals by frequency (descending).
|
|
||||||
std::vector<std::pair<int, const CanonicalCommand*>> sorted;
|
|
||||||
for(auto& [ptr, cnt]: canonical_hist)
|
|
||||||
sorted.push_back({cnt, ptr});
|
|
||||||
std::ranges::sort(sorted,
|
|
||||||
std::greater{},
|
|
||||||
&std::pair<int, const CanonicalCommand*>::first);
|
|
||||||
|
|
||||||
// Show top-5 canonical commands.
|
|
||||||
for(int i = 0; i < std::min(5, (int)sorted.size()); i++) {
|
|
||||||
auto [cnt, cmd] = sorted[i];
|
|
||||||
std::println(" canonical[{}] ({} files, {} args):", i, cnt, cmd->arguments.size());
|
|
||||||
for(auto arg: cmd->arguments)
|
|
||||||
std::println(" {}", arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a singleton canonical (count==1) to see what per-file arg leaks in.
|
|
||||||
for(auto& [cnt, cmd]: sorted) {
|
|
||||||
if(cnt == 1) {
|
|
||||||
std::println(" singleton canonical ({} args):", cmd->arguments.size());
|
|
||||||
for(auto arg: cmd->arguments)
|
|
||||||
std::println(" {}", arg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find two canonicals that differ by only a few args.
|
|
||||||
if(sorted.size() >= 2) {
|
|
||||||
auto* a = sorted[0].second;
|
|
||||||
auto* b = sorted[1].second;
|
|
||||||
std::println(" --- Canonical diff (top-1 vs top-2) ---");
|
|
||||||
auto max_len = std::max(a->arguments.size(), b->arguments.size());
|
|
||||||
for(std::size_t i = 0; i < max_len; i++) {
|
|
||||||
llvm::StringRef av = i < a->arguments.size() ? a->arguments[i] : "<missing>";
|
|
||||||
llvm::StringRef bv = i < b->arguments.size() ? b->arguments[i] : "<missing>";
|
|
||||||
if(av != bv)
|
|
||||||
std::println(" DIFF[{}]: '{}' vs '{}'", i, av, bv);
|
|
||||||
else
|
|
||||||
std::println(" SAME[{}]: '{}'", i, av);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::println("\nRunning {} cold start scan(s)...\n", runs);
|
|
||||||
|
|
||||||
PathPool path_pool;
|
|
||||||
DependencyGraph graph;
|
|
||||||
std::vector<std::int64_t> elapsed_times;
|
|
||||||
std::vector<std::int64_t> config_times;
|
|
||||||
std::vector<std::int64_t> phase1_times;
|
|
||||||
std::vector<std::int64_t> phase2_times;
|
|
||||||
elapsed_times.reserve(runs);
|
|
||||||
config_times.reserve(runs);
|
|
||||||
phase1_times.reserve(runs);
|
|
||||||
phase2_times.reserve(runs);
|
|
||||||
|
|
||||||
for(int i = 0; i < runs; i++) {
|
|
||||||
// True cold start: rebuild CDB (clears toolchain & config caches),
|
|
||||||
// reset PathPool and DependencyGraph.
|
|
||||||
cdb = CompilationDatabase{};
|
|
||||||
cdb.load(cdb_path);
|
|
||||||
path_pool = PathPool{};
|
|
||||||
graph = DependencyGraph{};
|
|
||||||
|
|
||||||
auto report = scan_dependency_graph(cdb, path_pool, graph);
|
|
||||||
|
|
||||||
elapsed_times.push_back(report.elapsed_ms);
|
|
||||||
config_times.push_back(report.config_ms);
|
|
||||||
phase1_times.push_back(report.phase1_ms);
|
|
||||||
phase2_times.push_back(report.phase2_ms);
|
|
||||||
|
|
||||||
std::println("[run {:2}] {}ms | config={}ms phase1={}ms phase2={}ms | files={}",
|
|
||||||
i + 1,
|
|
||||||
report.elapsed_ms,
|
|
||||||
report.config_ms,
|
|
||||||
report.phase1_ms,
|
|
||||||
report.phase2_ms,
|
|
||||||
report.total_files);
|
|
||||||
|
|
||||||
// Print detailed report for the first run only.
|
|
||||||
if(i == 0) {
|
|
||||||
std::println("");
|
|
||||||
print_report(report);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Summary statistics.
|
|
||||||
if(runs > 1) {
|
|
||||||
auto stats = [](std::vector<std::int64_t>& v) {
|
|
||||||
std::ranges::sort(v);
|
|
||||||
auto sum = std::accumulate(v.begin(), v.end(), std::int64_t{0});
|
|
||||||
return std::tuple{v.front(), sum / static_cast<std::int64_t>(v.size()), v.back()};
|
|
||||||
};
|
|
||||||
auto [e_min, e_avg, e_max] = stats(elapsed_times);
|
|
||||||
auto [c_min, c_avg, c_max] = stats(config_times);
|
|
||||||
auto [p1_min, p1_avg, p1_max] = stats(phase1_times);
|
|
||||||
auto [p2_min, p2_avg, p2_max] = stats(phase2_times);
|
|
||||||
|
|
||||||
std::println("\n Summary ({} runs) min avg max", runs);
|
|
||||||
std::println(" Total: {:>7} {:>6} {:>6}", e_min, e_avg, e_max);
|
|
||||||
std::println(" Config extraction: {:>7} {:>6} {:>6}", c_min, c_avg, c_max);
|
|
||||||
std::println(" Phase 1 (read+scan):{:>7} {:>6} {:>6}", p1_min, p1_avg, p1_max);
|
|
||||||
std::println(" Phase 2 (resolve): {:>7} {:>6} {:>6}", p2_min, p2_avg, p2_max);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export dependency graph as JSON if requested.
|
|
||||||
if(opts.export_path.has_value()) {
|
|
||||||
export_graph_json(path_pool, graph, *opts.export_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
if(OUTPUT MATCHES "\\.tar\\.gz$")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E tar czf "${OUTPUT}" .
|
|
||||||
WORKING_DIRECTORY "${WORK_DIR}"
|
|
||||||
COMMAND_ERROR_IS_FATAL ANY
|
|
||||||
)
|
|
||||||
elseif(OUTPUT MATCHES "\\.zip$")
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E tar cf "${OUTPUT}" --format=zip .
|
|
||||||
WORKING_DIRECTORY "${WORK_DIR}"
|
|
||||||
COMMAND_ERROR_IS_FATAL ANY
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
message(FATAL_ERROR "Unsupported archive format: ${OUTPUT}")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
message(STATUS "Created: ${OUTPUT}")
|
|
||||||
158
cmake/builtin-libraries.cmake
Normal file
158
cmake/builtin-libraries.cmake
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
include_guard(GLOBAL)
|
||||||
|
|
||||||
|
set(
|
||||||
|
CLICE_BUILTIN_LIBRARY_MODULES
|
||||||
|
"${CLICE_BUILTIN_LIBRARY_MODULES}"
|
||||||
|
CACHE STRING
|
||||||
|
"Semicolon-separated list of CMake modules that register extra builtin clice libraries"
|
||||||
|
)
|
||||||
|
|
||||||
|
function(clice_add_builtin_library)
|
||||||
|
set(options)
|
||||||
|
set(oneValueArgs NAME ENTRYPOINT)
|
||||||
|
set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS COMPILE_OPTIONS)
|
||||||
|
cmake_parse_arguments(CBL "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||||
|
|
||||||
|
if(CBL_UNPARSED_ARGUMENTS)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"clice_add_builtin_library got unexpected arguments: ${CBL_UNPARSED_ARGUMENTS}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_NAME)
|
||||||
|
message(FATAL_ERROR "clice_add_builtin_library requires NAME")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_SOURCES)
|
||||||
|
message(FATAL_ERROR "clice_add_builtin_library(${CBL_NAME}) requires SOURCES")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_ENTRYPOINT)
|
||||||
|
message(FATAL_ERROR "clice_add_builtin_library(${CBL_NAME}) requires ENTRYPOINT")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET clice_builtin_api)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"clice_builtin_api must be defined before calling clice_add_builtin_library(${CBL_NAME})"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(target "clice_builtin_${CBL_NAME}")
|
||||||
|
if(TARGET "${target}")
|
||||||
|
message(FATAL_ERROR "builtin library target '${target}' already exists")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library("${target}" OBJECT)
|
||||||
|
target_sources("${target}" PRIVATE ${CBL_SOURCES})
|
||||||
|
target_link_libraries("${target}" PUBLIC clice_builtin_api)
|
||||||
|
add_dependencies("${target}" generate_flatbuffers_schema generate_config)
|
||||||
|
|
||||||
|
if(CBL_INCLUDE_DIRECTORIES)
|
||||||
|
target_include_directories("${target}" PRIVATE ${CBL_INCLUDE_DIRECTORIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CBL_LINK_LIBRARIES)
|
||||||
|
target_link_libraries("${target}" PUBLIC ${CBL_LINK_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CBL_COMPILE_DEFINITIONS)
|
||||||
|
target_compile_definitions("${target}" PRIVATE ${CBL_COMPILE_DEFINITIONS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CBL_COMPILE_OPTIONS)
|
||||||
|
target_compile_options("${target}" PRIVATE ${CBL_COMPILE_OPTIONS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_property(GLOBAL APPEND PROPERTY CLICE_BUILTIN_LIBRARY_TARGETS "${target}")
|
||||||
|
set_property(GLOBAL APPEND PROPERTY CLICE_BUILTIN_LIBRARY_ENTRYPOINTS "${CBL_ENTRYPOINT}")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(clice_include_builtin_library_modules)
|
||||||
|
foreach(module_path IN LISTS CLICE_BUILTIN_LIBRARY_MODULES)
|
||||||
|
cmake_path(
|
||||||
|
ABSOLUTE_PATH module_path
|
||||||
|
BASE_DIRECTORY "${PROJECT_SOURCE_DIR}"
|
||||||
|
NORMALIZE
|
||||||
|
OUTPUT_VARIABLE module_abs_path
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT EXISTS "${module_abs_path}")
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"builtin library module '${module_path}' does not exist: ${module_abs_path}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include("${module_abs_path}")
|
||||||
|
endforeach()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(clice_finalize_builtin_libraries)
|
||||||
|
set(options)
|
||||||
|
set(oneValueArgs TARGET REGISTRATION_SOURCE)
|
||||||
|
cmake_parse_arguments(CBL "${options}" "${oneValueArgs}" "" ${ARGN})
|
||||||
|
|
||||||
|
if(CBL_UNPARSED_ARGUMENTS)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"clice_finalize_builtin_libraries got unexpected arguments: ${CBL_UNPARSED_ARGUMENTS}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_TARGET)
|
||||||
|
message(FATAL_ERROR "clice_finalize_builtin_libraries requires TARGET")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET "${CBL_TARGET}")
|
||||||
|
message(FATAL_ERROR "target '${CBL_TARGET}' does not exist")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CBL_REGISTRATION_SOURCE)
|
||||||
|
set(CBL_REGISTRATION_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/generated/builtin-libraries.cpp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_property(builtin_targets GLOBAL PROPERTY CLICE_BUILTIN_LIBRARY_TARGETS)
|
||||||
|
get_property(builtin_entrypoints GLOBAL PROPERTY CLICE_BUILTIN_LIBRARY_ENTRYPOINTS)
|
||||||
|
|
||||||
|
if(NOT builtin_targets)
|
||||||
|
set(builtin_targets "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT builtin_entrypoints)
|
||||||
|
set(builtin_entrypoints "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(registration_source_content "#include \"Server/Plugin.h\"\n\nnamespace clice {\n\n")
|
||||||
|
|
||||||
|
foreach(entrypoint IN LISTS builtin_entrypoints)
|
||||||
|
string(APPEND registration_source_content "::clice::PluginInfo ${entrypoint}();\n")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
string(APPEND registration_source_content "\nvoid register_builtin_server_plugins(ServerPluginBuilder& builder) {\n")
|
||||||
|
|
||||||
|
foreach(entrypoint IN LISTS builtin_entrypoints)
|
||||||
|
string(
|
||||||
|
APPEND
|
||||||
|
registration_source_content
|
||||||
|
" ${entrypoint}().register_server_callbacks(builder);\n"
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
string(APPEND registration_source_content "}\n\n} // namespace clice\n")
|
||||||
|
|
||||||
|
get_filename_component(registration_source_dir "${CBL_REGISTRATION_SOURCE}" DIRECTORY)
|
||||||
|
file(MAKE_DIRECTORY "${registration_source_dir}")
|
||||||
|
file(CONFIGURE OUTPUT "${CBL_REGISTRATION_SOURCE}" CONTENT "${registration_source_content}" @ONLY)
|
||||||
|
|
||||||
|
target_sources("${CBL_TARGET}" PRIVATE "${CBL_REGISTRATION_SOURCE}")
|
||||||
|
|
||||||
|
if(builtin_targets)
|
||||||
|
foreach(builtin_target IN LISTS builtin_targets)
|
||||||
|
target_sources("${CBL_TARGET}" PRIVATE $<TARGET_OBJECTS:${builtin_target}>)
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
target_link_libraries("${CBL_TARGET}" PRIVATE ${builtin_targets})
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
@@ -25,22 +25,6 @@ function(setup_llvm LLVM_VERSION)
|
|||||||
list(APPEND LLVM_SETUP_ARGS "--offline")
|
list(APPEND LLVM_SETUP_ARGS "--offline")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(DEFINED CLICE_TARGET_TRIPLE)
|
|
||||||
if(CLICE_TARGET_TRIPLE MATCHES "linux")
|
|
||||||
list(APPEND LLVM_SETUP_ARGS "--target-platform" "Linux")
|
|
||||||
elseif(CLICE_TARGET_TRIPLE MATCHES "darwin")
|
|
||||||
list(APPEND LLVM_SETUP_ARGS "--target-platform" "macosx")
|
|
||||||
elseif(CLICE_TARGET_TRIPLE MATCHES "windows")
|
|
||||||
list(APPEND LLVM_SETUP_ARGS "--target-platform" "Windows")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CLICE_TARGET_TRIPLE MATCHES "^aarch64")
|
|
||||||
list(APPEND LLVM_SETUP_ARGS "--target-arch" "arm64")
|
|
||||||
elseif(CLICE_TARGET_TRIPLE MATCHES "^x86_64")
|
|
||||||
list(APPEND LLVM_SETUP_ARGS "--target-arch" "x64")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND "${Python3_EXECUTABLE}" "${LLVM_SETUP_SCRIPT}" ${LLVM_SETUP_ARGS}
|
COMMAND "${Python3_EXECUTABLE}" "${LLVM_SETUP_SCRIPT}" ${LLVM_SETUP_ARGS}
|
||||||
RESULT_VARIABLE LLVM_SETUP_RESULT
|
RESULT_VARIABLE LLVM_SETUP_RESULT
|
||||||
@@ -117,15 +101,8 @@ function(setup_llvm LLVM_VERSION)
|
|||||||
clangToolingSyntax
|
clangToolingSyntax
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}LLVM[a-zA-Z]*${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/*${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||||
file(GLOB CLANG_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}clang[a-zA-Z]*${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
target_link_libraries(llvm-libs INTERFACE ${LLVM_LIBRARIES})
|
||||||
# TODO: find a better way to find out whether zlib and zstd are needed
|
|
||||||
# Currently link if present in the LLVM lib directory
|
|
||||||
file(GLOB OTHER_REQUIRED_LIBS CONFIGURE_DEPENDS
|
|
||||||
"${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}z${CMAKE_STATIC_LIBRARY_SUFFIX}"
|
|
||||||
"${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}zstd${CMAKE_STATIC_LIBRARY_SUFFIX}"
|
|
||||||
)
|
|
||||||
target_link_libraries(llvm-libs INTERFACE ${LLVM_LIBRARIES} ${CLANG_LIBRARIES} ${OTHER_REQUIRED_LIBS})
|
|
||||||
target_compile_definitions(llvm-libs INTERFACE CLANG_BUILD_STATIC=1)
|
target_compile_definitions(llvm-libs INTERFACE CLANG_BUILD_STATIC=1)
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|||||||
@@ -1,28 +1,56 @@
|
|||||||
include_guard()
|
include_guard()
|
||||||
|
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/llvm.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/llvm.cmake)
|
||||||
setup_llvm("21.1.8")
|
setup_llvm("21.1.4+r1")
|
||||||
|
|
||||||
# install dependencies
|
# install dependencies
|
||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
set(FETCHCONTENT_UPDATES_DISCONNECTED ON)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
set(NULL_DEVICE NUL)
|
||||||
|
else()
|
||||||
|
set(NULL_DEVICE /dev/null)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# libuv
|
||||||
|
FetchContent_Declare(
|
||||||
|
libuv
|
||||||
|
GIT_REPOSITORY https://github.com/libuv/libuv.git
|
||||||
|
GIT_TAG v1.x
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
if(NOT WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
set(ASAN ON CACHE BOOL "Enable AddressSanitizer for libuv" FORCE)
|
||||||
|
endif()
|
||||||
|
set(LIBUV_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
||||||
|
set(LIBUV_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
# spdlog
|
# spdlog
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
spdlog
|
spdlog
|
||||||
GIT_REPOSITORY https://github.com/gabime/spdlog.git
|
GIT_REPOSITORY https://github.com/gabime/spdlog.git
|
||||||
GIT_TAG v1.15.3
|
GIT_TAG v1.15.3
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
# tomlplusplus
|
||||||
|
FetchContent_Declare(
|
||||||
|
tomlplusplus
|
||||||
|
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
|
||||||
|
GIT_TAG v3.4.0
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
)
|
)
|
||||||
set(SPDLOG_USE_STD_FORMAT ON CACHE BOOL "" FORCE)
|
|
||||||
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
|
||||||
|
|
||||||
# croaring
|
# croaring
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
croaring
|
croaring
|
||||||
GIT_REPOSITORY https://github.com/RoaringBitmap/CRoaring.git
|
GIT_REPOSITORY https://github.com/RoaringBitmap/CRoaring.git
|
||||||
GIT_TAG v4.4.2
|
GIT_TAG v4.4.2
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
)
|
)
|
||||||
set(ENABLE_ROARING_TESTS OFF CACHE INTERNAL "" FORCE)
|
set(ENABLE_ROARING_TESTS OFF CACHE INTERNAL "" FORCE)
|
||||||
set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
|
set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
|
||||||
@@ -31,26 +59,43 @@ set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
|
|||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
flatbuffers
|
flatbuffers
|
||||||
GIT_REPOSITORY https://github.com/google/flatbuffers.git
|
GIT_REPOSITORY https://github.com/google/flatbuffers.git
|
||||||
GIT_TAG v25.9.23
|
GIT_TAG v25.9.23
|
||||||
GIT_SHALLOW TRUE
|
GIT_SHALLOW TRUE
|
||||||
)
|
)
|
||||||
set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE)
|
set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE)
|
||||||
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
|
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
FetchContent_Declare(
|
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers)
|
||||||
kotatsu
|
|
||||||
GIT_REPOSITORY https://github.com/clice-io/kotatsu
|
if(CLICE_ENABLE_TEST)
|
||||||
GIT_TAG main
|
# cpptrace
|
||||||
GIT_SHALLOW TRUE
|
FetchContent_Declare(
|
||||||
|
cpptrace
|
||||||
|
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||||
|
GIT_TAG v1.0.4
|
||||||
|
GIT_SHALLOW TRUE
|
||||||
|
)
|
||||||
|
set(CPPTRACE_DISABLE_CXX_20_MODULES ON CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(cpptrace)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_compile_definitions(uv_a PRIVATE _CRT_SECURE_NO_WARNINGS)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT MSVC AND TARGET uv_a)
|
||||||
|
target_compile_options(uv_a PRIVATE
|
||||||
|
"-Wno-unused-function"
|
||||||
|
"-Wno-unused-variable"
|
||||||
|
"-Wno-unused-but-set-variable"
|
||||||
|
"-Wno-deprecated-declarations"
|
||||||
|
"-Wno-missing-braces"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_definitions(spdlog PUBLIC
|
||||||
|
SPDLOG_USE_STD_FORMAT=1
|
||||||
|
SPDLOG_NO_EXCEPTIONS=1
|
||||||
)
|
)
|
||||||
|
|
||||||
set(KOTA_ENABLE_ZEST ON)
|
|
||||||
set(KOTA_ENABLE_TEST OFF)
|
|
||||||
set(KOTA_CODEC_ENABLE_SIMDJSON ON)
|
|
||||||
set(KOTA_CODEC_ENABLE_YYJSON ON)
|
|
||||||
set(KOTA_CODEC_ENABLE_TOML ON)
|
|
||||||
set(KOTA_ENABLE_EXCEPTIONS OFF)
|
|
||||||
set(KOTA_ENABLE_RTTI OFF)
|
|
||||||
|
|
||||||
FetchContent_MakeAvailable(kotatsu spdlog croaring flatbuffers)
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
include_guard()
|
|
||||||
|
|
||||||
set(CLICE_PACK_DIR "${PROJECT_BINARY_DIR}/pack")
|
|
||||||
set(CLICE_SYMBOL_DIR "${PROJECT_BINARY_DIR}/pack-symbol")
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
set(CLICE_ARCHIVE_EXT ".zip")
|
|
||||||
set(CLICE_SYMBOL_NAME "clice.pdb")
|
|
||||||
else()
|
|
||||||
set(CLICE_ARCHIVE_EXT ".tar.gz")
|
|
||||||
if(APPLE)
|
|
||||||
set(CLICE_SYMBOL_NAME "clice.dSYM")
|
|
||||||
else()
|
|
||||||
set(CLICE_SYMBOL_NAME "clice.debug")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
add_custom_target(clice-strip ALL
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
||||||
"$<TARGET_PDB_FILE:clice>"
|
|
||||||
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
|
|
||||||
DEPENDS clice
|
|
||||||
COMMENT "Collecting PDB for clice"
|
|
||||||
)
|
|
||||||
elseif(APPLE)
|
|
||||||
add_custom_target(clice-strip ALL
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
|
|
||||||
COMMAND dsymutil "$<TARGET_FILE:clice>" -o "${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
|
|
||||||
COMMAND strip -x "$<TARGET_FILE:clice>"
|
|
||||||
DEPENDS clice
|
|
||||||
COMMENT "Extracting dSYM and stripping clice"
|
|
||||||
)
|
|
||||||
else()
|
|
||||||
add_custom_target(clice-strip ALL
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}"
|
|
||||||
COMMAND ${CMAKE_OBJCOPY} --only-keep-debug "$<TARGET_FILE:clice>" "${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}"
|
|
||||||
COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded "$<TARGET_FILE:clice>"
|
|
||||||
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink="${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "$<TARGET_FILE:clice>"
|
|
||||||
DEPENDS clice
|
|
||||||
COMMENT "Extracting debug symbols and stripping clice"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_custom_target(clice-pack ALL
|
|
||||||
DEPENDS clice-strip copy_clang_resource
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${CLICE_PACK_DIR}"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_PACK_DIR}/clice/bin"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy "$<TARGET_FILE:clice>" "${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}
|
|
||||||
-DOUTPUT="${PROJECT_BINARY_DIR}/clice${CLICE_ARCHIVE_EXT}"
|
|
||||||
-DWORK_DIR="${CLICE_PACK_DIR}"
|
|
||||||
-P "${PROJECT_SOURCE_DIR}/cmake/archive.cmake"
|
|
||||||
COMMENT "Packaging clice distribution"
|
|
||||||
)
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
set(CLICE_COPY_SYMBOL_CMD ${CMAKE_COMMAND} -E copy_directory
|
|
||||||
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "${CLICE_SYMBOL_DIR}/pack/${CLICE_SYMBOL_NAME}")
|
|
||||||
else()
|
|
||||||
set(CLICE_COPY_SYMBOL_CMD ${CMAKE_COMMAND} -E copy
|
|
||||||
"${CLICE_SYMBOL_DIR}/${CLICE_SYMBOL_NAME}" "${CLICE_SYMBOL_DIR}/pack/")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_custom_target(clice-pack-symbol ALL
|
|
||||||
DEPENDS clice-strip
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${CLICE_SYMBOL_DIR}/pack"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CLICE_SYMBOL_DIR}/pack"
|
|
||||||
COMMAND ${CLICE_COPY_SYMBOL_CMD}
|
|
||||||
COMMAND ${CMAKE_COMMAND}
|
|
||||||
-DOUTPUT="${PROJECT_BINARY_DIR}/clice-symbol${CLICE_ARCHIVE_EXT}"
|
|
||||||
-DWORK_DIR="${CLICE_SYMBOL_DIR}/pack"
|
|
||||||
-P "${PROJECT_SOURCE_DIR}/cmake/archive.cmake"
|
|
||||||
COMMENT "Packaging clice debug symbols"
|
|
||||||
)
|
|
||||||
@@ -1,73 +1,44 @@
|
|||||||
cmake_minimum_required(VERSION 3.30)
|
cmake_minimum_required(VERSION 3.30)
|
||||||
|
|
||||||
# Cross-compilation support via CLICE_TARGET_TRIPLE.
|
if(WIN32)
|
||||||
# Examples:
|
set(CMAKE_C_COMPILER clang-cl CACHE STRING "C compiler")
|
||||||
# -DCLICE_TARGET_TRIPLE=x86_64-apple-darwin (macOS x64 from arm64)
|
set(CMAKE_CXX_COMPILER clang-cl CACHE STRING "C++ compiler")
|
||||||
# -DCLICE_TARGET_TRIPLE=aarch64-linux-gnu (Linux arm64 from x64)
|
set(AR_PROGRAM_NAME "llvm-lib")
|
||||||
# -DCLICE_TARGET_TRIPLE=aarch64-pc-windows-msvc (Windows arm64 from x64)
|
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "MSVC runtime")
|
||||||
if(DEFINED CLICE_TARGET_TRIPLE)
|
|
||||||
if(CLICE_TARGET_TRIPLE MATCHES "^x86_64-apple-darwin")
|
find_program(LLVM_LLD_LINK_PATH "lld-link")
|
||||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "")
|
if(LLVM_LLD_LINK_PATH)
|
||||||
elseif(CLICE_TARGET_TRIPLE MATCHES "^aarch64-.*linux")
|
set(CMAKE_LINKER "${LLVM_LLD_LINK_PATH}" CACHE FILEPATH "Linker")
|
||||||
set(CMAKE_SYSTEM_NAME Linux)
|
|
||||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
|
||||||
set(CMAKE_C_COMPILER_TARGET "aarch64-linux-gnu" CACHE STRING "")
|
|
||||||
set(CMAKE_CXX_COMPILER_TARGET "aarch64-linux-gnu" CACHE STRING "")
|
|
||||||
if(DEFINED ENV{CONDA_PREFIX} AND NOT DEFINED CMAKE_SYSROOT)
|
|
||||||
set(CMAKE_SYSROOT "$ENV{CONDA_PREFIX}/aarch64-conda-linux-gnu/sysroot" CACHE PATH "")
|
|
||||||
endif()
|
|
||||||
elseif(CLICE_TARGET_TRIPLE MATCHES "^aarch64-.*-windows")
|
|
||||||
set(CMAKE_SYSTEM_NAME Windows)
|
|
||||||
set(CMAKE_SYSTEM_PROCESSOR ARM64)
|
|
||||||
set(CMAKE_C_COMPILER_TARGET "aarch64-pc-windows-msvc" CACHE STRING "")
|
|
||||||
set(CMAKE_CXX_COMPILER_TARGET "aarch64-pc-windows-msvc" CACHE STRING "")
|
|
||||||
endif()
|
endif()
|
||||||
|
else()
|
||||||
|
set(CMAKE_C_COMPILER clang CACHE STRING "C compiler")
|
||||||
|
set(CMAKE_CXX_COMPILER clang++ CACHE STRING "C++ compiler")
|
||||||
|
set(AR_PROGRAM_NAME "llvm-ar")
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=lld" CACHE STRING "Executable linker flags")
|
||||||
|
set(CMAKE_SHARED_LINKER_FLAGS "-fuse-ld=lld" CACHE STRING "Shared library linker flags")
|
||||||
|
set(CMAKE_MODULE_LINKER_FLAGS "-fuse-ld=lld" CACHE STRING "Module linker flags")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(CMAKE_C_COMPILER clang CACHE STRING "")
|
find_program(LLVM_AR_PATH ${AR_PROGRAM_NAME})
|
||||||
set(CMAKE_CXX_COMPILER clang++ CACHE STRING "")
|
|
||||||
|
|
||||||
find_program(LLVM_AR_PATH "llvm-ar")
|
|
||||||
if(LLVM_AR_PATH)
|
if(LLVM_AR_PATH)
|
||||||
set(CMAKE_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
set(CMAKE_AR "${LLVM_AR_PATH}" CACHE FILEPATH "Archiver")
|
||||||
set(CMAKE_C_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
set(CMAKE_C_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "C archiver")
|
||||||
set(CMAKE_CXX_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
set(CMAKE_CXX_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "C++ archiver")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(LLVM_RANLIB_PATH "llvm-ranlib")
|
find_program(LLVM_RANLIB_PATH "llvm-ranlib")
|
||||||
if(LLVM_RANLIB_PATH)
|
if(LLVM_RANLIB_PATH)
|
||||||
set(CMAKE_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
set(CMAKE_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "Ranlib")
|
||||||
set(CMAKE_C_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
set(CMAKE_C_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "C ranlib")
|
||||||
set(CMAKE_CXX_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
set(CMAKE_CXX_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "C++ ranlib")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(LLVM_NM_PATH "llvm-nm")
|
find_program(LLVM_NM_PATH "llvm-nm")
|
||||||
if(LLVM_NM_PATH)
|
if(LLVM_NM_PATH)
|
||||||
set(CMAKE_NM "${LLVM_NM_PATH}" CACHE FILEPATH "")
|
set(CMAKE_NM "${LLVM_NM_PATH}" CACHE FILEPATH "Symbol lister")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_program(LLVM_RC_PATH "llvm-rc")
|
find_program(LLVM_RC_PATH "llvm-rc")
|
||||||
if(LLVM_RC_PATH)
|
if(LLVM_RC_PATH)
|
||||||
set(CMAKE_RC_COMPILER "${LLVM_RC_PATH}" CACHE FILEPATH "")
|
set(CMAKE_RC_COMPILER "${LLVM_RC_PATH}" CACHE FILEPATH "Resource compiler")
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WIN32)
|
|
||||||
find_program(SCCACHE_PATH "sccache")
|
|
||||||
if(SCCACHE_PATH)
|
|
||||||
set(CMAKE_C_COMPILER_LAUNCHER "${SCCACHE_PATH}" CACHE FILEPATH "")
|
|
||||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${SCCACHE_PATH}" CACHE FILEPATH "")
|
|
||||||
endif()
|
|
||||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "")
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
|
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
|
|
||||||
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld-link")
|
|
||||||
else()
|
|
||||||
find_program(CCACHE_PATH "ccache")
|
|
||||||
if(CCACHE_PATH)
|
|
||||||
set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PATH}" CACHE FILEPATH "")
|
|
||||||
set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PATH}" CACHE FILEPATH "")
|
|
||||||
endif()
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
|
||||||
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")
|
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -1,142 +1,83 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "aarch64-linux-gnu-releasedbg-lto.tar.xz",
|
|
||||||
"sha256": "f3444ee840b50933c23656cbee7c4d010e752ac55ca66095b97f7c0e997b13b5",
|
|
||||||
"lto": true,
|
|
||||||
"asan": false,
|
|
||||||
"platform": "linux",
|
|
||||||
"arch": "arm64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "21.1.8",
|
|
||||||
"filename": "aarch64-linux-gnu-releasedbg.tar.xz",
|
|
||||||
"sha256": "b9012bf059e4d8673fb564b5780e5fc78c6a2e47f5cc6a39f444d1879b42dd2a",
|
|
||||||
"lto": false,
|
|
||||||
"asan": false,
|
|
||||||
"platform": "linux",
|
|
||||||
"arch": "arm64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "21.1.8",
|
|
||||||
"filename": "aarch64-windows-msvc-releasedbg-lto.tar.xz",
|
|
||||||
"sha256": "8870d16141ba7f9ea12f5147b8d91329abbbaa4376cd4576667dd323d896dd08",
|
|
||||||
"lto": true,
|
|
||||||
"asan": false,
|
|
||||||
"platform": "windows",
|
|
||||||
"arch": "arm64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "21.1.8",
|
|
||||||
"filename": "aarch64-windows-msvc-releasedbg.tar.xz",
|
|
||||||
"sha256": "ad394e79ec85dd40f942671bb0342ffe54a103eb2baabacb773999d57d80134b",
|
|
||||||
"lto": false,
|
|
||||||
"asan": false,
|
|
||||||
"platform": "windows",
|
|
||||||
"arch": "arm64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "21.1.8",
|
|
||||||
"filename": "arm64-macos-clang-debug-asan.tar.xz",
|
"filename": "arm64-macos-clang-debug-asan.tar.xz",
|
||||||
"sha256": "b02d20e4f7294ee33f49a09dfdd765b3b44135e003ef50e3a760aeee39e3f993",
|
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
|
||||||
"lto": false,
|
"lto": false,
|
||||||
"asan": true,
|
"asan": true,
|
||||||
"platform": "macosx",
|
"platform": "macosx",
|
||||||
"arch": "arm64",
|
|
||||||
"build_type": "Debug"
|
"build_type": "Debug"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "arm64-macos-clang-releasedbg-lto.tar.xz",
|
"filename": "arm64-macos-clang-releasedbg-lto.tar.xz",
|
||||||
"sha256": "e40c21eb0d0b91d9d4ab31212a5cb01ea46707f5c29839414567857e4147604d",
|
"sha256": "300455b169448f9f01ae95e3bc269f489558a4ca3955e3032171cc75feca0e30",
|
||||||
"lto": true,
|
"lto": true,
|
||||||
"asan": false,
|
"asan": false,
|
||||||
"platform": "macosx",
|
"platform": "macosx",
|
||||||
"arch": "arm64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
"build_type": "RelWithDebInfo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "arm64-macos-clang-releasedbg.tar.xz",
|
"filename": "arm64-macos-clang-releasedbg.tar.xz",
|
||||||
"sha256": "e1b01de34f0edfd41c118e4981a93afb35556ae369597e864f4a393db623b926",
|
"sha256": "9abfc6cd65b957d734ffb97610a634fb4a66d3fbe0fcfb5a1c9124ef693c1495",
|
||||||
"lto": false,
|
"lto": false,
|
||||||
"asan": false,
|
"asan": false,
|
||||||
"platform": "macosx",
|
"platform": "macosx",
|
||||||
"arch": "arm64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
"build_type": "RelWithDebInfo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "x64-linux-gnu-debug-asan.tar.xz",
|
"filename": "x64-linux-gnu-debug-asan.tar.xz",
|
||||||
"sha256": "76bb82d822b5377fb5e0fac8abcfba125142e6a0acc02bb36d1fa1532a268646",
|
"sha256": "c1ad3ec476911596a842ac67dd9c9c9475ce9f0a77b81101d3c801840292e7bc",
|
||||||
"lto": false,
|
"lto": false,
|
||||||
"asan": true,
|
"asan": true,
|
||||||
"platform": "linux",
|
"platform": "linux",
|
||||||
"arch": "x64",
|
|
||||||
"build_type": "Debug"
|
"build_type": "Debug"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "x64-linux-gnu-releasedbg-lto.tar.xz",
|
"filename": "x64-linux-gnu-releasedbg-lto.tar.xz",
|
||||||
"sha256": "32f5edddec1e689124f045b586fb402ae30febc05203af7391b088bc8494cd53",
|
"sha256": "8a869c2184d139dbba704e2d712e7a68336458ad2d70622b3eb906c3e3511e54",
|
||||||
"lto": true,
|
"lto": true,
|
||||||
"asan": false,
|
"asan": false,
|
||||||
"platform": "linux",
|
"platform": "linux",
|
||||||
"arch": "x64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
"build_type": "RelWithDebInfo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "x64-linux-gnu-releasedbg.tar.xz",
|
"filename": "x64-linux-gnu-releasedbg.tar.xz",
|
||||||
"sha256": "8ba3c84f23a2a81a86c54780754a61adf99048aa2ac0dc9b9708d0f842d553de",
|
"sha256": "552bab86f715d4f2c027f07eaaf5b3d6b8e430af0b74b470142f3f00da4feec6",
|
||||||
"lto": false,
|
"lto": false,
|
||||||
"asan": false,
|
"asan": false,
|
||||||
"platform": "linux",
|
"platform": "linux",
|
||||||
"arch": "x64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
"build_type": "RelWithDebInfo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "x64-macos-clang-releasedbg-lto.tar.xz",
|
"filename": "x64-windows-msvc-debug-asan.tar.xz",
|
||||||
"sha256": "97e81d6296896d7237f118f728d05291707b9e4e5791e07ce4be8aee0517505d",
|
"sha256": "093667a493d336c22ff3c604c5f1fea2a7d2c927c1179cec44e9a03726906ac1",
|
||||||
"lto": true,
|
|
||||||
"asan": false,
|
|
||||||
"platform": "macosx",
|
|
||||||
"arch": "x64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "21.1.8",
|
|
||||||
"filename": "x64-macos-clang-releasedbg.tar.xz",
|
|
||||||
"sha256": "53c13f8e1082fa2fe2f9c05303de48cb3133bf5f24271f4b3062f1dec578159c",
|
|
||||||
"lto": false,
|
"lto": false,
|
||||||
"asan": false,
|
"asan": true,
|
||||||
"platform": "macosx",
|
"platform": "windows",
|
||||||
"arch": "x64",
|
"build_type": "Debug"
|
||||||
"build_type": "RelWithDebInfo"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "x64-windows-msvc-releasedbg-lto.tar.xz",
|
"filename": "x64-windows-msvc-releasedbg-lto.tar.xz",
|
||||||
"sha256": "16bcf0e4cbc3d2b1204edd619a3837004dacea28eeff0a101c8d0212f936427d",
|
"sha256": "010539e85621dc3c6ecf359d899feb4075aeca5d0bba6625cdbec0e570e79129",
|
||||||
"lto": true,
|
"lto": true,
|
||||||
"asan": false,
|
"asan": false,
|
||||||
"platform": "windows",
|
"platform": "windows",
|
||||||
"arch": "x64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
"build_type": "RelWithDebInfo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"version": "21.1.8",
|
"version": "21.1.4+r1",
|
||||||
"filename": "x64-windows-msvc-releasedbg.tar.xz",
|
"filename": "x64-windows-msvc-releasedbg.tar.xz",
|
||||||
"sha256": "81d31fad05e200726c8178314b0b2045c947483dddd8cb974f4c376ae5f441fa",
|
"sha256": "f473c09fbea10053fac00be409d75dc228d4a38bcbc5e4aeb58b56a4b0dde78e",
|
||||||
"lto": false,
|
"lto": false,
|
||||||
"asan": false,
|
"asan": false,
|
||||||
"platform": "windows",
|
"platform": "windows",
|
||||||
"arch": "x64",
|
|
||||||
"build_type": "RelWithDebInfo"
|
"build_type": "RelWithDebInfo"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default defineConfig({
|
|||||||
title: "clice",
|
title: "clice",
|
||||||
description: "a powerful and modern C++ language server",
|
description: "a powerful and modern C++ language server",
|
||||||
cleanUrls: true,
|
cleanUrls: true,
|
||||||
base: "/clice/",
|
base: "/",
|
||||||
rewrites: {
|
rewrites: {
|
||||||
"en/:rest*": ":rest*",
|
"en/:rest*": ":rest*",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ max_active_file = 8
|
|||||||
cache_dir = "${workspace}/.clice/cache"
|
cache_dir = "${workspace}/.clice/cache"
|
||||||
# Directory for storing index files.
|
# Directory for storing index files.
|
||||||
index_dir = "${workspace}/.clice/index"
|
index_dir = "${workspace}/.clice/index"
|
||||||
logging_dir = "${workspace}/.clice/logs"
|
logging_dir = "${workspace}/.clice/logging"
|
||||||
# Compile commands files or directories to search for compile_commands.json files.
|
# Compile commands files or directories to search for compile_commands.json files.
|
||||||
compile_commands_paths = ["${workspace}/build"]
|
compile_commands_paths = ["${workspace}/build"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
# Server Architecture
|
|
||||||
|
|
||||||
clice uses a **multi-process architecture** where a single **Master Server** coordinates multiple **Worker** processes. This design isolates Clang AST operations (which are memory-heavy and may crash) from the main LSP event loop.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────────┐ JSON/LSP ┌────────────────┐ Bincode/IPC ┌──────────────────┐
|
|
||||||
│ LSP Client │ ◄──────────► │ Master Server │ ◄─────────────► │ Stateful Workers │
|
|
||||||
│ (Editor) │ (stdio) │ │ (stdio) │ (AST cache) │
|
|
||||||
└──────────────┘ │ - Lifecycle │ └──────────────────┘
|
|
||||||
│ - Documents │
|
|
||||||
│ - CDB │ Bincode/IPC ┌──────────────────┐
|
|
||||||
│ - Build drain │ ◄─────────────► │ Stateless Workers│
|
|
||||||
│ - Indexing │ (stdio) │ (one-shot tasks)│
|
|
||||||
└────────────────┘ └──────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Master Server
|
|
||||||
|
|
||||||
The master server (`src/server/master_server.cpp`) is the central coordinator. It runs a single-threaded async event loop and never touches Clang directly. Its responsibilities:
|
|
||||||
|
|
||||||
### LSP Lifecycle
|
|
||||||
|
|
||||||
The server progresses through these states:
|
|
||||||
|
|
||||||
1. **Uninitialized** — waiting for `initialize` request
|
|
||||||
2. **Initialized** — capabilities exchanged, waiting for `initialized` notification
|
|
||||||
3. **Ready** — workers spawned, workspace loaded, accepting requests
|
|
||||||
4. **ShuttingDown** — `shutdown` received, draining work
|
|
||||||
5. **Exited** — `exit` received, stopping the event loop
|
|
||||||
|
|
||||||
On `initialized`, the master:
|
|
||||||
|
|
||||||
- Loads configuration from `clice.toml` (or uses defaults)
|
|
||||||
- Starts the worker pool (spawns stateful + stateless processes)
|
|
||||||
- Loads `compile_commands.json` and builds an include graph
|
|
||||||
- Starts the background indexer coroutine (if enabled)
|
|
||||||
|
|
||||||
### Document Management
|
|
||||||
|
|
||||||
Each open document is tracked in a `DocumentState` with:
|
|
||||||
|
|
||||||
- Current `version` and `text` (kept in sync via `didOpen`/`didChange`)
|
|
||||||
- A `generation` counter to detect stale compile results
|
|
||||||
- Build state flags (`build_running`, `build_requested`, `drain_scheduled`)
|
|
||||||
|
|
||||||
When a document is opened or changed:
|
|
||||||
|
|
||||||
1. The include graph is re-scanned (via dependency directives)
|
|
||||||
2. The compile unit is registered/updated in the `CompileGraph`
|
|
||||||
3. A debounced build is scheduled
|
|
||||||
|
|
||||||
### Build Drain
|
|
||||||
|
|
||||||
The `run_build_drain` coroutine implements debounced compilation:
|
|
||||||
|
|
||||||
1. Wait for the debounce timer (default 200ms) to expire
|
|
||||||
2. Ensure PCH/PCM dependencies are ready via `CompileGraph`
|
|
||||||
3. Send a `compile` request to the assigned stateful worker
|
|
||||||
4. Publish diagnostics from the result (or clear them on failure)
|
|
||||||
5. If more edits arrived during compilation (`build_requested`), loop back to step 2
|
|
||||||
|
|
||||||
This ensures rapid typing doesn't trigger a compile per keystroke.
|
|
||||||
|
|
||||||
### Request Routing
|
|
||||||
|
|
||||||
Feature requests are split between two worker types:
|
|
||||||
|
|
||||||
**Stateful workers** (affinity-routed by file path):
|
|
||||||
|
|
||||||
- `textDocument/hover`
|
|
||||||
- `textDocument/semanticTokens/full`
|
|
||||||
- `textDocument/inlayHint`
|
|
||||||
- `textDocument/foldingRange`
|
|
||||||
- `textDocument/documentSymbol`
|
|
||||||
- `textDocument/documentLink`
|
|
||||||
- `textDocument/codeAction`
|
|
||||||
- `textDocument/definition`
|
|
||||||
|
|
||||||
**Stateless workers** (round-robin):
|
|
||||||
|
|
||||||
- `textDocument/completion`
|
|
||||||
- `textDocument/signatureHelp`
|
|
||||||
|
|
||||||
All feature responses use `RawValue` passthrough — the worker serializes the LSP result to JSON, and the master forwards the raw JSON bytes to the client without deserializing. This avoids bincode↔JSON conversion overhead and serde annotation conflicts.
|
|
||||||
|
|
||||||
## Worker Pool
|
|
||||||
|
|
||||||
The worker pool (`src/server/worker_pool.cpp`) manages spawning and communicating with worker processes. Each worker is a child process of the same `clice` binary, launched with `--mode stateful-worker` or `--mode stateless-worker`.
|
|
||||||
|
|
||||||
### Communication
|
|
||||||
|
|
||||||
Workers communicate with the master via **stdio pipes** using a **bincode** serialization format (via `kota::ipc::BincodePeer`). This is more compact and faster than JSON for internal IPC, while the master handles JSON for the external LSP protocol.
|
|
||||||
|
|
||||||
### Stateful Worker Routing
|
|
||||||
|
|
||||||
Stateful workers use **affinity routing**: each file is consistently assigned to the same worker so that the worker retains the cached AST. Assignment uses a **least-loaded** strategy for new files, with **LRU tracking** to manage ownership.
|
|
||||||
|
|
||||||
When a worker exceeds its document capacity (currently hardcoded at 16 documents), it evicts the least-recently-used document and notifies the master via an `evicted` notification.
|
|
||||||
|
|
||||||
### Stateless Worker Routing
|
|
||||||
|
|
||||||
Stateless workers use simple **round-robin** dispatch. Each request includes the full source text and compilation arguments, so any worker can handle it independently.
|
|
||||||
|
|
||||||
## Stateful Worker
|
|
||||||
|
|
||||||
The stateful worker (`src/server/stateful_worker.cpp`) caches compiled ASTs in memory. Key behavior:
|
|
||||||
|
|
||||||
- **Compile**: Parses source code into a `CompilationUnit`, caches the AST, and returns diagnostics as a `RawValue` (JSON bytes)
|
|
||||||
- **Feature queries**: Look up the cached AST and invoke the corresponding `feature::*` function (hover, semantic tokens, etc.), serializing the result to JSON
|
|
||||||
- **Document updates**: Received as notifications — the worker updates the stored text and marks the document as `dirty`, causing feature queries to return `null` until recompilation
|
|
||||||
- **Eviction**: LRU-based; evicts the oldest document when capacity is exceeded, notifying the master
|
|
||||||
- **Concurrency**: Each document has a per-document `kota::mutex` (strand) to serialize compilation and feature queries. Heavy work (compilation, feature extraction) runs on a thread pool via `kota::queue`.
|
|
||||||
|
|
||||||
## Stateless Worker
|
|
||||||
|
|
||||||
The stateless worker (`src/server/stateless_worker.cpp`) handles one-shot requests that don't benefit from cached ASTs:
|
|
||||||
|
|
||||||
- **Completion**: Creates a fresh compilation with `CompilationKind::Completion` and invokes `feature::code_complete`
|
|
||||||
- **Signature help**: Similar to completion, using `feature::signature_help`
|
|
||||||
- **Build PCH**: Compiles a precompiled header to a temporary file
|
|
||||||
- **Build PCM**: Compiles a C++20 module interface to a temporary file
|
|
||||||
- **Index**: Compiles a file for indexing (TUIndex generation — currently a stub)
|
|
||||||
|
|
||||||
All requests are dispatched to a thread pool via `kota::queue`.
|
|
||||||
|
|
||||||
## Compile Graph
|
|
||||||
|
|
||||||
The compile graph (`src/server/compile_graph.cpp`) tracks compilation unit dependencies as a DAG. It handles:
|
|
||||||
|
|
||||||
- **Registration**: Each file registers its included dependencies
|
|
||||||
- **Cascade invalidation**: When a file changes, all transitive dependents are marked dirty and their ongoing compilations are cancelled
|
|
||||||
- **Dependency compilation**: Before compiling a file, `compile_deps` ensures all dependencies (PCH, PCMs) are built first
|
|
||||||
- **Cancellation**: Uses `kota::cancellation_source` to abort in-flight compilations when files are invalidated
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
The server reads configuration from `clice.toml` (or `.clice/config.toml`) in the workspace root. If no config file exists, sensible defaults are computed from system resources:
|
|
||||||
|
|
||||||
| Setting | Default | Description |
|
|
||||||
| ------------------------ | --------------------- | ------------------------------------------- |
|
|
||||||
| `stateful_worker_count` | CPU cores / 4 | Number of stateful worker processes |
|
|
||||||
| `stateless_worker_count` | CPU cores / 4 | Number of stateless worker processes |
|
|
||||||
| `worker_memory_limit` | 4 GB | Memory limit per stateful worker |
|
|
||||||
| `compile_commands_path` | auto-detect | Path to `compile_commands.json` |
|
|
||||||
| `cache_dir` | `<workspace>/.clice/` | Cache directory for PCH/PCM files |
|
|
||||||
| `debounce_ms` | 200 | Debounce interval for recompilation |
|
|
||||||
| `enable_indexing` | true | Enable background indexing |
|
|
||||||
| `idle_timeout_ms` | 3000 | Idle time before background indexing starts |
|
|
||||||
|
|
||||||
String values support `${workspace}` substitution.
|
|
||||||
|
|
||||||
## IPC Protocol
|
|
||||||
|
|
||||||
The master and workers communicate using custom RPC messages defined in `src/server/protocol.h`. Each message type has a `RequestTraits` or `NotificationTraits` specialization that defines the method name and result type.
|
|
||||||
|
|
||||||
### Stateful Worker Messages
|
|
||||||
|
|
||||||
| Method | Direction | Purpose |
|
|
||||||
| ----------------------------- | ------------ | ------------------------------------- |
|
|
||||||
| `clice/worker/compile` | Request | Compile source and return diagnostics |
|
|
||||||
| `clice/worker/hover` | Request | Get hover info at position |
|
|
||||||
| `clice/worker/semanticTokens` | Request | Get semantic tokens for file |
|
|
||||||
| `clice/worker/inlayHints` | Request | Get inlay hints for range |
|
|
||||||
| `clice/worker/foldingRange` | Request | Get folding ranges |
|
|
||||||
| `clice/worker/documentSymbol` | Request | Get document symbols |
|
|
||||||
| `clice/worker/documentLink` | Request | Get document links |
|
|
||||||
| `clice/worker/codeAction` | Request | Get code actions for range |
|
|
||||||
| `clice/worker/goToDefinition` | Request | Go to definition at position |
|
|
||||||
| `clice/worker/documentUpdate` | Notification | Update document text (marks dirty) |
|
|
||||||
| `clice/worker/evict` | Notification | Master → Worker: evict a document |
|
|
||||||
| `clice/worker/evicted` | Notification | Worker → Master: document was evicted |
|
|
||||||
|
|
||||||
### Stateless Worker Messages
|
|
||||||
|
|
||||||
| Method | Direction | Purpose |
|
|
||||||
| ---------------------------- | --------- | ---------------------------- |
|
|
||||||
| `clice/worker/completion` | Request | Code completion at position |
|
|
||||||
| `clice/worker/signatureHelp` | Request | Signature help at position |
|
|
||||||
| `clice/worker/buildPCH` | Request | Build precompiled header |
|
|
||||||
| `clice/worker/buildPCM` | Request | Build C++20 module interface |
|
|
||||||
| `clice/worker/index` | Request | Index a translation unit |
|
|
||||||
@@ -32,6 +32,18 @@ pixi run integration-test Debug
|
|||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If you want to develop directly with `cmake`, `ninja`, `clang++`, etc., run `pixi shell` to enter a shell with all env vars configured.
|
> If you want to develop directly with `cmake`, `ninja`, `clang++`, etc., run `pixi shell` to enter a shell with all env vars configured.
|
||||||
|
|
||||||
|
### XMake
|
||||||
|
|
||||||
|
We also support building with XMake:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# config & build (default releasedbg)
|
||||||
|
pixi run xmake
|
||||||
|
|
||||||
|
# unit & integration
|
||||||
|
pixi run xmake-test
|
||||||
|
```
|
||||||
|
|
||||||
## Manual Build
|
## Manual Build
|
||||||
|
|
||||||
If you plan to build manually, first ensure your toolchain matches the versions defined in `pixi.toml`.
|
If you plan to build manually, first ensure your toolchain matches the versions defined in `pixi.toml`.
|
||||||
@@ -57,6 +69,24 @@ Optional build options:
|
|||||||
| CLICE_ENABLE_TEST | OFF | Build clice unit tests |
|
| CLICE_ENABLE_TEST | OFF | Build clice unit tests |
|
||||||
| CLICE_USE_LIBCXX | OFF | Build clice with libc++ (adds `-std=libc++`); if enabled, ensure the LLVM libs are also built with libc++ |
|
| CLICE_USE_LIBCXX | OFF | Build clice with libc++ (adds `-std=libc++`); if enabled, ensure the LLVM libs are also built with libc++ |
|
||||||
| CLICE_CI_ENVIRONMENT | OFF | Enable the `CLICE_CI_ENVIRONMENT` macro; some tests only run in CI |
|
| CLICE_CI_ENVIRONMENT | OFF | Enable the `CLICE_CI_ENVIRONMENT` macro; some tests only run in CI |
|
||||||
|
| CLICE_BUILTIN_LIBRARY_MODULES | "" | Semicolon-separated list of CMake modules that register builtin libraries compiled into `clice`; see [Builtin Libraries](./builtin-library.md) |
|
||||||
|
|
||||||
|
### XMake
|
||||||
|
|
||||||
|
Build clice with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xmake f -c --mode=releasedbg --toolchain=clang
|
||||||
|
xmake build --all
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional build options:
|
||||||
|
|
||||||
|
| Option | Default | Effect |
|
||||||
|
| ------------- | ------- | ---------------------------------------- |
|
||||||
|
| --llvm | "" | Build clice with LLVM from a custom path |
|
||||||
|
| --enable_test | false | Build clice unit tests |
|
||||||
|
| --ci | false | Enable `CLICE_CI_ENVIRONMENT` |
|
||||||
|
|
||||||
## About LLVM
|
## About LLVM
|
||||||
|
|
||||||
@@ -64,7 +94,7 @@ clice calls Clang APIs to parse C++ code, so it must link against LLVM/Clang. Be
|
|||||||
|
|
||||||
Two ways to satisfy this dependency:
|
Two ways to satisfy this dependency:
|
||||||
|
|
||||||
1. We publish prebuilt binaries of the LLVM version we use at [clice-llvm](https://github.com/clice-io/clice-llvm/releases) for CI and release builds. During builds, cmake downloads these LLVM libs by default.
|
1. We publish prebuilt binaries of the LLVM version we use at [clice-llvm](https://github.com/clice-io/clice-llvm/releases) for CI and release builds. During builds, cmake and xmake download these LLVM libs by default.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
>
|
>
|
||||||
|
|||||||
101
docs/en/dev/builtin-library.md
Normal file
101
docs/en/dev/builtin-library.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Builtin Libraries
|
||||||
|
|
||||||
|
Builtin libraries are compiled directly into the `clice` binary instead of being loaded later with `--plugin-path`.
|
||||||
|
|
||||||
|
This is useful when:
|
||||||
|
|
||||||
|
- your plugin sources live outside the `clice` source tree
|
||||||
|
- you want the builtin to be part of the default executable
|
||||||
|
- you need extra include directories, compile definitions, or link dependencies during the main build
|
||||||
|
|
||||||
|
## CMake Entry Point
|
||||||
|
|
||||||
|
`clice` now exposes a small helper module at [cmake/builtin-libraries.cmake](/cmake/builtin-libraries.cmake).
|
||||||
|
|
||||||
|
Extra builtin libraries are registered through the cache variable `CLICE_BUILTIN_LIBRARY_MODULES`.
|
||||||
|
|
||||||
|
Each value in `CLICE_BUILTIN_LIBRARY_MODULES` must be a CMake file. During configure, `clice` includes those files, and each file calls `clice_add_builtin_library(...)`.
|
||||||
|
|
||||||
|
## Minimal Module
|
||||||
|
|
||||||
|
Create a CMake file in your external project, for example `/path/to/my-plugin/clice-builtin.cmake`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
clice_add_builtin_library(
|
||||||
|
NAME my_plugin
|
||||||
|
SOURCES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/src/MyPlugin.cpp"
|
||||||
|
INCLUDE_DIRECTORIES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||||
|
ENTRYPOINT
|
||||||
|
clice_get_my_plugin_server_plugin_info
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then configure `clice` with:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -G Ninja \
|
||||||
|
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/my-plugin/clice-builtin.cmake"
|
||||||
|
```
|
||||||
|
|
||||||
|
To load multiple modules, pass a semicolon-separated CMake list:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -G Ninja \
|
||||||
|
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/a.cmake;/path/to/b.cmake"
|
||||||
|
```
|
||||||
|
|
||||||
|
## `clice_add_builtin_library`
|
||||||
|
|
||||||
|
The helper accepts the following arguments:
|
||||||
|
|
||||||
|
| Argument | Required | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `NAME` | Yes | Logical name used to create an internal object target |
|
||||||
|
| `SOURCES` | Yes | Source files compiled into `clice`; absolute paths and out-of-tree paths are supported |
|
||||||
|
| `ENTRYPOINT` | Yes | Unique function name in namespace `clice` that returns `::clice::PluginInfo` |
|
||||||
|
| `INCLUDE_DIRECTORIES` | No | Extra include directories for this builtin only |
|
||||||
|
| `LINK_LIBRARIES` | No | Extra libraries or targets needed by this builtin |
|
||||||
|
| `COMPILE_DEFINITIONS` | No | Extra compile definitions for this builtin |
|
||||||
|
| `COMPILE_OPTIONS` | No | Extra compile options for this builtin |
|
||||||
|
|
||||||
|
## Entrypoint Requirements
|
||||||
|
|
||||||
|
Builtin libraries share a single final executable, so each builtin must use its own unique entrypoint function inside namespace `clice`.
|
||||||
|
|
||||||
|
Dynamic plugins use:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
clice_get_server_plugin_info()
|
||||||
|
```
|
||||||
|
|
||||||
|
Builtin libraries should use a unique name such as:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
clice_get_my_plugin_server_plugin_info()
|
||||||
|
```
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "Server/Plugin.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
::clice::PluginInfo clice_get_my_plugin_server_plugin_info() {
|
||||||
|
return {
|
||||||
|
CLICE_PLUGIN_API_VERSION,
|
||||||
|
"MyPlugin",
|
||||||
|
"v0.0.1",
|
||||||
|
CLICE_PLUGIN_DEF_HASH,
|
||||||
|
[](clice::ServerPluginBuilder& builder) {
|
||||||
|
// register callbacks here
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
|
```
|
||||||
|
|
||||||
|
`clice` generates the static registration glue automatically, so once the module is included, no additional edits to `src/clice.cc` are required.
|
||||||
78
docs/en/dev/server-plugin.md
Normal file
78
docs/en/dev/server-plugin.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
You can implement a clice server plugin to extend clice's functionality.
|
||||||
|
|
||||||
|
## Use case
|
||||||
|
|
||||||
|
When you use `clice` as LSP backend of LLM agents, e.g. claude code, you can add plugin to provide some extra features.
|
||||||
|
|
||||||
|
## Writing a plugin
|
||||||
|
|
||||||
|
When a plugin is loaded by the server, it will call `clice_get_server_plugin_info` to obtain information about this plugin and about how to register its customization points.
|
||||||
|
|
||||||
|
This function needs to be implemented by the plugin, see the example below:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
extern "C" ::clice::PluginInfo LLVM_ATTRIBUTE_WEAK
|
||||||
|
clice_get_server_plugin_info() {
|
||||||
|
return {
|
||||||
|
CLICE_PLUGIN_API_VERSION, "MyPlugin", "v0.1", CLICE_PLUGIN_DEF_HASH,
|
||||||
|
[](ServerPluginBuilder builder) { ... }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See [PluginProtocol.h](/include/Server/PluginProtocol.h) for more details.
|
||||||
|
|
||||||
|
## Compiling a plugin
|
||||||
|
|
||||||
|
The plugin must be compiled with the same dependencies and compiler options as clice, otherwise it will cause undefined behavior. [config/llvm-manifest.json](/config/llvm-manifest.json) defines the build information used by clice.
|
||||||
|
|
||||||
|
## Loading plugins
|
||||||
|
|
||||||
|
For security reasons, clice does not allow loading plugins through configuration files, but must specify the plugin path through command line options.
|
||||||
|
|
||||||
|
When `clice` starts, it will load all plugins specified in the command line. You can specify the plugin path through the `--plugin-path` option.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ clice --plugin-path /path/to/my-plugin.so
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting content of `CLICE_PLUGIN_DEF_HASH`
|
||||||
|
|
||||||
|
There are two values to return in the `clice_get_server_plugin_info` function.
|
||||||
|
|
||||||
|
- `CLICE_PLUGIN_API_VERSION` is used to ensure compability of the `clice_get_server_plugin_info` function between the plugin and the server.
|
||||||
|
- `CLICE_PLUGIN_DEF_HASH` is used to ensure the consistency of the C++ declarations between the plugin and the server.
|
||||||
|
|
||||||
|
To debug the content of `CLICE_PLUGIN_DEF_HASH`, you can run following command:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/clice-io/clice.git
|
||||||
|
$ cd clice
|
||||||
|
$ git checkout `clice --version --git-describe`
|
||||||
|
$ python scripts/plugin-def.py content
|
||||||
|
```
|
||||||
|
|
||||||
|
You will get a C source code file, content of which is like this:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#if 0
|
||||||
|
// begin of config/llvm-manifest.json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"version": "21.1.4+r1",
|
||||||
|
"filename": "arm64-macos-clang-debug-asan.tar.xz",
|
||||||
|
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
|
||||||
|
"lto": false,
|
||||||
|
"asan": true,
|
||||||
|
"platform": "macosx",
|
||||||
|
"build_type": "Debug"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
...
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Builtin libraries
|
||||||
|
|
||||||
|
If you want to compile a plugin directly into the `clice` binary instead of loading it dynamically, see [Builtin Libraries](./builtin-library.md).
|
||||||
@@ -18,6 +18,13 @@ We use pytest to run integration tests. Please refer to `pyproject.toml` to inst
|
|||||||
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice
|
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you use xmake as your build system, you can run the tests directly with xmake:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ xmake run --verbose unit_tests
|
||||||
|
$ xmake test --verbose integration_tests/default
|
||||||
|
```
|
||||||
|
|
||||||
## Debug
|
## Debug
|
||||||
|
|
||||||
If you want to attach a debugger to clice for debugging, it is recommended to first start clice in socket mode independently, and then connect the client to it.
|
If you want to attach a debugger to clice for debugging, it is recommended to first start clice in socket mode independently, and then connect the client to it.
|
||||||
|
|||||||
@@ -54,73 +54,14 @@ bazel run @hedron_compile_commands//:refresh_all
|
|||||||
|
|
||||||
### Visual Studio
|
### Visual Studio
|
||||||
|
|
||||||
Visual Studio (2019 16.1+) can generate a compilation database via CMake integration. Open your project as a CMake project, then configure the generation in `CMakeSettings.json`:
|
TODO:
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "x64-Debug",
|
|
||||||
"generator": "Ninja",
|
|
||||||
"buildRoot": "${projectDir}\\build",
|
|
||||||
"cmakeCommandArgs": "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, for MSBuild-based projects (`.vcxproj`), you can use [compiledb-vs](https://github.com/pjbroad/compiledb-vs) or [catter](https://github.com/clice-io/catter) to generate the compilation database.
|
|
||||||
|
|
||||||
### Makefile
|
### Makefile
|
||||||
|
|
||||||
For Makefile-based projects, use [bear](https://github.com/rizsotto/Bear) to intercept compilation commands:
|
TODO:
|
||||||
|
|
||||||
```bash
|
|
||||||
bear -- make
|
|
||||||
```
|
|
||||||
|
|
||||||
This will generate a `compile_commands.json` in the current directory. Note that `bear` requires a clean build to capture all commands — run `make clean` before `bear -- make` if needed.
|
|
||||||
|
|
||||||
Alternatively, if you use GNU Make, you can use [compiledb](https://github.com/nicktimko/compiledb):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
compiledb make
|
|
||||||
```
|
|
||||||
|
|
||||||
### Meson
|
|
||||||
|
|
||||||
Meson generates a compilation database automatically during setup:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
meson setup build
|
|
||||||
```
|
|
||||||
|
|
||||||
The `compile_commands.json` will be in the `build` directory.
|
|
||||||
|
|
||||||
### Xmake
|
### Xmake
|
||||||
|
|
||||||
Use one of the following approaches to generate a compilation database.
|
|
||||||
|
|
||||||
#### Command Line
|
|
||||||
|
|
||||||
Run the following command to manually generate a compilation database:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
xmake project -k compile_commands --lsp=clangd build
|
|
||||||
```
|
|
||||||
|
|
||||||
> Compilation database generated manually doesn't automatically update itself. Re-generate if changes are made to the project.
|
|
||||||
|
|
||||||
#### VSCode Extension
|
|
||||||
|
|
||||||
The Xmake official VSCode extension automatically generates the compilation database when `xmake.lua` is updated. However, it generates the database to the `.vscode` directory by default. Add this setting in `settings.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"xmake.compileCommandsDirectory": "build"
|
|
||||||
```
|
|
||||||
|
|
||||||
to explicitly ask the extension to generate the compilation database in `build`.
|
|
||||||
|
|
||||||
### Others
|
### Others
|
||||||
|
|
||||||
For any other build system, you can use [catter](https://github.com/clice-io/catter) to generate a compilation database. It captures compilation commands through a fake compiler approach and is designed to work reliably with any build system that invokes a compiler executable.
|
For any other build system, you can try using [bear](https://github.com/rizsotto/Bear) or [scan-build](https://github.com/rizsotto/scan-build) to intercept compilation commands and obtain the compilation database (no guarantee of success). We plan to write a **new tool** in the future that captures compilation commands through a fake compiler approach.
|
||||||
|
|||||||
@@ -32,6 +32,18 @@ pixi run integration-test Debug
|
|||||||
> [!TIP]
|
> [!TIP]
|
||||||
> 如果你想直接使用 `cmake`, `ninja`, `clang++` 等命令进行开发,请运行 `pixi shell` 进入已配置好环境变量的终端
|
> 如果你想直接使用 `cmake`, `ninja`, `clang++` 等命令进行开发,请运行 `pixi shell` 进入已配置好环境变量的终端
|
||||||
|
|
||||||
|
### XMake
|
||||||
|
|
||||||
|
我们同样支持使用 XMake 构建:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# config & build (default releasedbg)
|
||||||
|
pixi run xmake
|
||||||
|
|
||||||
|
# unit & integration
|
||||||
|
pixi run xmake-test
|
||||||
|
```
|
||||||
|
|
||||||
## Manual Build
|
## Manual Build
|
||||||
|
|
||||||
如果你打算手动构建,请务必先确认你的工具链满足 pixi.toml 中定义的版本要求。
|
如果你打算手动构建,请务必先确认你的工具链满足 pixi.toml 中定义的版本要求。
|
||||||
@@ -57,6 +69,24 @@ cmake -B build -G Ninja \
|
|||||||
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
|
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
|
||||||
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice(添加 `-std=libc++`),如果开启,请确保 LLVM 库也是使用 libc++ 编译的 |
|
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice(添加 `-std=libc++`),如果开启,请确保 LLVM 库也是使用 libc++ 编译的 |
|
||||||
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
|
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
|
||||||
|
| CLICE_BUILTIN_LIBRARY_MODULES | "" | 以分号分隔的 CMake 模块列表,用于注册会被编译进 `clice` 的 builtin library;详见 [Builtin Libraries](./builtin-library.md) |
|
||||||
|
|
||||||
|
### XMake
|
||||||
|
|
||||||
|
使用如下命令即可构建 clice:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xmake f -c --mode=releasedbg --toolchain=clang
|
||||||
|
xmake build --all
|
||||||
|
```
|
||||||
|
|
||||||
|
可选的构建选项:
|
||||||
|
|
||||||
|
| 选项 | 默认值 | 效果 |
|
||||||
|
| ------------- | ------ | ------------------------------------ |
|
||||||
|
| --llvm | "" | 使用自定义路径的 LLVM 库来构建 clice |
|
||||||
|
| --enable_test | false | 是否构建 clice 的单元测试 |
|
||||||
|
| --ci | false | 是否打开 `CLICE_CI_ENVIRONMENT` |
|
||||||
|
|
||||||
## About LLVM
|
## About LLVM
|
||||||
|
|
||||||
@@ -64,7 +94,7 @@ clice 调用 Clang API 来解析 C++ 代码,因此必须链接 LLVM/Clang 库
|
|||||||
|
|
||||||
主要有两种方式解决这个依赖问题:
|
主要有两种方式解决这个依赖问题:
|
||||||
|
|
||||||
1. 我们在 [clice-llvm](https://github.com/clice-io/clice-llvm/releases) 上会发布使用的 LLVM 版本的预编译二进制,用于 CI 或者 release 构建。在构建时 cmake 默认会从此处下载 LLVM 库然后使用。
|
1. 我们在 [clice-llvm](https://github.com/clice-io/clice-llvm/releases) 上会发布使用的 LLVM 版本的预编译二进制,用于 CI 或者 release 构建。在构建时 cmake 和 xmake 默认会从此处下载 LLVM 库然后使用。
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
>
|
>
|
||||||
|
|||||||
101
docs/zh/dev/builtin-library.md
Normal file
101
docs/zh/dev/builtin-library.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Builtin Libraries
|
||||||
|
|
||||||
|
Builtin library 会被直接编译进 `clice` 可执行文件,而不是在运行时通过 `--plugin-path` 动态加载。
|
||||||
|
|
||||||
|
这种方式适合以下场景:
|
||||||
|
|
||||||
|
- 插件源码位于 `clice` 源码树之外
|
||||||
|
- 希望该 builtin 默认随 `clice` 可执行文件一起发布
|
||||||
|
- 需要为该 builtin 单独补充 include 路径、编译宏或链接依赖
|
||||||
|
|
||||||
|
## CMake 入口
|
||||||
|
|
||||||
|
`clice` 现在提供了一个辅助模块:[cmake/builtin-libraries.cmake](/cmake/builtin-libraries.cmake)。
|
||||||
|
|
||||||
|
额外的 builtin library 通过缓存变量 `CLICE_BUILTIN_LIBRARY_MODULES` 注册。
|
||||||
|
|
||||||
|
`CLICE_BUILTIN_LIBRARY_MODULES` 中的每一项都必须是一个 CMake 文件。配置阶段 `clice` 会 `include()` 这些文件,而每个文件都需要调用 `clice_add_builtin_library(...)`。
|
||||||
|
|
||||||
|
## 最小示例
|
||||||
|
|
||||||
|
你可以在外部项目中创建一个 CMake 文件,例如 `/path/to/my-plugin/clice-builtin.cmake`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
clice_add_builtin_library(
|
||||||
|
NAME my_plugin
|
||||||
|
SOURCES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/src/MyPlugin.cpp"
|
||||||
|
INCLUDE_DIRECTORIES
|
||||||
|
"${CMAKE_CURRENT_LIST_DIR}/include"
|
||||||
|
ENTRYPOINT
|
||||||
|
clice_get_my_plugin_server_plugin_info
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在配置 `clice` 时传入:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -G Ninja \
|
||||||
|
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/my-plugin/clice-builtin.cmake"
|
||||||
|
```
|
||||||
|
|
||||||
|
如果要加载多个模块,可以传入以分号分隔的 CMake 列表:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cmake -B build -G Ninja \
|
||||||
|
-DCLICE_BUILTIN_LIBRARY_MODULES="/path/to/a.cmake;/path/to/b.cmake"
|
||||||
|
```
|
||||||
|
|
||||||
|
## `clice_add_builtin_library`
|
||||||
|
|
||||||
|
该辅助函数支持以下参数:
|
||||||
|
|
||||||
|
| 参数 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `NAME` | 是 | 逻辑名称,会用于创建内部 object target |
|
||||||
|
| `SOURCES` | 是 | 要编译进 `clice` 的源文件,支持绝对路径和源码树外路径 |
|
||||||
|
| `ENTRYPOINT` | 是 | `clice` 命名空间中的唯一函数名,返回值类型为 `::clice::PluginInfo` |
|
||||||
|
| `INCLUDE_DIRECTORIES` | 否 | 仅对当前 builtin 生效的额外头文件目录 |
|
||||||
|
| `LINK_LIBRARIES` | 否 | 当前 builtin 额外需要的库或 target |
|
||||||
|
| `COMPILE_DEFINITIONS` | 否 | 当前 builtin 额外需要的编译宏 |
|
||||||
|
| `COMPILE_OPTIONS` | 否 | 当前 builtin 额外需要的编译选项 |
|
||||||
|
|
||||||
|
## Entrypoint 要求
|
||||||
|
|
||||||
|
所有 builtin library 最终都会被链接进同一个可执行文件,因此每个 builtin 都必须在 `clice` 命名空间中使用唯一的入口函数名。
|
||||||
|
|
||||||
|
动态插件通常使用:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
clice_get_server_plugin_info()
|
||||||
|
```
|
||||||
|
|
||||||
|
builtin library 应该改用类似下面的唯一名字:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
clice_get_my_plugin_server_plugin_info()
|
||||||
|
```
|
||||||
|
|
||||||
|
例如:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "Server/Plugin.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
::clice::PluginInfo clice_get_my_plugin_server_plugin_info() {
|
||||||
|
return {
|
||||||
|
CLICE_PLUGIN_API_VERSION,
|
||||||
|
"MyPlugin",
|
||||||
|
"v0.0.1",
|
||||||
|
CLICE_PLUGIN_DEF_HASH,
|
||||||
|
[](clice::ServerPluginBuilder& builder) {
|
||||||
|
// 在这里注册回调
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
|
```
|
||||||
|
|
||||||
|
`clice` 会自动生成静态注册代码,因此只要模块被包含进来,就不需要再手动修改 `src/clice.cc`。
|
||||||
@@ -30,7 +30,7 @@ pixi run publish-vscode
|
|||||||
|
|
||||||
1. `pixi shell -e node`
|
1. `pixi shell -e node`
|
||||||
2. 在 `editors/vscode` 下运行 `pnpm run watch`(增量构建)
|
2. 在 `editors/vscode` 下运行 `pnpm run watch`(增量构建)
|
||||||
3. VSCode 中使用”Run Extension/Launch Extension”调试配置,或执行 `code --extensionDevelopmentPath=$(pwd)/editors/vscode`
|
3. VSCode 中使用 “Run Extension/Launch Extension” 调试配置,或执行 `code --extensionDevelopmentPath=$(pwd)/editors/vscode`
|
||||||
|
|
||||||
常用脚本(在 `pixi shell -e node` 下):
|
常用脚本(在 `pixi shell -e node` 下):
|
||||||
|
|
||||||
|
|||||||
78
docs/zh/dev/server-plugin.md
Normal file
78
docs/zh/dev/server-plugin.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
你可以在 clice 中实现一个 server plugin 来扩展 clice 的功能。
|
||||||
|
|
||||||
|
## 用例
|
||||||
|
|
||||||
|
当你使用 `clice` 作为 LLM 代理的 LSP 后端时,比如 claude code,你可以添加插件来提供一些额外功能。
|
||||||
|
|
||||||
|
## 编写插件
|
||||||
|
|
||||||
|
当一个插件被服务器加载时,它会调用 `clice_get_server_plugin_info` 来获取关于这个插件的信息以及如何注册它的定制点。
|
||||||
|
|
||||||
|
这个函数需要由插件实现,请参考下面的示例:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
extern "C" ::clice::PluginInfo LLVM_ATTRIBUTE_WEAK
|
||||||
|
clice_get_server_plugin_info() {
|
||||||
|
return {
|
||||||
|
CLICE_PLUGIN_API_VERSION, "MyPlugin", "v0.1", CLICE_PLUGIN_DEF_HASH,
|
||||||
|
[](ServerPluginBuilder builder) { ... }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
请参考 [PluginProtocol.h](/include/Server/PluginProtocol.h) 了解更多细节。
|
||||||
|
|
||||||
|
## 编译插件
|
||||||
|
|
||||||
|
插件必须使用与 clice 一致的依赖和编译器选项来编译,否则会导致 undefined behavior。[config/llvm-manifest.json](/config/llvm-manifest.json) 中定义了 clice 使用的构建信息。
|
||||||
|
|
||||||
|
## 加载插件
|
||||||
|
|
||||||
|
为了安全考虑,clice 不允许通过配置文件来加载插件,而必须通过命令行选项来指定插件的路径。
|
||||||
|
|
||||||
|
在 `clice` 启动时,它会加载所有在命令行中指定的插件。你可以通过 `--plugin-path` 选项来指定插件的路径。
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ clice --plugin-path /path/to/my-plugin.so
|
||||||
|
```
|
||||||
|
|
||||||
|
## 获取 `CLICE_PLUGIN_DEF_HASH` 的内容
|
||||||
|
|
||||||
|
在 `clice_get_server_plugin_info` 函数中需要返回两个值。
|
||||||
|
|
||||||
|
- `CLICE_PLUGIN_API_VERSION` 用于确保插件和服务器之间的 `clice_get_server_plugin_info` 函数的一致性。
|
||||||
|
- `CLICE_PLUGIN_DEF_HASH` 用于确保插件和服务器之间的 C++ 声明的一致性。
|
||||||
|
|
||||||
|
要调试 `CLICE_PLUGIN_DEF_HASH` 的内容,你可以运行以下命令:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/clice-io/clice.git
|
||||||
|
$ cd clice
|
||||||
|
$ git checkout `clice --version --git-describe`
|
||||||
|
$ python scripts/plugin-def.py content > /tmp/plugin-proto.h
|
||||||
|
```
|
||||||
|
|
||||||
|
你将会得到一个 C 源码格式的文件,内容大致如下:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#if 0
|
||||||
|
// begin of config/llvm-manifest.json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"version": "21.1.4+r1",
|
||||||
|
"filename": "arm64-macos-clang-debug-asan.tar.xz",
|
||||||
|
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
|
||||||
|
"lto": false,
|
||||||
|
"asan": true,
|
||||||
|
"platform": "macosx",
|
||||||
|
"build_type": "Debug"
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
...
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
## Builtin libraries
|
||||||
|
|
||||||
|
如果你希望把插件直接编译进 `clice` 可执行文件,而不是在运行时动态加载,请参考 [Builtin Libraries](./builtin-library.md)。
|
||||||
@@ -18,6 +18,13 @@ $ ./build/bin/unit_tests --test-dir="./tests/data"
|
|||||||
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice
|
$ pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice
|
||||||
```
|
```
|
||||||
|
|
||||||
|
如果你使用 xmake 作为构建系统,可以直接通过 xmake 运行测试:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ xmake run --verbose unit_tests
|
||||||
|
$ xmake test --verbose integration_tests/default
|
||||||
|
```
|
||||||
|
|
||||||
## Debug
|
## Debug
|
||||||
|
|
||||||
如果想在 clice 上附加调试器并进行调试,推荐先单独以 socket 模式启动 clice,然后再将客户端连接到 clice 上
|
如果想在 clice 上附加调试器并进行调试,推荐先单独以 socket 模式启动 clice,然后再将客户端连接到 clice 上
|
||||||
|
|||||||
@@ -54,73 +54,14 @@ bazel run @hedron_compile_commands//:refresh_all
|
|||||||
|
|
||||||
### Visual Studio
|
### Visual Studio
|
||||||
|
|
||||||
Visual Studio(2019 16.1+)可以通过 CMake 集成来生成编译数据库。将项目作为 CMake 项目打开,然后在 `CMakeSettings.json` 中配置:
|
TODO:
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "x64-Debug",
|
|
||||||
"generator": "Ninja",
|
|
||||||
"buildRoot": "${projectDir}\\build",
|
|
||||||
"cmakeCommandArgs": "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
对于基于 MSBuild 的项目(`.vcxproj`),可以使用 [compiledb-vs](https://github.com/pjbroad/compiledb-vs) 或 [catter](https://github.com/clice-io/catter) 来生成编译数据库。
|
|
||||||
|
|
||||||
### Makefile
|
### Makefile
|
||||||
|
|
||||||
对于基于 Makefile 的项目,使用 [bear](https://github.com/rizsotto/Bear) 来拦截编译命令:
|
TODO:
|
||||||
|
|
||||||
```bash
|
|
||||||
bear -- make
|
|
||||||
```
|
|
||||||
|
|
||||||
这会在当前目录生成 `compile_commands.json`。注意 `bear` 需要干净的构建来捕获所有命令——如果需要的话,在运行 `bear -- make` 之前先执行 `make clean`。
|
|
||||||
|
|
||||||
另外,如果使用 GNU Make,也可以使用 [compiledb](https://github.com/nicktimko/compiledb):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
compiledb make
|
|
||||||
```
|
|
||||||
|
|
||||||
### Meson
|
|
||||||
|
|
||||||
Meson 在配置阶段会自动生成编译数据库:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
meson setup build
|
|
||||||
```
|
|
||||||
|
|
||||||
`compile_commands.json` 会生成在 `build` 目录下。
|
|
||||||
|
|
||||||
### Xmake
|
### Xmake
|
||||||
|
|
||||||
用下列任意方法生成编译数据库。
|
|
||||||
|
|
||||||
#### 命令行手动生成
|
|
||||||
|
|
||||||
在命令行中执行以下命令:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
xmake project -k compile_commands --lsp=clangd build
|
|
||||||
```
|
|
||||||
|
|
||||||
> 通过这种方法生成的编译数据库无法自动更新,需要在项目编译配置更改时手动重新生成。
|
|
||||||
|
|
||||||
#### VSCode 扩展
|
|
||||||
|
|
||||||
Xmake 提供的官方 VSCode 扩展会在 `xmake.lua` 更新时自动生成编译数据库。然而默认情况下,它将编译数据库生成到了 `.vscode` 文件夹。在 `settings.json` 中添加以下配置:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"xmake.compileCommandsDirectory": "build"
|
|
||||||
```
|
|
||||||
|
|
||||||
以将编译数据库的生成目录调整到 `build`,供 clice 使用。
|
|
||||||
|
|
||||||
### Others
|
### Others
|
||||||
|
|
||||||
对于任意其它的构建系统,可以使用 [catter](https://github.com/clice-io/catter) 来生成编译数据库。它通过伪装编译器的方式来捕获编译命令,能够可靠地与任何调用编译器可执行文件的构建系统配合工作。
|
对于任意其它的构建系统,可以尝试使用 [bear](https://github.com/rizsotto/Bear) 或者 [scan-build](https://github.com/rizsotto/scan-build) 来拦截编译命令并获取到编译数据库(不保证成功)。我们计划在未来编写一个**新的工具**,通过假编译器的方式来实现编译命令的捕获。
|
||||||
|
|||||||
9
editors/vscode/.vscode/extensions.json
vendored
Normal file
9
editors/vscode/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"amodio.tsl-problem-matcher",
|
||||||
|
"ms-vscode.extension-test-runner"
|
||||||
|
]
|
||||||
|
}
|
||||||
17
editors/vscode/.vscode/launch.json
vendored
Normal file
17
editors/vscode/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Run Extension",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||||
|
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
|
||||||
|
"preLaunchTask": "${defaultBuildTask}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
editors/vscode/.vscode/settings.json
vendored
Normal file
13
editors/vscode/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
"out": false, // set this to true to hide the "out" folder with the compiled JS files
|
||||||
|
"dist": false // set this to true to hide the "dist" folder with the compiled JS files
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
"out": true, // set this to false to include "out" folder in search results
|
||||||
|
"dist": true // set this to false to include "dist" folder in search results
|
||||||
|
},
|
||||||
|
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
|
||||||
|
"typescript.tsc.autoDetect": "off"
|
||||||
|
}
|
||||||
37
editors/vscode/.vscode/tasks.json
vendored
Normal file
37
editors/vscode/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "watch",
|
||||||
|
"problemMatcher": "$ts-webpack-watch",
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "never",
|
||||||
|
"group": "watchers"
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "watch-tests",
|
||||||
|
"problemMatcher": "$tsc-watch",
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "never",
|
||||||
|
"group": "watchers"
|
||||||
|
},
|
||||||
|
"group": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "tasks: watch-tests",
|
||||||
|
"dependsOn": ["npm: watch", "npm: watch-tests"],
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -62,12 +62,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commands": [
|
"commands": [],
|
||||||
{
|
|
||||||
"command": "clice.restart",
|
|
||||||
"title": "Clice: Restart Language Server"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"semanticTokenTypes": [
|
"semanticTokenTypes": [
|
||||||
{
|
{
|
||||||
"id": "character",
|
"id": "character",
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface Setting {
|
|||||||
export function getSetting(): Setting | undefined {
|
export function getSetting(): Setting | undefined {
|
||||||
const setting = vscode.workspace.getConfiguration("clice");
|
const setting = vscode.workspace.getConfiguration("clice");
|
||||||
const executable = setting.get<string>("executable");
|
const executable = setting.get<string>("executable");
|
||||||
const mode = process.env.CLICE_MODE || setting.get<string>("mode");
|
const mode = setting.get<string>("mode");
|
||||||
|
|
||||||
if (mode !== "pipe" && mode !== "socket") {
|
if (mode !== "pipe" && mode !== "socket") {
|
||||||
vscode.window.showErrorMessage(`Unexpected mode: ${mode}`);
|
vscode.window.showErrorMessage(`Unexpected mode: ${mode}`);
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <concepts>
|
#include "AST/SourceCode.h"
|
||||||
|
#include "Compiler/CompilationUnit.h"
|
||||||
#include "compile/compilation_unit.h"
|
|
||||||
|
|
||||||
#include "clang/AST/RecursiveASTVisitor.h"
|
#include "clang/AST/RecursiveASTVisitor.h"
|
||||||
|
|
||||||
63
include/AST/RelationKind.h
Normal file
63
include/AST/RelationKind.h
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Support/Enum.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
struct RelationKind : refl::Enum<RelationKind, true, uint32_t> {
|
||||||
|
enum Kind : uint32_t {
|
||||||
|
Invalid,
|
||||||
|
Declaration,
|
||||||
|
Definition,
|
||||||
|
Reference,
|
||||||
|
WeakReference,
|
||||||
|
// Write Relation.
|
||||||
|
Read,
|
||||||
|
Write,
|
||||||
|
Interface,
|
||||||
|
Implementation,
|
||||||
|
/// When target is a type definition of source, source is possible type or constructor.
|
||||||
|
TypeDefinition,
|
||||||
|
|
||||||
|
/// When target is a base class of source.
|
||||||
|
Base,
|
||||||
|
/// When target is a derived class of source.
|
||||||
|
Derived,
|
||||||
|
|
||||||
|
/// When target is a constructor of source.
|
||||||
|
Constructor,
|
||||||
|
/// When target is a destructor of source.
|
||||||
|
Destructor,
|
||||||
|
|
||||||
|
// When target is a caller of source.
|
||||||
|
Caller,
|
||||||
|
// When target is a callee of source.
|
||||||
|
Callee,
|
||||||
|
};
|
||||||
|
|
||||||
|
using Enum::Enum;
|
||||||
|
|
||||||
|
constexpr bool isDeclOrDef() {
|
||||||
|
return is_one_of(Declaration, Definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool isReference() {
|
||||||
|
return is_one_of(Reference, WeakReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool isBetweenSymbol() {
|
||||||
|
return is_one_of(Interface,
|
||||||
|
Implementation,
|
||||||
|
TypeDefinition,
|
||||||
|
Base,
|
||||||
|
Derived,
|
||||||
|
Constructor,
|
||||||
|
Destructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool isCall() {
|
||||||
|
return is_one_of(Caller, Callee);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -17,12 +17,9 @@ namespace clice {
|
|||||||
/// completion, you cannot get go-to-definition, etc. To avoid this, we just use
|
/// completion, you cannot get go-to-definition, etc. To avoid this, we just use
|
||||||
/// some heuristics to simplify the dependent names as normal type/expression.
|
/// some heuristics to simplify the dependent names as normal type/expression.
|
||||||
/// For example, `std::vector<T>::value_type` can be simplified as `T`.
|
/// For example, `std::vector<T>::value_type` can be simplified as `T`.
|
||||||
///
|
|
||||||
/// Thread safety: NOT thread-safe. Each compilation unit should have its own resolver.
|
|
||||||
/// The `resolved` cache persists across multiple resolve() calls on the same unit.
|
|
||||||
class TemplateResolver {
|
class TemplateResolver {
|
||||||
public:
|
public:
|
||||||
explicit TemplateResolver(clang::Sema& sema) : sema(sema) {}
|
TemplateResolver(clang::Sema& sema) : sema(sema) {}
|
||||||
|
|
||||||
clang::QualType resolve(clang::QualType type);
|
clang::QualType resolve(clang::QualType type);
|
||||||
|
|
||||||
@@ -30,7 +27,7 @@ public:
|
|||||||
|
|
||||||
void resolve(clang::UnresolvedLookupExpr* expr);
|
void resolve(clang::UnresolvedLookupExpr* expr);
|
||||||
|
|
||||||
// TODO: Use a clearer approach for resolving UnresolvedLookupExpr.
|
// TODO: use a relative clear way to resolve `UnresolvedLookupExpr`.
|
||||||
|
|
||||||
void resolve(clang::UnresolvedUsingType* type);
|
void resolve(clang::UnresolvedUsingType* type);
|
||||||
|
|
||||||
@@ -53,7 +50,7 @@ public:
|
|||||||
if(identifier) {
|
if(identifier) {
|
||||||
return lookup(template_name.getQualifier(), identifier);
|
return lookup(template_name.getQualifier(), identifier);
|
||||||
} else {
|
} else {
|
||||||
/// TODO: Operators don't have an IdentifierInfo; need DeclarationName-based lookup.
|
/// FIXME: Operators does't have a name.
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,7 +60,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
lookup_result lookup(const clang::UnresolvedLookupExpr* expr) {
|
lookup_result lookup(const clang::UnresolvedLookupExpr* expr) {
|
||||||
/// TODO: Only returns the first TemplateDecl; should handle overloaded lookups.
|
/// FIXME:
|
||||||
for(auto decl: expr->decls()) {
|
for(auto decl: expr->decls()) {
|
||||||
if(auto TD = llvm::dyn_cast<clang::TemplateDecl>(decl)) {
|
if(auto TD = llvm::dyn_cast<clang::TemplateDecl>(decl)) {
|
||||||
return lookup_result(TD);
|
return lookup_result(TD);
|
||||||
@@ -77,8 +74,8 @@ public:
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TODO: Implement dependent member expression lookup (e.g. `x.template foo<T>()`).
|
/// TODO:
|
||||||
lookup_result lookup(const clang::CXXDependentScopeMemberExpr* expr) {
|
lookup_result lookup(clang::CXXDependentScopeMemberExpr* expr) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,19 +83,16 @@ public:
|
|||||||
return lookup(decl->getQualifier(), decl->getDeclName());
|
return lookup(decl->getQualifier(), decl->getDeclName());
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup_result lookup(const clang::UnresolvedUsingTypenameDecl* decl) {
|
lookup_result resolve(const clang::UnresolvedUsingTypenameDecl* decl) {
|
||||||
return lookup(decl->getQualifier(), decl->getDeclName());
|
return lookup(decl->getQualifier(), decl->getDeclName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
static inline bool debug = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
clang::Sema& sema;
|
clang::Sema& sema;
|
||||||
|
|
||||||
/// Cache of resolved dependent types, keyed by AST node pointer.
|
|
||||||
/// Shared across resolve() calls within the same TU for performance.
|
|
||||||
/// This is safe because a given AST node (DependentNameType*, etc.) has a
|
|
||||||
/// unique identity within the TU — the same pointer always refers to the same
|
|
||||||
/// syntactic occurrence. Different syntactic occurrences of the "same" type
|
|
||||||
/// have different AST node pointers.
|
|
||||||
llvm::DenseMap<const void*, clang::QualType> resolved;
|
llvm::DenseMap<const void*, clang::QualType> resolved;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
|
||||||
#include "syntax/token.h"
|
#include "SourceCode.h"
|
||||||
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
#include "llvm/ADT/SmallVector.h"
|
||||||
#include "clang/AST/ASTTypeTraits.h"
|
#include "clang/AST/ASTTypeTraits.h"
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "semantic/ast_utility.h"
|
#include "FilterASTVisitor.h"
|
||||||
#include "semantic/filtered_ast_visitor.h"
|
#include "RelationKind.h"
|
||||||
#include "semantic/relation_kind.h"
|
#include "Resolver.h"
|
||||||
#include "semantic/resolver.h"
|
#include "SymbolKind.h"
|
||||||
#include "semantic/symbol_kind.h"
|
#include "Utility.h"
|
||||||
|
|
||||||
namespace clice {
|
namespace clice {
|
||||||
|
|
||||||
@@ -131,6 +131,33 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if(auto module = unit.context().getCurrentNamedModule()) {
|
||||||
|
// auto keyword = module->DefinitionLoc;
|
||||||
|
// auto begin = TB.spelledTokenContaining(keyword);
|
||||||
|
// // assert(begin->kind() == clang::tok::identifier && begin->text(SM) == "module" &&
|
||||||
|
// // "Invalid module declaration");
|
||||||
|
//
|
||||||
|
// begin += 1;
|
||||||
|
// auto end = TB.spelledTokens(unit.file_id(keyword)).end();
|
||||||
|
//
|
||||||
|
// for(auto iter = begin; iter != end; ++iter) {
|
||||||
|
// if(iter->kind() == clang::tok::identifier) {
|
||||||
|
// if(auto next = iter + 1; next != end && (next->kind() == clang::tok::period ||
|
||||||
|
// next->kind() == clang::tok::colon)) {
|
||||||
|
// iter += 1;
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// end = iter + 1;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// std::unreachable();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// handleModuleOccurrence(keyword, llvm::ArrayRef<clang::syntax::Token>(begin, end));
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
193
include/AST/SourceCode.h
Normal file
193
include/AST/SourceCode.h
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "clang/Basic/SourceLocation.h"
|
||||||
|
#include "clang/Lex/Token.h"
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tuple_size<clang::SourceRange> : std::integral_constant<std::size_t, 2> {};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tuple_element<0, clang::SourceRange> {
|
||||||
|
using type = clang::SourceLocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct tuple_element<1, clang::SourceRange> {
|
||||||
|
using type = clang::SourceLocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
|
||||||
|
namespace clang {
|
||||||
|
|
||||||
|
/// Through ADL, make `clang::SourceRange` could be destructured.
|
||||||
|
template <std::size_t I>
|
||||||
|
clang::SourceLocation get(clang::SourceRange range) {
|
||||||
|
if constexpr(I == 0) {
|
||||||
|
return range.getBegin();
|
||||||
|
} else {
|
||||||
|
return range.getEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Lexer;
|
||||||
|
|
||||||
|
} // namespace clang
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
struct LocalSourceRange {
|
||||||
|
/// The begin position offset to the source file.
|
||||||
|
uint32_t begin = static_cast<uint32_t>(-1);
|
||||||
|
|
||||||
|
/// The end position offset to the source file.
|
||||||
|
uint32_t end = static_cast<uint32_t>(-1);
|
||||||
|
|
||||||
|
constexpr bool operator==(const LocalSourceRange& other) const = default;
|
||||||
|
|
||||||
|
constexpr auto length() {
|
||||||
|
return end - begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool contains(uint32_t offset) const {
|
||||||
|
return offset >= begin && offset <= end;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool intersects(const LocalSourceRange& other) const {
|
||||||
|
return begin <= other.end && end >= other.begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool valid() const {
|
||||||
|
return begin != -1 && end != -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using TokenKind = clang::tok::TokenKind;
|
||||||
|
|
||||||
|
struct Token {
|
||||||
|
/// Whether this token is at the start of line.
|
||||||
|
bool is_at_start_of_line = false;
|
||||||
|
|
||||||
|
/// Whether this token is a preprocessor directive.
|
||||||
|
bool is_pp_keyword = false;
|
||||||
|
|
||||||
|
/// The kind of this token.
|
||||||
|
TokenKind kind;
|
||||||
|
|
||||||
|
/// The source range of this token.
|
||||||
|
LocalSourceRange range;
|
||||||
|
|
||||||
|
bool valid() {
|
||||||
|
return range.valid();
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::StringRef name() const {
|
||||||
|
return clang::tok::getTokenName(kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
llvm::StringRef text(llvm::StringRef content) const {
|
||||||
|
assert(range.valid() && "Invalid source range");
|
||||||
|
return content.substr(range.begin, range.end - range.begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_eod() const {
|
||||||
|
return kind == clang::tok::eod;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_eof() const {
|
||||||
|
return kind == clang::tok::eof;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_identifier() const {
|
||||||
|
return kind == clang::tok::raw_identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_directive_hash() const {
|
||||||
|
return is_at_start_of_line && kind == clang::tok::hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The tokens after the include directive are regarded as
|
||||||
|
/// a whole token, whose kind is `header_name`. For example
|
||||||
|
/// `<iostream>` and `"test.h"` are both header name.
|
||||||
|
bool is_header_name() const {
|
||||||
|
return kind == clang::tok::header_name;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Lexer {
|
||||||
|
public:
|
||||||
|
Lexer(llvm::StringRef content,
|
||||||
|
bool ignore_comments = true,
|
||||||
|
const clang::LangOptions* lang_opts = nullptr,
|
||||||
|
bool ignore_end_of_directive = true);
|
||||||
|
|
||||||
|
Lexer(const Lexer&) = delete;
|
||||||
|
|
||||||
|
Lexer(Lexer&&) = delete;
|
||||||
|
|
||||||
|
Lexer& operator=(const Lexer&) = delete;
|
||||||
|
|
||||||
|
Lexer& operator=(Lexer&&) = delete;
|
||||||
|
|
||||||
|
~Lexer();
|
||||||
|
|
||||||
|
void lex(Token& token);
|
||||||
|
|
||||||
|
/// Get the token before this token without moving the lexer.
|
||||||
|
Token last();
|
||||||
|
|
||||||
|
/// Get the token after this token without moving the lexer.
|
||||||
|
Token next();
|
||||||
|
|
||||||
|
/// Advance the lexer and return the next token.
|
||||||
|
Token advance();
|
||||||
|
|
||||||
|
/// Advance the lexer if the next token kind is the param.
|
||||||
|
std::optional<Token> advance_if(llvm::function_ref<bool(const Token&)> callback);
|
||||||
|
|
||||||
|
std::optional<Token> advance_if(llvm::StringRef spelling) {
|
||||||
|
return advance_if([&](const Token& token) {
|
||||||
|
return token.is_identifier() && token.text(content) == spelling;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Token> advance_if(TokenKind kind) {
|
||||||
|
return advance_if([&](const Token& token) { return token.kind == kind; });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance the lexer until meet the specific kind token.
|
||||||
|
Token advance_until(TokenKind kind);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// If this is set to false, the lexer will emit tok::eod at the end
|
||||||
|
/// of directive.
|
||||||
|
bool ignore_end_of_directive = true;
|
||||||
|
|
||||||
|
/// Whether we are lexing the preprocessor directive.
|
||||||
|
bool parse_pp_keyword = false;
|
||||||
|
|
||||||
|
/// Whether we are lexing the header name.
|
||||||
|
bool parse_header_name = false;
|
||||||
|
|
||||||
|
bool module_declaration_context = true;
|
||||||
|
|
||||||
|
/// The cache of last token.
|
||||||
|
Token last_token;
|
||||||
|
|
||||||
|
/// The cache of current token.
|
||||||
|
Token current_token;
|
||||||
|
|
||||||
|
/// The cache of next token.
|
||||||
|
std::optional<Token> next_token;
|
||||||
|
|
||||||
|
/// The lexed content.
|
||||||
|
llvm::StringRef content;
|
||||||
|
|
||||||
|
std::unique_ptr<clang::Lexer> lexer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include "Support/Enum.h"
|
||||||
|
|
||||||
#include "clang/AST/Decl.h"
|
#include "clang/AST/Decl.h"
|
||||||
#include "clang/Basic/TokenKinds.h"
|
|
||||||
|
|
||||||
namespace clice {
|
namespace clice {
|
||||||
|
|
||||||
@@ -15,8 +14,8 @@ namespace clice {
|
|||||||
/// consistently across our responses to the client and in the index. Users who prefer to stick to
|
/// consistently across our responses to the client and in the index. Users who prefer to stick to
|
||||||
/// standard LSP kinds can map our `SymbolKind` to the corresponding LSP kinds through
|
/// standard LSP kinds can map our `SymbolKind` to the corresponding LSP kinds through
|
||||||
/// configuration.
|
/// configuration.
|
||||||
struct SymbolKind {
|
struct SymbolKind : refl::Enum<SymbolKind, false, uint8_t> {
|
||||||
enum Kind : std::uint8_t {
|
enum Kind : uint8_t {
|
||||||
Comment = 0, ///< C/C++ comments.
|
Comment = 0, ///< C/C++ comments.
|
||||||
Number, ///< C/C++ number literal.
|
Number, ///< C/C++ number literal.
|
||||||
Character, ///< C/C++ character literal.
|
Character, ///< C/C++ character literal.
|
||||||
@@ -51,121 +50,37 @@ struct SymbolKind {
|
|||||||
Invalid,
|
Invalid,
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr SymbolKind() = default;
|
using Enum::Enum;
|
||||||
|
|
||||||
constexpr SymbolKind(Kind kind) : kind_value(kind) {}
|
constexpr inline static auto InvalidEnum = Kind::Invalid;
|
||||||
|
|
||||||
constexpr explicit SymbolKind(std::uint8_t kind) : kind_value(static_cast<Kind>(kind)) {}
|
|
||||||
|
|
||||||
constexpr operator Kind() const {
|
|
||||||
return kind_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr std::uint8_t value_of() const {
|
|
||||||
return static_cast<std::uint8_t>(kind_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr std::uint8_t value() const {
|
|
||||||
return value_of();
|
|
||||||
}
|
|
||||||
|
|
||||||
static SymbolKind from(const clang::Decl* decl);
|
static SymbolKind from(const clang::Decl* decl);
|
||||||
|
|
||||||
static SymbolKind from(const clang::tok::TokenKind kind);
|
static SymbolKind from(const clang::tok::TokenKind kind);
|
||||||
|
|
||||||
private:
|
|
||||||
Kind kind_value = Invalid;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SymbolModifiers {
|
struct SymbolModifiers : refl::Enum<SymbolModifiers, true, uint32_t> {
|
||||||
enum Kind : std::uint32_t {
|
enum Kind {
|
||||||
/// Represents that the symbol is a declaration(e.g. function declaration).
|
/// Represents that the symbol is a declaration(e.g. function declaration).
|
||||||
Declaration = 0,
|
Declaration = 0,
|
||||||
|
|
||||||
/// Represents that the symbol is a definition(e.g. function definition).
|
/// Represents that the symbol is a definition(e.g. function definition).
|
||||||
Definition = 1,
|
Definition,
|
||||||
|
|
||||||
/// Represents that the symbol is const modified(e.g. `const` variable).
|
/// Represents that the symbol is const modified(e.g. `const` variable).
|
||||||
Const = 2,
|
Const,
|
||||||
|
|
||||||
/// Represents that the symbol is overloaded(e.g. overloaded functions and operators).
|
/// Represents that the symbol is overloaded(e.g. overloaded functions and operators).
|
||||||
Overloaded = 3,
|
Overloaded,
|
||||||
|
|
||||||
/// Represents that the symbol is a part of type(e.g. `*` in `int*`).
|
/// Represents that the symbol is a part of type(e.g. `*` in `int*`).
|
||||||
Typed = 4,
|
Typed,
|
||||||
|
|
||||||
/// Represents that the symbol is a template(e.g. class template or function template).
|
/// Represents that the symbol is a template(e.g. class template or function template).
|
||||||
Templated = 5,
|
Templated,
|
||||||
|
|
||||||
/// Represents that the symbol is deprecated.
|
|
||||||
Deprecated = 6,
|
|
||||||
|
|
||||||
/// Represents that the symbol is deduced.
|
|
||||||
Deduced = 7,
|
|
||||||
|
|
||||||
/// Represents that the symbol is readonly.
|
|
||||||
Readonly = 8,
|
|
||||||
|
|
||||||
/// Represents that the symbol is static.
|
|
||||||
Static = 9,
|
|
||||||
|
|
||||||
/// Represents that the symbol is abstract.
|
|
||||||
Abstract = 10,
|
|
||||||
|
|
||||||
/// Represents that the symbol is virtual.
|
|
||||||
Virtual = 11,
|
|
||||||
|
|
||||||
/// Represents that the symbol is a dependent name.
|
|
||||||
DependentName = 12,
|
|
||||||
|
|
||||||
/// Represents that the symbol comes from the default library.
|
|
||||||
DefaultLibrary = 13,
|
|
||||||
|
|
||||||
/// Represents that the symbol is used through a mutable reference.
|
|
||||||
UsedAsMutableReference = 14,
|
|
||||||
|
|
||||||
/// Represents that the symbol is used through a mutable pointer.
|
|
||||||
UsedAsMutablePointer = 15,
|
|
||||||
|
|
||||||
/// Represents that the symbol is a constructor or destructor.
|
|
||||||
ConstructorOrDestructor = 16,
|
|
||||||
|
|
||||||
/// Represents that the symbol is user-defined.
|
|
||||||
UserDefined = 17,
|
|
||||||
|
|
||||||
/// Represents that the symbol is function-scoped.
|
|
||||||
FunctionScope = 18,
|
|
||||||
|
|
||||||
/// Represents that the symbol is class-scoped.
|
|
||||||
ClassScope = 19,
|
|
||||||
|
|
||||||
/// Represents that the symbol is file-scoped.
|
|
||||||
FileScope = 20,
|
|
||||||
|
|
||||||
/// Represents that the symbol is global-scoped.
|
|
||||||
GlobalScope = 21,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr static std::uint32_t to_mask(Kind kind) {
|
using Enum::Enum;
|
||||||
return std::uint32_t(1) << static_cast<std::uint32_t>(kind);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr SymbolModifiers() = default;
|
|
||||||
|
|
||||||
constexpr SymbolModifiers(Kind kind) : value(to_mask(kind)) {}
|
|
||||||
|
|
||||||
constexpr explicit SymbolModifiers(std::uint32_t bits) : value(bits) {}
|
|
||||||
|
|
||||||
constexpr operator std::uint32_t() const {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool contains(Kind kind) const {
|
|
||||||
return (value & to_mask(kind)) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::uint32_t value = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clice
|
} // namespace clice
|
||||||
10
include/Async/Async.h
Normal file
10
include/Async/Async.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
#include "FileSystem.h"
|
||||||
|
#include "Gather.h"
|
||||||
|
#include "Lock.h"
|
||||||
|
#include "Network.h"
|
||||||
|
#include "Sleep.h"
|
||||||
|
#include "ThreadPool.h"
|
||||||
|
#include "libuv.h"
|
||||||
96
include/Async/Awaiter.h
Normal file
96
include/Async/Awaiter.h
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
|
#include "Task.h"
|
||||||
|
#include "libuv.h"
|
||||||
|
|
||||||
|
namespace clice::async::awaiter {
|
||||||
|
|
||||||
|
template <typename Request>
|
||||||
|
struct uv_base;
|
||||||
|
|
||||||
|
template <typename Request>
|
||||||
|
requires (is_uv_handle_v<Request>)
|
||||||
|
struct uv_base<Request> {
|
||||||
|
/// For libuv handles, `uv_close` must be called to release resources.
|
||||||
|
/// However, `uv_close` can only be async operation. When close is actually called,
|
||||||
|
/// the promise object may have already been destroyed, leading to undefined behavior
|
||||||
|
/// such as use-after-free. To avoid this situation, we allocate memory separately
|
||||||
|
/// for the handle and destroy it in the callback function.
|
||||||
|
Request& request;
|
||||||
|
|
||||||
|
uv_base() : request(*static_cast<Request*>(std::malloc(sizeof(Request)))) {}
|
||||||
|
|
||||||
|
~uv_base() {
|
||||||
|
uv_close(reinterpret_cast<uv_handle_t*>(&request),
|
||||||
|
[](uv_handle_t* handle) { std::free(handle); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Request>
|
||||||
|
requires (is_uv_req_v<Request>)
|
||||||
|
struct uv_base<Request> {
|
||||||
|
/// For libuv requests, they don't need to be closed. We can lay them on the promise
|
||||||
|
/// object directly.
|
||||||
|
Request request;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The CRTP base class for the awaiter of libuv async operations. The Derived should
|
||||||
|
/// implement the `start` and `cleanup` functions.
|
||||||
|
template <typename Derived, typename Request, typename Ret, typename... Extras>
|
||||||
|
struct uv : uv_base<Request> {
|
||||||
|
int error = 0;
|
||||||
|
promise_base* continuation;
|
||||||
|
|
||||||
|
bool await_ready() const noexcept {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The callback function to handle the async operation. This should always called
|
||||||
|
/// in the main thread.
|
||||||
|
static void callback(Request* request, Extras... extras) {
|
||||||
|
auto& self = *static_cast<Derived*>(request->data);
|
||||||
|
|
||||||
|
/// The derived should implement the cleanup function to release resources or set
|
||||||
|
/// the error code if the async operation fails.
|
||||||
|
self.cleanup(extras...);
|
||||||
|
|
||||||
|
/// Then we resume the coroutine. It may destroy the current task,
|
||||||
|
/// If the task is cancelled and disposable.
|
||||||
|
self.continuation->resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Promise>
|
||||||
|
std::coroutine_handle<> await_suspend(std::coroutine_handle<Promise> waiting) noexcept {
|
||||||
|
continuation = &waiting.promise();
|
||||||
|
this->request.data = static_cast<Derived*>(this);
|
||||||
|
|
||||||
|
auto& self = *static_cast<Derived*>(this);
|
||||||
|
|
||||||
|
/// Start the async operation.
|
||||||
|
error = self.start(callback);
|
||||||
|
|
||||||
|
/// If the async operation fails, resume the coroutine immediately.
|
||||||
|
if(error < 0) {
|
||||||
|
return continuation->resume_handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Otherwise, return the coroutine handle to resume later.
|
||||||
|
return std::noop_coroutine();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::expected<Ret, std::error_code> await_resume() noexcept {
|
||||||
|
if(error < 0) {
|
||||||
|
return std::unexpected(std::error_code(error, category()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr(!std::is_void_v<Ret>) {
|
||||||
|
return static_cast<Derived*>(this)->result();
|
||||||
|
} else {
|
||||||
|
return std::expected<void, std::error_code>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice::async::awaiter
|
||||||
58
include/Async/Event.h
Normal file
58
include/Async/Event.h
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Task.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
|
||||||
|
namespace clice::async {
|
||||||
|
|
||||||
|
namespace awaiter {
|
||||||
|
|
||||||
|
struct event {
|
||||||
|
bool ready;
|
||||||
|
llvm::SmallVectorImpl<promise_base*>& awaiters;
|
||||||
|
|
||||||
|
bool await_ready() const noexcept {
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Promise>
|
||||||
|
void await_suspend(std::coroutine_handle<Promise> handle) const noexcept {
|
||||||
|
awaiters.emplace_back(&handle.promise());
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace awaiter
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
public:
|
||||||
|
Event() = default;
|
||||||
|
|
||||||
|
void set() {
|
||||||
|
ready = true;
|
||||||
|
for(auto* awaiter: awaiters) {
|
||||||
|
awaiter->schedule();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void unset() {
|
||||||
|
ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
ready = false;
|
||||||
|
awaiters.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() {
|
||||||
|
return awaiter::event{ready, awaiters};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ready = false;
|
||||||
|
llvm::SmallVector<promise_base*, 4> awaiters;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice::async
|
||||||
95
include/Async/FileSystem.h
Normal file
95
include/Async/FileSystem.h
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include "Awaiter.h"
|
||||||
|
#include "Task.h"
|
||||||
|
#include "libuv.h"
|
||||||
|
#include "Support/Enum.h"
|
||||||
|
#include "Support/JSON.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/FunctionExtras.h"
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
namespace clice::async {
|
||||||
|
|
||||||
|
namespace fs {
|
||||||
|
|
||||||
|
struct Mode : refl::Enum<Mode, true> {
|
||||||
|
enum Kind {
|
||||||
|
/// Open the file for reading.
|
||||||
|
Read = 0,
|
||||||
|
|
||||||
|
/// Open the file for writing.
|
||||||
|
Write,
|
||||||
|
|
||||||
|
/// Open the file for reading and writing.
|
||||||
|
ReadWrite,
|
||||||
|
|
||||||
|
/// If the file does not exist, create it.
|
||||||
|
Create,
|
||||||
|
|
||||||
|
/// If the file exists, append the data to the end of the file.
|
||||||
|
Append,
|
||||||
|
|
||||||
|
/// If the file exists, truncate the file to zero length.
|
||||||
|
Truncate,
|
||||||
|
|
||||||
|
/// If the file exists, fail the open.
|
||||||
|
Exclusive,
|
||||||
|
};
|
||||||
|
|
||||||
|
using Enum::Enum;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct handle {
|
||||||
|
public:
|
||||||
|
handle(uv_file file) : file(file) {}
|
||||||
|
|
||||||
|
handle(const handle&) = delete;
|
||||||
|
|
||||||
|
handle(handle&& other) noexcept : file(other.file) {
|
||||||
|
other.file = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
~handle();
|
||||||
|
|
||||||
|
handle& operator=(const handle&) = delete;
|
||||||
|
|
||||||
|
handle& operator=(handle&& other) noexcept = delete;
|
||||||
|
|
||||||
|
int value() const {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uv_file file;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Open the file asynchronously.
|
||||||
|
Result<handle> open(std::string path, Mode mode);
|
||||||
|
|
||||||
|
/// Read the file asynchronously, make sure the buffer is valid until the task is done.
|
||||||
|
Result<ssize_t> read(const handle& handle, char* buffer, std::size_t size);
|
||||||
|
|
||||||
|
Result<std::string> read(std::string path, Mode mode = Mode::Read);
|
||||||
|
|
||||||
|
/// Write the file asynchronously, make sure the buffer is valid until the task is done.
|
||||||
|
Result<void> write(const handle& handle, char* buffer, std::size_t size);
|
||||||
|
|
||||||
|
Result<void> write(std::string path,
|
||||||
|
char* buffer,
|
||||||
|
std::size_t size,
|
||||||
|
Mode mode = Mode(Mode::Write, Mode::Create, Mode::Truncate));
|
||||||
|
|
||||||
|
struct Stats {
|
||||||
|
std::chrono::milliseconds mtime;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
Result<Stats> stat(std::string path);
|
||||||
|
|
||||||
|
} // namespace fs
|
||||||
|
|
||||||
|
} // namespace clice::async
|
||||||
143
include/Async/Gather.h
Normal file
143
include/Async/Gather.h
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
#include "Task.h"
|
||||||
|
|
||||||
|
namespace clice::async {
|
||||||
|
|
||||||
|
struct none {};
|
||||||
|
|
||||||
|
template <typename Task, typename V = typename std::remove_cvref_t<Task>::value_type>
|
||||||
|
using task_value_t = std::conditional_t<std::is_void_v<V>, none, V>;
|
||||||
|
|
||||||
|
template <typename... Tasks>
|
||||||
|
auto gather(Tasks&&... tasks) -> Task<std::tuple<task_value_t<Tasks>...>> {
|
||||||
|
constexpr static std::size_t count = sizeof...(Tasks);
|
||||||
|
|
||||||
|
Event event;
|
||||||
|
std::size_t finished = 0;
|
||||||
|
|
||||||
|
auto run_task = [&](auto& task) -> Task<task_value_t<decltype(task)>> {
|
||||||
|
using V = typename std::remove_cvref_t<decltype(task)>::value_type;
|
||||||
|
if constexpr(std::is_void_v<V>) {
|
||||||
|
co_await task;
|
||||||
|
/// Check if all tasks are finished. If so, set the event to
|
||||||
|
/// resume the gather handle.
|
||||||
|
finished += 1;
|
||||||
|
if(finished == count) {
|
||||||
|
event.set();
|
||||||
|
}
|
||||||
|
co_return none{};
|
||||||
|
} else {
|
||||||
|
auto result = co_await task;
|
||||||
|
finished += 1;
|
||||||
|
if(finished == count) {
|
||||||
|
event.set();
|
||||||
|
}
|
||||||
|
co_return std::move(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto schedule_task = [&](auto& task) {
|
||||||
|
auto core = run_task(task);
|
||||||
|
core.schedule();
|
||||||
|
return core;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::tuple all = {schedule_task(tasks)...};
|
||||||
|
|
||||||
|
/// Wait for all tasks to finish.
|
||||||
|
co_await event;
|
||||||
|
|
||||||
|
/// Return the results of all tasks.
|
||||||
|
co_return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
|
||||||
|
return std::make_tuple(std::get<Is>(all).result()...);
|
||||||
|
}(std::make_index_sequence<count>{});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the tasks in parallel and return the results.
|
||||||
|
template <typename... Tasks>
|
||||||
|
auto run(Tasks&&... tasks) {
|
||||||
|
auto core = gather(std::forward<Tasks>(tasks)...);
|
||||||
|
core.schedule();
|
||||||
|
async::run();
|
||||||
|
assert(core.done() && "run: not done");
|
||||||
|
return core.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <ranges::input_range Range, typename Coroutine>
|
||||||
|
requires requires(Coroutine coroutine, ranges::range_value_t<Range> value) {
|
||||||
|
{ coroutine(value) } -> std::same_as<Task<bool>>;
|
||||||
|
}
|
||||||
|
Task<bool> gather(Range&& range,
|
||||||
|
Coroutine&& coroutine,
|
||||||
|
std::size_t concurrency = std::thread::hardware_concurrency()) {
|
||||||
|
std::vector<Task<>> tasks;
|
||||||
|
tasks.reserve(concurrency);
|
||||||
|
|
||||||
|
auto iter = ranges::begin(range);
|
||||||
|
auto end = ranges::end(range);
|
||||||
|
|
||||||
|
Event event;
|
||||||
|
std::size_t finished = 0;
|
||||||
|
bool cancelled = false;
|
||||||
|
|
||||||
|
auto run_task = [&](auto& value) -> async::Task<> {
|
||||||
|
/// Execute the first task.
|
||||||
|
auto task = coroutine(value);
|
||||||
|
|
||||||
|
/// If any task fails, cancel all tasks and return false.
|
||||||
|
if(auto result = co_await task; !result) {
|
||||||
|
for(auto& task: tasks) {
|
||||||
|
task.cancel();
|
||||||
|
task.dispose();
|
||||||
|
}
|
||||||
|
cancelled = true;
|
||||||
|
event.set();
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
finished += 1;
|
||||||
|
|
||||||
|
/// Check if still have tasks to run. If so, run the next task.
|
||||||
|
while(iter != end) {
|
||||||
|
auto task = coroutine(*iter);
|
||||||
|
iter++;
|
||||||
|
finished -= 1;
|
||||||
|
|
||||||
|
if(auto result = co_await task; !result) {
|
||||||
|
for(auto& task: tasks) {
|
||||||
|
task.cancel();
|
||||||
|
task.dispose();
|
||||||
|
}
|
||||||
|
cancelled = true;
|
||||||
|
event.set();
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
finished += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if all tasks are finished. If so, set the event to
|
||||||
|
/// resume the gather handle.
|
||||||
|
if(finished == tasks.size()) {
|
||||||
|
event.set();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Fill tasks.
|
||||||
|
while(iter != end && tasks.size() < concurrency) {
|
||||||
|
tasks.emplace_back(run_task(*iter));
|
||||||
|
tasks.back().schedule();
|
||||||
|
iter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
co_await event;
|
||||||
|
|
||||||
|
co_return !cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace clice::async
|
||||||
79
include/Async/Lock.h
Normal file
79
include/Async/Lock.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
namespace clice::async {
|
||||||
|
|
||||||
|
namespace awaiter {
|
||||||
|
|
||||||
|
struct lock {
|
||||||
|
llvm::SmallVectorImpl<promise_base*>& awaiters;
|
||||||
|
|
||||||
|
bool await_ready() const noexcept {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Promise>
|
||||||
|
void await_suspend(std::coroutine_handle<Promise> handle) const noexcept {
|
||||||
|
awaiters.emplace_back(&handle.promise());
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() const noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace awaiter
|
||||||
|
|
||||||
|
class Lock {
|
||||||
|
friend class guard;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Lock() = default;
|
||||||
|
|
||||||
|
class Guard {
|
||||||
|
public:
|
||||||
|
Guard(Lock* lock) : lock(lock) {}
|
||||||
|
|
||||||
|
Guard(Guard&& other) : lock(other.lock) {
|
||||||
|
other.lock = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Guard() {
|
||||||
|
if(!lock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock->locked = false;
|
||||||
|
if(!lock->awaiters.empty()) {
|
||||||
|
lock->awaiters.front()->schedule();
|
||||||
|
lock->awaiters.erase(lock->awaiters.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Lock* lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Try to get the lock. If the lock is locked, the current coroutine will be
|
||||||
|
/// suspended and wait for the lock to be released.
|
||||||
|
Task<Guard> try_lock() {
|
||||||
|
/// Note that this task also may be canceled, we make sure
|
||||||
|
/// even cancel, it can resume one task(through destructor).
|
||||||
|
Guard guard(this);
|
||||||
|
|
||||||
|
if(locked) {
|
||||||
|
co_await awaiter::lock{awaiters};
|
||||||
|
}
|
||||||
|
|
||||||
|
locked = true;
|
||||||
|
|
||||||
|
/// Use `std::move` to make sure it will not resume
|
||||||
|
/// the awaiter here.
|
||||||
|
co_return std::move(guard);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool locked = false;
|
||||||
|
llvm::SmallVector<promise_base*, 4> awaiters;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice::async
|
||||||
23
include/Async/Network.h
Normal file
23
include/Async/Network.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Task.h"
|
||||||
|
#include "libuv.h"
|
||||||
|
#include "Support/JSON.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/FunctionExtras.h"
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
namespace clice::async::net {
|
||||||
|
|
||||||
|
using Callback = llvm::unique_function<Task<void>(json::Value)>;
|
||||||
|
|
||||||
|
/// Listen on stdin/stdout, callback is called when there is a LSP message available.
|
||||||
|
void listen(Callback callback);
|
||||||
|
|
||||||
|
/// Listen on the given host and port, callback is called when there is a LSP message available.
|
||||||
|
void listen(const char* host, unsigned int port, Callback callback);
|
||||||
|
|
||||||
|
/// Write a JSON value to the client.
|
||||||
|
Task<> write(json::Value value);
|
||||||
|
|
||||||
|
} // namespace clice::async::net
|
||||||
37
include/Async/Sleep.h
Normal file
37
include/Async/Sleep.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include "Awaiter.h"
|
||||||
|
|
||||||
|
namespace clice::async {
|
||||||
|
|
||||||
|
namespace awaiter {
|
||||||
|
|
||||||
|
struct sleep : uv<sleep, uv_timer_t, void> {
|
||||||
|
std::chrono::milliseconds duration;
|
||||||
|
|
||||||
|
int start(auto callback) {
|
||||||
|
int err = uv_timer_init(async::loop, &request);
|
||||||
|
if(err < 0) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return uv_timer_start(&request, callback, duration.count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup() {
|
||||||
|
error = uv_timer_stop(&request);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace awaiter
|
||||||
|
|
||||||
|
inline auto sleep(std::chrono::milliseconds duration) {
|
||||||
|
return awaiter::sleep{{}, duration};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline auto sleep(std::size_t milliseconds) {
|
||||||
|
return sleep(std::chrono::milliseconds(milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
}; // namespace clice::async
|
||||||
334
include/Async/Task.h
Normal file
334
include/Async/Task.h
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <coroutine>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <optional>
|
||||||
|
#include <source_location>
|
||||||
|
|
||||||
|
#include "Support/Format.h"
|
||||||
|
|
||||||
|
namespace clice::async {
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Task;
|
||||||
|
|
||||||
|
struct promise_base {
|
||||||
|
enum Flags : uint8_t {
|
||||||
|
Empty = 0,
|
||||||
|
|
||||||
|
/// The task is cancelled.
|
||||||
|
Cancelled = 1,
|
||||||
|
|
||||||
|
/// The coroutine handle will be destroyed when the task is done or cancelled.
|
||||||
|
Disposable = 1 << 1,
|
||||||
|
|
||||||
|
/// The coroutine is done or is cancelled and resumed, means it will never
|
||||||
|
/// scheduled again.
|
||||||
|
Finished = 1 << 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t flags;
|
||||||
|
|
||||||
|
void* data;
|
||||||
|
|
||||||
|
/// The coroutine handle that is waiting for the task to complete.
|
||||||
|
/// If this is a top-level coroutine, it is empty.
|
||||||
|
promise_base* continuation = nullptr;
|
||||||
|
|
||||||
|
promise_base* next = nullptr;
|
||||||
|
|
||||||
|
std::source_location location;
|
||||||
|
|
||||||
|
template <typename Promise>
|
||||||
|
void set(std::coroutine_handle<Promise> handle) {
|
||||||
|
flags = Empty;
|
||||||
|
data = handle.address();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto handle() const noexcept {
|
||||||
|
return std::coroutine_handle<>::from_address(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void schedule();
|
||||||
|
|
||||||
|
bool done() const noexcept {
|
||||||
|
return handle().done();
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy() {
|
||||||
|
handle().destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel() {
|
||||||
|
auto p = this;
|
||||||
|
while(p) {
|
||||||
|
p->flags |= Flags::Cancelled;
|
||||||
|
p = p->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cancelled() const noexcept {
|
||||||
|
return flags & Flags::Cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
flags |= Flags::Disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool disposable() const noexcept {
|
||||||
|
return flags & Flags::Disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finish() {
|
||||||
|
flags |= Flags::Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool finished() {
|
||||||
|
return flags & Flags::Finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::coroutine_handle<> resume_handle() {
|
||||||
|
if(cancelled()) {
|
||||||
|
/// If the task is cancelled and disposable, destroy the coroutine handle.
|
||||||
|
auto p = this;
|
||||||
|
while(p && p->cancelled()) {
|
||||||
|
auto con = p->continuation;
|
||||||
|
|
||||||
|
if(p->disposable()) {
|
||||||
|
p->destroy();
|
||||||
|
} else {
|
||||||
|
p->finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
p = con;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::noop_coroutine();
|
||||||
|
} else {
|
||||||
|
/// Otherwise, resume the coroutine handle.
|
||||||
|
return handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void resume() {
|
||||||
|
resume_handle().resume();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace awaiter {
|
||||||
|
|
||||||
|
/// The awaiter for the final suspend point of `Task`.
|
||||||
|
struct final {
|
||||||
|
promise_base* continuation;
|
||||||
|
|
||||||
|
bool await_ready() noexcept {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Promise>
|
||||||
|
std::coroutine_handle<> await_suspend(std::coroutine_handle<Promise> current) noexcept {
|
||||||
|
std::coroutine_handle<> handle = std::noop_coroutine();
|
||||||
|
|
||||||
|
/// In the final suspend point, this coroutine is already done.
|
||||||
|
/// So try to resume the waiting coroutine if it exists.
|
||||||
|
if(continuation) {
|
||||||
|
continuation->next = nullptr;
|
||||||
|
handle = continuation->resume_handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark current coroutine as finished.
|
||||||
|
current.promise().finish();
|
||||||
|
|
||||||
|
if(current.promise().disposable()) {
|
||||||
|
/// If this task is disposable, destroy the coroutine handle.
|
||||||
|
current.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void await_resume() noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The awaiter for the `Task` type.
|
||||||
|
template <typename T, typename P>
|
||||||
|
struct task {
|
||||||
|
std::coroutine_handle<P> handle;
|
||||||
|
|
||||||
|
bool await_ready() noexcept {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Promise>
|
||||||
|
auto await_suspend(std::coroutine_handle<Promise> waiting) noexcept {
|
||||||
|
/// Store the waiting coroutine in the promise for later scheduling.
|
||||||
|
/// It will be scheduled in the final suspend point.
|
||||||
|
assert(!handle.promise().continuation && "await_suspend: already waiting");
|
||||||
|
handle.promise().continuation = &waiting.promise();
|
||||||
|
waiting.promise().next = &handle.promise();
|
||||||
|
|
||||||
|
/// If this `Task` is awaited from another coroutine, we should schedule
|
||||||
|
/// the this task first.
|
||||||
|
return handle.promise().resume_handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
T await_resume() noexcept {
|
||||||
|
if constexpr(!std::is_void_v<T>) {
|
||||||
|
assert(handle.promise().value.has_value() && "await_resume: value not set");
|
||||||
|
return std::move(*handle.promise().value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace awaiter
|
||||||
|
|
||||||
|
template <typename T = void>
|
||||||
|
class Task {
|
||||||
|
public:
|
||||||
|
template <typename V>
|
||||||
|
struct promise_result {
|
||||||
|
std::optional<V> value;
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
void return_value(U&& val) noexcept {
|
||||||
|
assert(!value.has_value() && "return_value: value already set");
|
||||||
|
value.emplace(std::forward<U>(val));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// WORKAROUND: GCC bug - full specialization in non-namespace scope not supported
|
||||||
|
// see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85282
|
||||||
|
template <std::same_as<void> V>
|
||||||
|
struct promise_result<V> {
|
||||||
|
void return_void() noexcept {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct promise_type : promise_base, promise_result<T> {
|
||||||
|
promise_type(std::source_location location = std::source_location::current()) {
|
||||||
|
set(handle());
|
||||||
|
this->location = location;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto get_return_object() {
|
||||||
|
return Task<T>(handle());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto initial_suspend() {
|
||||||
|
return std::suspend_always();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto final_suspend() noexcept {
|
||||||
|
return awaiter::final{continuation};
|
||||||
|
}
|
||||||
|
|
||||||
|
void unhandled_exception() {
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto handle() {
|
||||||
|
return std::coroutine_handle<promise_type>::from_promise(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using coroutine_handle = std::coroutine_handle<promise_type>;
|
||||||
|
|
||||||
|
using value_type = T;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Task() = default;
|
||||||
|
|
||||||
|
Task(coroutine_handle handle) : core(handle) {}
|
||||||
|
|
||||||
|
Task(const Task&) = delete;
|
||||||
|
|
||||||
|
Task(Task&& other) noexcept : core(other.core) {
|
||||||
|
other.core = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Task& operator=(const Task&) = delete;
|
||||||
|
|
||||||
|
Task& operator=(Task&& other) noexcept {
|
||||||
|
if(core) {
|
||||||
|
core.destroy();
|
||||||
|
}
|
||||||
|
core = other.core;
|
||||||
|
other.core = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Task() {
|
||||||
|
if(core) {
|
||||||
|
core.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
coroutine_handle handle() const noexcept {
|
||||||
|
return core;
|
||||||
|
}
|
||||||
|
|
||||||
|
coroutine_handle release() noexcept {
|
||||||
|
auto handle = core;
|
||||||
|
core = nullptr;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const noexcept {
|
||||||
|
return !core;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool done() const noexcept {
|
||||||
|
return core.done();
|
||||||
|
}
|
||||||
|
|
||||||
|
void schedule() {
|
||||||
|
core.promise().schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel the task, the suspend point after the current one will be skipped.
|
||||||
|
void cancel() {
|
||||||
|
core.promise().cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool cancelled() {
|
||||||
|
return core.promise().cancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dispose the task, it will be destroyed when finished or cancelled.
|
||||||
|
void dispose() {
|
||||||
|
core.promise().dispose();
|
||||||
|
core = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool finished() {
|
||||||
|
return core.promise().finished();
|
||||||
|
}
|
||||||
|
|
||||||
|
T result() {
|
||||||
|
if constexpr(!std::is_void_v<T>) {
|
||||||
|
return std::move(core.promise().value.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator co_await() const noexcept {
|
||||||
|
return awaiter::task<T, promise_type>{core};
|
||||||
|
}
|
||||||
|
|
||||||
|
void stacktrace() {
|
||||||
|
promise_base* handle = core;
|
||||||
|
while(handle) {
|
||||||
|
std::println("{}:{}:{}",
|
||||||
|
handle->location.file_name(),
|
||||||
|
handle->location.line(),
|
||||||
|
handle->location.function_name());
|
||||||
|
handle = handle->continuation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
coroutine_handle core;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice::async
|
||||||
60
include/Async/ThreadPool.h
Normal file
60
include/Async/ThreadPool.h
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Awaiter.h"
|
||||||
|
|
||||||
|
namespace clice::async {
|
||||||
|
|
||||||
|
namespace awaiter {
|
||||||
|
|
||||||
|
template <typename Ret>
|
||||||
|
struct value {
|
||||||
|
std::optional<Ret> value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct value<void> {};
|
||||||
|
|
||||||
|
template <typename Work, typename Ret>
|
||||||
|
struct thread_pool : value<Ret>, uv<thread_pool<Work, Ret>, uv_work_t, Ret, int> {
|
||||||
|
Work work;
|
||||||
|
|
||||||
|
/// `uv_work_t` has two callback functions, `work_cb` is executed in the thread pool,
|
||||||
|
/// and `after_work_cb` is executed in the main thread.
|
||||||
|
static void work_cb(uv_work_t* work) {
|
||||||
|
auto& awaiter = uv_cast<thread_pool>(work);
|
||||||
|
if constexpr(!std::is_void_v<Ret>) {
|
||||||
|
awaiter.value.emplace(awaiter.work());
|
||||||
|
} else {
|
||||||
|
awaiter.work();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int start(auto callback) {
|
||||||
|
return uv_queue_work(async::loop, &this->request, work_cb, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup(int status) {
|
||||||
|
this->error = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ret result() {
|
||||||
|
if constexpr(!std::is_void_v<Ret>) {
|
||||||
|
return std::move(*this->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace awaiter
|
||||||
|
|
||||||
|
template <typename Work, typename Ret = std::invoke_result_t<Work>>
|
||||||
|
async::Task<Ret> submit(Work&& work) {
|
||||||
|
using W = std::remove_cvref_t<Work>;
|
||||||
|
auto result = co_await awaiter::thread_pool<W, Ret>{{}, {}, std::forward<Work>(work)};
|
||||||
|
if(!result) {
|
||||||
|
/// Thread pool task should never fails.
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
co_return std::move(*result);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace clice::async
|
||||||
79
include/Async/libuv.h
Normal file
79
include/Async/libuv.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "uv.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#undef THIS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <expected>
|
||||||
|
#include <system_error>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "Support/Logging.h"
|
||||||
|
#include "Support/TypeTraits.h"
|
||||||
|
|
||||||
|
namespace clice::async {
|
||||||
|
|
||||||
|
/// The default event loop.
|
||||||
|
extern uv_loop_t* loop;
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
T& uv_cast(U* u) {
|
||||||
|
assert(u && u->data && "uv_cast: invalid uv handle");
|
||||||
|
return *static_cast<std::remove_cvref_t<T>*>(u->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define UV_TYPE_ITER(_, name) || std::is_same_v<T, uv_##name##_t>
|
||||||
|
|
||||||
|
/// Check if the type `T` is a libuv handle.
|
||||||
|
template <typename T>
|
||||||
|
constexpr bool is_uv_handle_v = false UV_HANDLE_TYPE_MAP(UV_TYPE_ITER);
|
||||||
|
|
||||||
|
/// Check if the type `T` is a libuv request.
|
||||||
|
template <typename T>
|
||||||
|
constexpr bool is_uv_req_v = false UV_REQ_TYPE_MAP(UV_TYPE_ITER);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
constexpr bool is_uv_stream_v = std::is_same_v<T, uv_stream_t> || std::is_same_v<T, uv_tcp_t> ||
|
||||||
|
std::is_same_v<T, uv_pipe_t> || std::is_same_v<T, uv_tty_t>;
|
||||||
|
|
||||||
|
#undef UV_TYPE_ITER
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
T* uv_cast(U& u) {
|
||||||
|
if constexpr(std::is_same_v<T, uv_handle_t>) {
|
||||||
|
static_assert(is_uv_handle_v<std::remove_cvref_t<U>>, "uv_cast: invalid uv handle");
|
||||||
|
} else if constexpr(std::is_same_v<T, uv_req_t>) {
|
||||||
|
static_assert(is_uv_req_v<std::remove_cvref_t<U>>, "uv_cast: invalid uv request");
|
||||||
|
} else if constexpr(std::is_same_v<T, uv_stream_t>) {
|
||||||
|
static_assert(is_uv_stream_v<std::remove_cvref_t<U>>, "uv_cast: invalid uv stream");
|
||||||
|
} else {
|
||||||
|
static_assert(dependent_false<U>, "uv_cast: invalid type");
|
||||||
|
}
|
||||||
|
return reinterpret_cast<T*>(&u);
|
||||||
|
}
|
||||||
|
|
||||||
|
void uv_check_result(const int result,
|
||||||
|
const std::source_location location = std::source_location::current());
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class Task;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using Result = Task<std::expected<T, std::error_code>>;
|
||||||
|
|
||||||
|
const std::error_category& category();
|
||||||
|
|
||||||
|
void init();
|
||||||
|
|
||||||
|
void run();
|
||||||
|
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
} // namespace clice::async
|
||||||
125
include/Compiler/Command.h
Normal file
125
include/Compiler/Command.h
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
|
#include "Toolchain.h"
|
||||||
|
#include "Support/Enum.h"
|
||||||
|
#include "Support/Format.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
|
#include "llvm/ADT/DenseSet.h"
|
||||||
|
#include "llvm/ADT/StringMap.h"
|
||||||
|
#include "llvm/Support/Allocator.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
struct CommandOptions {
|
||||||
|
/// Ignore unknown commands arguments.
|
||||||
|
bool ignore_unknown = true;
|
||||||
|
|
||||||
|
/// Inject resource directory to the command.
|
||||||
|
bool resource_dir = false;
|
||||||
|
|
||||||
|
/// Query the compiler driver for additional information, such as system includes and target.
|
||||||
|
bool query_toolchain = false;
|
||||||
|
|
||||||
|
/// Suppress the warning log if failed to query driver info.
|
||||||
|
/// Set true in unittests to avoid cluttering test output.
|
||||||
|
bool suppress_logging = false;
|
||||||
|
|
||||||
|
/// The commands that you want to remove from original commands list.
|
||||||
|
llvm::ArrayRef<std::string> remove;
|
||||||
|
|
||||||
|
/// The commands that you want to add to original commands list.
|
||||||
|
llvm::ArrayRef<std::string> append;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class UpdateKind : std::uint8_t {
|
||||||
|
Unchanged,
|
||||||
|
Inserted,
|
||||||
|
Deleted,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UpdateInfo {
|
||||||
|
/// The kind of update.
|
||||||
|
UpdateKind kind;
|
||||||
|
|
||||||
|
/// The updated file.
|
||||||
|
std::uint32_t path_id;
|
||||||
|
|
||||||
|
/// The compilation context of this file command, which could
|
||||||
|
/// be used to identity the same file with different compilation
|
||||||
|
/// contexts.
|
||||||
|
const void* context;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompilationContext {
|
||||||
|
/// The working directory of compilation.
|
||||||
|
llvm::StringRef directory;
|
||||||
|
|
||||||
|
/// The compilation arguments.
|
||||||
|
std::vector<const char*> arguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string print_argv(llvm::ArrayRef<const char*> args);
|
||||||
|
|
||||||
|
class CompilationDatabase {
|
||||||
|
public:
|
||||||
|
CompilationDatabase();
|
||||||
|
|
||||||
|
CompilationDatabase(const CompilationDatabase&) = delete;
|
||||||
|
|
||||||
|
CompilationDatabase(CompilationDatabase&& other);
|
||||||
|
|
||||||
|
CompilationDatabase& operator=(const CompilationDatabase&) = delete;
|
||||||
|
|
||||||
|
CompilationDatabase& operator=(CompilationDatabase&& other);
|
||||||
|
|
||||||
|
~CompilationDatabase();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Read the compilation database on the give file and return the
|
||||||
|
/// incremental update infos.
|
||||||
|
std::vector<UpdateInfo> load_compile_database(llvm::StringRef file);
|
||||||
|
|
||||||
|
/// Lookup the compilation context of specific file. If the context
|
||||||
|
/// param is provided, we will return the compilation context corresponding
|
||||||
|
/// to the handle. Otherwise we just return the first one(if the file have)
|
||||||
|
/// multiple compilation contexts.
|
||||||
|
CompilationContext lookup(llvm::StringRef file,
|
||||||
|
const CommandOptions& options = {},
|
||||||
|
const void* context = nullptr);
|
||||||
|
|
||||||
|
/// TODO: list all compilation context of the file, this is useful to show
|
||||||
|
/// all contexts and let user choose one.
|
||||||
|
/// std::vector<CompilationContext> fetch_all(llvm::StringRef file);
|
||||||
|
|
||||||
|
/// Get an the option for specific argument.
|
||||||
|
static std::optional<std::uint32_t> get_option_id(llvm::StringRef argument);
|
||||||
|
|
||||||
|
/// FIXME: bad interface design ...
|
||||||
|
std::vector<llvm::StringRef> files();
|
||||||
|
|
||||||
|
/// FIXME: remove this api?
|
||||||
|
auto save_string(llvm::StringRef string) -> llvm::StringRef;
|
||||||
|
|
||||||
|
#ifdef CLICE_ENABLE_TEST
|
||||||
|
|
||||||
|
void add_command(llvm::StringRef directory,
|
||||||
|
llvm::StringRef file,
|
||||||
|
llvm::ArrayRef<const char*> arguments);
|
||||||
|
|
||||||
|
void add_command(llvm::StringRef directory, llvm::StringRef file, llvm::StringRef command);
|
||||||
|
|
||||||
|
/// FIXME: remove this
|
||||||
|
/// Update commands from json file and return all updated file.
|
||||||
|
std::expected<std::vector<UpdateInfo>, std::string> load_commands(llvm::StringRef json_content,
|
||||||
|
llvm::StringRef workspace);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> self;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -1,19 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include "CompilationUnit.h"
|
||||||
#include <cassert>
|
#include "Module.h"
|
||||||
#include <cstdint>
|
#include "Preamble.h"
|
||||||
#include <memory>
|
#include "Support/FileSystem.h"
|
||||||
#include <string>
|
|
||||||
#include <tuple>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "compile/compilation_unit.h"
|
|
||||||
#include "support/filesystem.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/StringMap.h"
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clang {
|
namespace clang {
|
||||||
|
|
||||||
@@ -23,46 +13,6 @@ class CodeCompleteConsumer;
|
|||||||
|
|
||||||
namespace clice {
|
namespace clice {
|
||||||
|
|
||||||
struct PCHInfo {
|
|
||||||
/// The path of the output PCH file.
|
|
||||||
std::string path;
|
|
||||||
|
|
||||||
/// The building time of this PCH.
|
|
||||||
std::int64_t mtime;
|
|
||||||
|
|
||||||
/// The content used to build this PCH.
|
|
||||||
std::string preamble;
|
|
||||||
|
|
||||||
/// All files involved in building this PCH.
|
|
||||||
std::vector<std::string> deps;
|
|
||||||
|
|
||||||
/// The command arguments used to build this PCH.
|
|
||||||
std::vector<const char*> arguments;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ModuleInfo {
|
|
||||||
/// Whether this module is an interface unit.
|
|
||||||
/// i.e. has export module declaration.
|
|
||||||
bool isInterfaceUnit = false;
|
|
||||||
|
|
||||||
/// Module name.
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
/// Dependent modules of this module.
|
|
||||||
std::vector<std::string> mods;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PCMInfo : ModuleInfo {
|
|
||||||
/// PCM file path.
|
|
||||||
std::string path;
|
|
||||||
|
|
||||||
/// Source file path.
|
|
||||||
std::string srcPath;
|
|
||||||
|
|
||||||
/// Files involved in building this PCM(not include module).
|
|
||||||
std::vector<std::string> deps;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompilationParams {
|
struct CompilationParams {
|
||||||
/// The kind of this compilation.
|
/// The kind of this compilation.
|
||||||
CompilationKind kind;
|
CompilationKind kind;
|
||||||
@@ -75,13 +25,15 @@ struct CompilationParams {
|
|||||||
|
|
||||||
std::string directory;
|
std::string directory;
|
||||||
|
|
||||||
|
bool arguments_from_database = false;
|
||||||
|
|
||||||
/// Responsible for storing the arguments.
|
/// Responsible for storing the arguments.
|
||||||
std::vector<const char*> arguments;
|
std::vector<const char*> arguments;
|
||||||
|
|
||||||
llvm::IntrusiveRefCntPtr<vfs::FileSystem> vfs = new ThreadSafeFS();
|
llvm::IntrusiveRefCntPtr<vfs::FileSystem> vfs = new ThreadSafeFS();
|
||||||
|
|
||||||
/// Information about reuse PCH.
|
/// Information about reuse PCH.
|
||||||
std::pair<std::string, std::uint32_t> pch;
|
std::pair<std::string, uint32_t> pch;
|
||||||
|
|
||||||
/// Information about reuse PCM(name, path).
|
/// Information about reuse PCM(name, path).
|
||||||
llvm::StringMap<std::string> pcms;
|
llvm::StringMap<std::string> pcms;
|
||||||
@@ -1,33 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "compile/diagnostic.h"
|
#include "Directive.h"
|
||||||
#include "compile/directive.h"
|
#include "AST/Resolver.h"
|
||||||
#include "semantic/resolver.h"
|
#include "AST/SourceCode.h"
|
||||||
#include "syntax/token.h"
|
#include "AST/SymbolID.h"
|
||||||
|
#include "Compiler/Diagnostic.h"
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/ADT/DenseSet.h"
|
|
||||||
#include "clang/Tooling/Syntax/Tokens.h"
|
#include "clang/Tooling/Syntax/Tokens.h"
|
||||||
|
|
||||||
namespace clice {
|
namespace clice {
|
||||||
|
|
||||||
namespace index {
|
|
||||||
|
|
||||||
// Temporary in-header definition during migration.
|
|
||||||
struct SymbolID {
|
|
||||||
std::uint64_t hash;
|
|
||||||
std::string name;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace index
|
|
||||||
|
|
||||||
enum class CompilationKind : std::uint8_t {
|
enum class CompilationKind : std::uint8_t {
|
||||||
/// From preprocessing the source file. Therefore directives
|
/// From preprocessing the source file. Therefore directives
|
||||||
/// are available but AST nodes are not.
|
/// are available but AST nodes are not.
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "syntax/token.h"
|
#include "AST/SourceCode.h"
|
||||||
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
|
|
||||||
namespace clice {
|
namespace clice {
|
||||||
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include "AST/SourceCode.h"
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "syntax/token.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
#include "clang/Lex/MacroInfo.h"
|
#include "clang/Lex/MacroInfo.h"
|
||||||
@@ -20,8 +16,11 @@ struct Include {
|
|||||||
/// The file id of included file.
|
/// The file id of included file.
|
||||||
clang::FileID fid;
|
clang::FileID fid;
|
||||||
|
|
||||||
/// Location of the `include` keyword.
|
/// Location of the `include`.
|
||||||
clang::SourceLocation location;
|
clang::SourceLocation location;
|
||||||
|
|
||||||
|
/// The range of filename(includes `""` or `<>`).
|
||||||
|
clang::SourceRange filename_range;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Information about `__has_include` directive.
|
/// Information about `__has_include` directive.
|
||||||
@@ -37,7 +36,7 @@ struct HasInclude {
|
|||||||
/// Information about `#if`, `#ifdef`, `#ifndef`, `#elif`,
|
/// Information about `#if`, `#ifdef`, `#ifndef`, `#elif`,
|
||||||
/// `#elifdef`, `#else`, `#endif` directive.
|
/// `#elifdef`, `#else`, `#endif` directive.
|
||||||
struct Condition {
|
struct Condition {
|
||||||
enum class BranchKind : std::uint8_t {
|
enum class BranchKind : uint8_t {
|
||||||
If = 0,
|
If = 0,
|
||||||
Elif,
|
Elif,
|
||||||
Ifdef,
|
Ifdef,
|
||||||
@@ -50,7 +49,7 @@ struct Condition {
|
|||||||
|
|
||||||
using enum BranchKind;
|
using enum BranchKind;
|
||||||
|
|
||||||
enum class ConditionValue : std::uint8_t {
|
enum class ConditionValue : uint8_t {
|
||||||
True = 0,
|
True = 0,
|
||||||
False,
|
False,
|
||||||
Skipped,
|
Skipped,
|
||||||
@@ -74,7 +73,7 @@ struct Condition {
|
|||||||
|
|
||||||
/// Information about macro definition, reference and undef.
|
/// Information about macro definition, reference and undef.
|
||||||
struct MacroRef {
|
struct MacroRef {
|
||||||
enum class Kind : std::uint8_t {
|
enum class Kind : uint8_t {
|
||||||
Def = 0,
|
Def = 0,
|
||||||
Ref,
|
Ref,
|
||||||
Undef,
|
Undef,
|
||||||
@@ -94,7 +93,7 @@ struct MacroRef {
|
|||||||
|
|
||||||
/// Information about `#pragma` directive.
|
/// Information about `#pragma` directive.
|
||||||
struct Pragma {
|
struct Pragma {
|
||||||
enum class Kind : std::uint8_t {
|
enum class Kind : uint8_t {
|
||||||
Region,
|
Region,
|
||||||
EndRegion,
|
EndRegion,
|
||||||
|
|
||||||
@@ -129,39 +128,6 @@ struct Import {
|
|||||||
std::vector<clang::SourceLocation> name_locations;
|
std::vector<clang::SourceLocation> name_locations;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Information about `#embed` directive.
|
|
||||||
struct Embed {
|
|
||||||
/// The file name in the embed directive, not including quotes or angle brackets.
|
|
||||||
llvm::StringRef file_name;
|
|
||||||
|
|
||||||
/// The actual file that may be embedded by this embed directive.
|
|
||||||
clang::OptionalFileEntryRef file;
|
|
||||||
|
|
||||||
/// Whether the file name is angled.
|
|
||||||
bool is_angled;
|
|
||||||
|
|
||||||
/// Location of the `#` token.
|
|
||||||
clang::SourceLocation loc;
|
|
||||||
|
|
||||||
/// TODO: Currently we do not store parameters of the embed directive.
|
|
||||||
/// See clang::LexEmbedParametersResult for details.
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Information about `__has_embed` directive.
|
|
||||||
struct HasEmbed {
|
|
||||||
/// The file name in the embed directive, not including quotes or angle brackets.
|
|
||||||
llvm::StringRef file_name;
|
|
||||||
|
|
||||||
/// The actual file that may be embedded by this embed directive.
|
|
||||||
clang::OptionalFileEntryRef file;
|
|
||||||
|
|
||||||
/// Whether the file name is angled.
|
|
||||||
bool is_angled;
|
|
||||||
|
|
||||||
/// Location of the `__has_embed` token.
|
|
||||||
clang::SourceLocation loc;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Directive {
|
struct Directive {
|
||||||
std::vector<Include> includes;
|
std::vector<Include> includes;
|
||||||
std::vector<HasInclude> has_includes;
|
std::vector<HasInclude> has_includes;
|
||||||
@@ -169,8 +135,6 @@ struct Directive {
|
|||||||
std::vector<MacroRef> macros;
|
std::vector<MacroRef> macros;
|
||||||
std::vector<Pragma> pragmas;
|
std::vector<Pragma> pragmas;
|
||||||
std::vector<Import> imports;
|
std::vector<Import> imports;
|
||||||
std::vector<Embed> embeds;
|
|
||||||
std::vector<HasEmbed> has_embeds;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clice
|
} // namespace clice
|
||||||
46
include/Compiler/Module.h
Normal file
46
include/Compiler/Module.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Support/Struct.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
class CompilationUnit;
|
||||||
|
|
||||||
|
struct CompilationParams;
|
||||||
|
|
||||||
|
struct ModuleInfo {
|
||||||
|
/// Whether this module is an interface unit.
|
||||||
|
/// i.e. has export module declaration.
|
||||||
|
bool isInterfaceUnit = false;
|
||||||
|
|
||||||
|
/// Module name.
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
/// Dependent modules of this module.
|
||||||
|
std::vector<std::string> mods;
|
||||||
|
};
|
||||||
|
|
||||||
|
inherited_struct(PCMInfo, ModuleInfo) {
|
||||||
|
/// PCM file path.
|
||||||
|
std::string path;
|
||||||
|
|
||||||
|
/// Source file path.
|
||||||
|
std::string srcPath;
|
||||||
|
|
||||||
|
/// Files involved in building this PCM(not include module).
|
||||||
|
std::vector<std::string> deps;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// If input file is module interface unit, return its module name.
|
||||||
|
/// Otherwise, return an empty string.
|
||||||
|
std::string scanModuleName(CompilationParams& params);
|
||||||
|
|
||||||
|
/// Run the preprocessor to scan the given module unit to
|
||||||
|
/// collect its module name and dependencies.
|
||||||
|
std::expected<ModuleInfo, std::string> scanModule(CompilationParams& params);
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
40
include/Compiler/Preamble.h
Normal file
40
include/Compiler/Preamble.h
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <expected>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
class CompilationUnit;
|
||||||
|
|
||||||
|
struct CompilationParams;
|
||||||
|
|
||||||
|
struct PCHInfo {
|
||||||
|
/// The path of the output PCH file.
|
||||||
|
std::string path;
|
||||||
|
|
||||||
|
/// The building time of this PCH.
|
||||||
|
std::int64_t mtime;
|
||||||
|
|
||||||
|
/// The content used to build this PCH.
|
||||||
|
std::string preamble;
|
||||||
|
|
||||||
|
/// All files involved in building this PCH.
|
||||||
|
std::vector<std::string> deps;
|
||||||
|
|
||||||
|
/// The command arguments used to build this PCH.
|
||||||
|
std::vector<const char*> arguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Compute the preamble bound of given content. We just
|
||||||
|
/// run lex until we find first not directive.
|
||||||
|
std::uint32_t compute_preamble_bound(llvm::StringRef content);
|
||||||
|
|
||||||
|
/// Same as above, but return a group of bounds for chained PCH
|
||||||
|
/// building.
|
||||||
|
std::vector<uint32_t> compute_preamble_bounds(llvm::StringRef content);
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
34
include/Compiler/Scan.h
Normal file
34
include/Compiler/Scan.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
struct Inclusion {
|
||||||
|
/// Whether this file is braced angles.
|
||||||
|
bool angled;
|
||||||
|
|
||||||
|
/// The line of this inclusion(zero based).
|
||||||
|
/// std::uint32_t line;
|
||||||
|
|
||||||
|
/// The included file.
|
||||||
|
llvm::StringRef file;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScanResult {
|
||||||
|
/// The module file of this file(may be empty).
|
||||||
|
std::vector<Token> module_name;
|
||||||
|
|
||||||
|
/// The includes of file.
|
||||||
|
std::vector<Inclusion> includes;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Scan the file and return necessary info.
|
||||||
|
ScanResult scan(llvm::StringRef content);
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "llvm/ADT/ArrayRef.h"
|
#include "llvm/ADT/ArrayRef.h"
|
||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
#include "llvm/ADT/StringRef.h"
|
#include "llvm/ADT/StringRef.h"
|
||||||
@@ -1,3 +1 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
int a = 1;
|
|
||||||
107
include/Feature/CodeCompletion.h
Normal file
107
include/Feature/CodeCompletion.h
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
struct CompilationParams;
|
||||||
|
|
||||||
|
namespace config {
|
||||||
|
|
||||||
|
struct CodeCompletionOption {
|
||||||
|
/// Insert placeholder for keywords? function call parameters? template arguments?
|
||||||
|
bool enable_keyword_snippet = false;
|
||||||
|
|
||||||
|
/// Also apply for lambda ...
|
||||||
|
bool enable_function_arguments_snippet = false;
|
||||||
|
bool enable_template_arguments_snippet = false;
|
||||||
|
|
||||||
|
bool insert_paren_in_function_call = false;
|
||||||
|
/// TODO: Add more detailed option, see
|
||||||
|
/// https://github.com/llvm/llvm-project/issues/63565
|
||||||
|
|
||||||
|
bool bundle_overloads = true;
|
||||||
|
|
||||||
|
/// The limits of code completion, 0 is non limit.
|
||||||
|
std::uint32_t limit = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // namespace config
|
||||||
|
|
||||||
|
namespace feature {
|
||||||
|
|
||||||
|
enum class CompletionItemKind {
|
||||||
|
None = 0,
|
||||||
|
Text,
|
||||||
|
Method,
|
||||||
|
Function,
|
||||||
|
Constructor,
|
||||||
|
Field,
|
||||||
|
Variable,
|
||||||
|
Class,
|
||||||
|
Interface,
|
||||||
|
Module,
|
||||||
|
Property,
|
||||||
|
Unit,
|
||||||
|
Value,
|
||||||
|
Enum,
|
||||||
|
Keyword,
|
||||||
|
Snippet,
|
||||||
|
Color,
|
||||||
|
File,
|
||||||
|
Reference,
|
||||||
|
Folder,
|
||||||
|
EnumMember,
|
||||||
|
Constant,
|
||||||
|
Struct,
|
||||||
|
Event,
|
||||||
|
Operator,
|
||||||
|
TypeParameter
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a single code completion item to be presented to the user.
|
||||||
|
struct CompletionItem {
|
||||||
|
/// The primary label displayed in the completion list.
|
||||||
|
std::string label;
|
||||||
|
|
||||||
|
/// Additional details, like a function signature, shown next to the label.
|
||||||
|
std::string detail;
|
||||||
|
|
||||||
|
/// A short description of the item, typically its type or namespace.
|
||||||
|
std::string description;
|
||||||
|
|
||||||
|
/// Full documentation for the item, shown on selection or hover.
|
||||||
|
std::string document;
|
||||||
|
|
||||||
|
/// The kind of item (function, class, etc.), used for an icon.
|
||||||
|
CompletionItemKind kind;
|
||||||
|
|
||||||
|
/// A score for ranking this item against others. Higher is better.
|
||||||
|
float score;
|
||||||
|
|
||||||
|
/// Whether this item is deprecated (often rendered with a strikethrough).
|
||||||
|
bool deprecated;
|
||||||
|
|
||||||
|
/// The text edit to be applied when this item is accepted.
|
||||||
|
struct Edit {
|
||||||
|
/// The new text to insert, which may be a snippet.
|
||||||
|
std::string text;
|
||||||
|
|
||||||
|
/// The source range to be replaced by the new text.
|
||||||
|
LocalSourceRange range;
|
||||||
|
} edit;
|
||||||
|
};
|
||||||
|
|
||||||
|
using CodeCompletionResult = std::vector<CompletionItem>;
|
||||||
|
|
||||||
|
std::vector<CompletionItem> code_complete(CompilationParams& params,
|
||||||
|
const config::CodeCompletionOption& option);
|
||||||
|
|
||||||
|
} // namespace feature
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -1,3 +1 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
int b = 2;
|
|
||||||
19
include/Feature/Diagnostic.h
Normal file
19
include/Feature/Diagnostic.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Compiler/Diagnostic.h"
|
||||||
|
#include "Server/Convert.h"
|
||||||
|
#include "Support/JSON.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
class CompilationUnitRef;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace clice::feature {
|
||||||
|
|
||||||
|
/// FIXME: This is not correct way, we don't want to couple
|
||||||
|
/// `Feature with Protocol`? Return an array of LSP diagnostic.
|
||||||
|
json::Value diagnostics(PositionEncodingKind kind, PathMapping mapping, CompilationUnitRef unit);
|
||||||
|
|
||||||
|
} // namespace clice::feature
|
||||||
3
include/Feature/DocumentHighlight.h
Normal file
3
include/Feature/DocumentHighlight.h
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace clice::proto {}
|
||||||
26
include/Feature/DocumentLink.h
Normal file
26
include/Feature/DocumentLink.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
#include "Index/Shared.h"
|
||||||
|
|
||||||
|
namespace clice::feature {
|
||||||
|
|
||||||
|
struct DocumentLink {
|
||||||
|
/// The range of the whole link.
|
||||||
|
LocalSourceRange range;
|
||||||
|
|
||||||
|
/// The target string path.
|
||||||
|
std::string file;
|
||||||
|
};
|
||||||
|
|
||||||
|
using DocumentLinks = std::vector<DocumentLink>;
|
||||||
|
|
||||||
|
/// Generate document link for main file.
|
||||||
|
DocumentLinks document_links(CompilationUnitRef unit);
|
||||||
|
|
||||||
|
/// Generate document link for all source file.
|
||||||
|
index::Shared<DocumentLinks> index_document_link(CompilationUnitRef unit);
|
||||||
|
|
||||||
|
} // namespace clice::feature
|
||||||
37
include/Feature/DocumentSymbol.h
Normal file
37
include/Feature/DocumentSymbol.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
#include "AST/SymbolKind.h"
|
||||||
|
#include "Index/Shared.h"
|
||||||
|
|
||||||
|
namespace clice::feature {
|
||||||
|
|
||||||
|
struct DocumentSymbol {
|
||||||
|
/// The range of symbol name in source code.
|
||||||
|
LocalSourceRange selectionRange;
|
||||||
|
|
||||||
|
/// The range of whole symbol.
|
||||||
|
LocalSourceRange range;
|
||||||
|
|
||||||
|
/// The symbol kind of this document symbol.
|
||||||
|
SymbolKind kind;
|
||||||
|
|
||||||
|
/// The symbol name.
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
/// Extra information about this symbol.
|
||||||
|
std::string detail;
|
||||||
|
|
||||||
|
/// The symbols that this symbol contains
|
||||||
|
std::vector<DocumentSymbol> children;
|
||||||
|
};
|
||||||
|
|
||||||
|
using DocumentSymbols = std::vector<DocumentSymbol>;
|
||||||
|
|
||||||
|
/// Generate document symbols for only interested file.
|
||||||
|
DocumentSymbols document_symbols(CompilationUnitRef unit);
|
||||||
|
|
||||||
|
/// Generate document symbols for all file in unit.
|
||||||
|
index::Shared<DocumentSymbols> index_document_symbol(CompilationUnitRef unit);
|
||||||
|
|
||||||
|
} // namespace clice::feature
|
||||||
55
include/Feature/FoldingRange.h
Normal file
55
include/Feature/FoldingRange.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
#include "Index/Shared.h"
|
||||||
|
#include "Support/Enum.h"
|
||||||
|
|
||||||
|
namespace clice::feature {
|
||||||
|
|
||||||
|
struct FoldingRangeKind : refl::Enum<FoldingRangeKind> {
|
||||||
|
enum Kind : uint8_t {
|
||||||
|
Invalid = 0,
|
||||||
|
Comment,
|
||||||
|
Imports,
|
||||||
|
Region,
|
||||||
|
Namespace,
|
||||||
|
Class,
|
||||||
|
Enum,
|
||||||
|
Struct,
|
||||||
|
Union,
|
||||||
|
LambdaCapture,
|
||||||
|
FunctionParams,
|
||||||
|
FunctionBody,
|
||||||
|
FunctionCall,
|
||||||
|
CompoundStmt,
|
||||||
|
AccessSpecifier,
|
||||||
|
ConditionDirective,
|
||||||
|
Initializer,
|
||||||
|
};
|
||||||
|
|
||||||
|
using Enum::Enum;
|
||||||
|
|
||||||
|
constexpr static auto InvalidEnum = Invalid;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// We don't record the coalesced text for a range, because it's rarely useful.
|
||||||
|
struct FoldingRange {
|
||||||
|
/// The range to fold.
|
||||||
|
LocalSourceRange range;
|
||||||
|
|
||||||
|
/// Describes the kind of the folding range.
|
||||||
|
FoldingRangeKind kind;
|
||||||
|
|
||||||
|
/// The text to display when the folding range is collapsed.
|
||||||
|
std::string text;
|
||||||
|
};
|
||||||
|
|
||||||
|
using FoldingRanges = std::vector<FoldingRange>;
|
||||||
|
|
||||||
|
/// Generate folding range for interested file only.
|
||||||
|
FoldingRanges folding_ranges(CompilationUnitRef unit);
|
||||||
|
|
||||||
|
/// Generate folding range for all files.
|
||||||
|
index::Shared<FoldingRanges> index_folding_range(CompilationUnitRef unit);
|
||||||
|
|
||||||
|
} // namespace clice::feature
|
||||||
14
include/Feature/Formatting.h
Normal file
14
include/Feature/Formatting.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
#include "Protocol/Feature/Formatting.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
namespace clice::feature {
|
||||||
|
|
||||||
|
std::vector<proto::TextEdit> document_format(llvm::StringRef file,
|
||||||
|
llvm::StringRef content,
|
||||||
|
std::optional<LocalSourceRange>);
|
||||||
|
|
||||||
|
}
|
||||||
74
include/Feature/Hover.h
Normal file
74
include/Feature/Hover.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
#include "AST/SymbolKind.h"
|
||||||
|
#include "Index/Shared.h"
|
||||||
|
|
||||||
|
namespace clice::config {
|
||||||
|
|
||||||
|
struct HoverOptions {
|
||||||
|
/// Strip doxygen info and merge with lsp info
|
||||||
|
bool enable_doxygen_parsing = true;
|
||||||
|
/// If set `false`, the comment will be wrapped
|
||||||
|
/// in code block and keep ascii typesetting
|
||||||
|
bool parse_comment_as_markdown = true;
|
||||||
|
/// Show sugar type
|
||||||
|
bool show_aka = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice::config
|
||||||
|
|
||||||
|
namespace clice::feature {
|
||||||
|
|
||||||
|
struct HoverItem {
|
||||||
|
enum class HoverKind : uint8_t {
|
||||||
|
/// The typename of a variable or a type alias.
|
||||||
|
Type,
|
||||||
|
/// Size of type or variable.
|
||||||
|
Size,
|
||||||
|
/// Align of type or variable.
|
||||||
|
Align,
|
||||||
|
/// Offset of field in a class/struct.
|
||||||
|
Offset,
|
||||||
|
/// Bit width of a bit field.
|
||||||
|
BitWidth,
|
||||||
|
/// The index of a field in a class/struct.
|
||||||
|
FieldIndex,
|
||||||
|
/// The value of an enum item.
|
||||||
|
EnumValue,
|
||||||
|
};
|
||||||
|
|
||||||
|
using enum HoverKind;
|
||||||
|
|
||||||
|
HoverKind kind;
|
||||||
|
|
||||||
|
std::string value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Hover information for a symbol.
|
||||||
|
struct Hover {
|
||||||
|
/// Title
|
||||||
|
SymbolKind kind;
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
/// Extra information.
|
||||||
|
std::vector<HoverItem> items;
|
||||||
|
|
||||||
|
/// Raw document in the source code.
|
||||||
|
std::string document;
|
||||||
|
|
||||||
|
/// The full qualified name of the declaration.
|
||||||
|
std::string qualifier;
|
||||||
|
|
||||||
|
/// The source code of the declaration.
|
||||||
|
std::string source;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Generate the hover information for the given declaration(for test).
|
||||||
|
Hover hover(CompilationUnitRef unit, const clang::NamedDecl* decl);
|
||||||
|
|
||||||
|
/// Generate the hover information for the symbol at the given offset.
|
||||||
|
Hover hover(CompilationUnitRef unit, std::uint32_t offset);
|
||||||
|
|
||||||
|
} // namespace clice::feature
|
||||||
57
include/Feature/InlayHint.h
Normal file
57
include/Feature/InlayHint.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
#include "AST/SymbolID.h"
|
||||||
|
#include "Index/Shared.h"
|
||||||
|
#include "Support/JSON.h"
|
||||||
|
|
||||||
|
namespace clice::config {
|
||||||
|
|
||||||
|
struct InlayHintsOptions {
|
||||||
|
/// If false, inlay hints are completely disabled.
|
||||||
|
bool enabled = true;
|
||||||
|
|
||||||
|
// Whether specific categories of hints are enabled.
|
||||||
|
bool parameters = true;
|
||||||
|
bool deduced_types = true;
|
||||||
|
bool designators = true;
|
||||||
|
bool block_end = false;
|
||||||
|
bool default_arguments = false;
|
||||||
|
|
||||||
|
// Limit the length of type names in inlay hints. (0 means no limit)
|
||||||
|
uint32_t type_name_limit = 32;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace clice::config
|
||||||
|
|
||||||
|
namespace clice::feature {
|
||||||
|
|
||||||
|
enum class InlayHintKind {
|
||||||
|
Parameter,
|
||||||
|
InvalidEnum,
|
||||||
|
DefaultArgument,
|
||||||
|
Type,
|
||||||
|
Designator,
|
||||||
|
BlockEnd,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InlayHint {
|
||||||
|
/// The position offset of the inlay hint in the source code.
|
||||||
|
std::uint32_t offset;
|
||||||
|
|
||||||
|
/// The kind/category of the inlay hint.
|
||||||
|
InlayHintKind kind;
|
||||||
|
|
||||||
|
/// The label parts of the inlay hint.
|
||||||
|
/// Each SymbolID consists of two parts: the symbol name and its USR hash.
|
||||||
|
/// For symbols without a USR (e.g., built-in types or function parameters),
|
||||||
|
/// the symbol hash will be empty.
|
||||||
|
/// Otherwise, the symbol hash is non-empty and can be used for "go-to-definition".
|
||||||
|
std::vector<index::SymbolID> parts;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto inlay_hints(CompilationUnitRef unit,
|
||||||
|
LocalSourceRange target,
|
||||||
|
const config::InlayHintsOptions& options) -> std::vector<InlayHint>;
|
||||||
|
|
||||||
|
} // namespace clice::feature
|
||||||
85
include/Feature/Lookup.h
Normal file
85
include/Feature/Lookup.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AST/SymbolKind.h"
|
||||||
|
#include "Server/Protocol.h"
|
||||||
|
#include "Support/Struct.h"
|
||||||
|
|
||||||
|
namespace clice::proto {
|
||||||
|
|
||||||
|
struct WorkDoneProgressOptions {
|
||||||
|
/// Report on work done progress.
|
||||||
|
bool workDoneProgress = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PartialResultParams {};
|
||||||
|
|
||||||
|
/// The options of the all lookup.
|
||||||
|
using LookupOptions = WorkDoneProgressOptions;
|
||||||
|
|
||||||
|
/// The parameters of the simple lookup(definition, declaration,
|
||||||
|
/// type definition, implementation and reference).
|
||||||
|
inherited_struct(ReferenceParams, TextDocumentPositionParams, PartialResultParams){};
|
||||||
|
|
||||||
|
/// The result of the simple lookup.
|
||||||
|
using ReferenceResult = std::vector<Location>;
|
||||||
|
|
||||||
|
/// The parameters of the all hierarchy resolve(call hierarchy and type hierarchy)
|
||||||
|
using HierarchyPrepareParams = TextDocumentPositionParams;
|
||||||
|
|
||||||
|
struct HierarchyItem {
|
||||||
|
/// The name of the item.
|
||||||
|
string name;
|
||||||
|
|
||||||
|
/// The kind of the item.
|
||||||
|
SymbolKind kind;
|
||||||
|
|
||||||
|
/// The resource identifier of this item.
|
||||||
|
DocumentUri uri;
|
||||||
|
|
||||||
|
/// The range enclosing this symbol not including leading/trailing whitespace
|
||||||
|
/// but everything else, e.g. comments and code.
|
||||||
|
Range range;
|
||||||
|
|
||||||
|
/// The range that should be selected and revealed when this symbol is being
|
||||||
|
/// picked, e.g. the name of a function. Must be contained by the
|
||||||
|
/// [`range`](#CallHierarchyItem.range).
|
||||||
|
Range selectionRange;
|
||||||
|
|
||||||
|
/// A customized data of the item. We use it to store
|
||||||
|
/// the USR hash of the item.
|
||||||
|
uint64_t data = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
using HierarchyPrepareResult = std::vector<HierarchyItem>;
|
||||||
|
|
||||||
|
/// The parameters of the both call hierarchy and type hierarchy.
|
||||||
|
inherited_struct(HierarchyParams, TextDocumentPositionParams, PartialResultParams) {
|
||||||
|
HierarchyItem item;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CallHierarchyIncomingCall {
|
||||||
|
/// The item that makes the call.
|
||||||
|
HierarchyItem from;
|
||||||
|
|
||||||
|
/// The ranges at which the calls appear. This is relative to the caller
|
||||||
|
/// denoted by [`this.from`](#CallHierarchyIncomingCall.from).
|
||||||
|
std::vector<Range> fromRanges;
|
||||||
|
};
|
||||||
|
|
||||||
|
using CallHierarchyIncomingCallsResult = std::vector<CallHierarchyIncomingCall>;
|
||||||
|
|
||||||
|
struct CallHierarchyOutgoingCall {
|
||||||
|
/// The item that is called.
|
||||||
|
HierarchyItem to;
|
||||||
|
|
||||||
|
/// The range at which this item is called. This is the range relative to
|
||||||
|
/// the caller, e.g the item passed to `callHierarchy/outgoingCalls` request.
|
||||||
|
std::vector<Range> fromRanges;
|
||||||
|
};
|
||||||
|
|
||||||
|
using CallHierarchyOutgoingCallsResult = std::vector<CallHierarchyOutgoingCall>;
|
||||||
|
|
||||||
|
/// The result of the both super and sub type hierarchy.
|
||||||
|
using TypeHierarchyResult = std::vector<HierarchyItem>;
|
||||||
|
|
||||||
|
} // namespace clice::proto
|
||||||
29
include/Feature/SemanticToken.h
Normal file
29
include/Feature/SemanticToken.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AST/SourceCode.h"
|
||||||
|
#include "AST/SymbolKind.h"
|
||||||
|
#include "Index/Shared.h"
|
||||||
|
|
||||||
|
namespace clice::config {
|
||||||
|
|
||||||
|
struct SemanticTokensOption {};
|
||||||
|
|
||||||
|
}; // namespace clice::config
|
||||||
|
|
||||||
|
namespace clice::feature {
|
||||||
|
|
||||||
|
struct SemanticToken {
|
||||||
|
LocalSourceRange range;
|
||||||
|
SymbolKind kind;
|
||||||
|
SymbolModifiers modifiers;
|
||||||
|
};
|
||||||
|
|
||||||
|
using SemanticTokens = std::vector<SemanticToken>;
|
||||||
|
|
||||||
|
/// Generate semantic tokens for the interested file only.
|
||||||
|
SemanticTokens semantic_tokens(CompilationUnitRef unit);
|
||||||
|
|
||||||
|
/// Generate semantic tokens for all files.
|
||||||
|
index::Shared<SemanticTokens> index_semantic_token(CompilationUnitRef unit);
|
||||||
|
|
||||||
|
} // namespace clice::feature
|
||||||
27
include/Feature/SignatureHelp.h
Normal file
27
include/Feature/SignatureHelp.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Protocol/Feature/SignatureHelp.h"
|
||||||
|
|
||||||
|
#include "llvm/ADT/StringRef.h"
|
||||||
|
|
||||||
|
namespace clice {
|
||||||
|
|
||||||
|
struct CompilationParams;
|
||||||
|
|
||||||
|
namespace config {
|
||||||
|
|
||||||
|
struct SignatureHelpOption {};
|
||||||
|
|
||||||
|
} // namespace config
|
||||||
|
|
||||||
|
namespace feature {
|
||||||
|
|
||||||
|
proto::SignatureHelp signature_help(CompilationParams& params,
|
||||||
|
const config::SignatureHelpOption& option);
|
||||||
|
|
||||||
|
} // namespace feature
|
||||||
|
|
||||||
|
} // namespace clice
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "syntax/token.h"
|
#include "AST/SourceCode.h"
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
#include "llvm/ADT/DenseMap.h"
|
||||||
|
|
||||||
@@ -57,7 +54,7 @@ struct IncludeGraph {
|
|||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint32_t path_id(clang::FileID fid) const {
|
std::uint32_t path_id(clang::FileID fid) {
|
||||||
auto include = include_location_id(fid);
|
auto include = include_location_id(fid);
|
||||||
if(include != -1) {
|
if(include != -1) {
|
||||||
return locations[include].path_id;
|
return locations[include].path_id;
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
#include "TUIndex.h"
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "index/tu_index.h"
|
|
||||||
|
|
||||||
#include "llvm/Support/Allocator.h"
|
#include "llvm/Support/Allocator.h"
|
||||||
#include "llvm/Support/MemoryBuffer.h"
|
#include "llvm/Support/MemoryBuffer.h"
|
||||||
@@ -64,23 +59,15 @@ public:
|
|||||||
/// Remove the index of specific path id.
|
/// Remove the index of specific path id.
|
||||||
void remove(this Self& self, std::uint32_t path_id);
|
void remove(this Self& self, std::uint32_t path_id);
|
||||||
|
|
||||||
/// Get the stored source content for position mapping.
|
|
||||||
llvm::StringRef content(this const Self& self);
|
|
||||||
|
|
||||||
/// Merge the index with given compilation context.
|
/// Merge the index with given compilation context.
|
||||||
void merge(this Self& self,
|
void merge(this Self& self,
|
||||||
std::uint32_t path_id,
|
std::uint32_t path_id,
|
||||||
std::chrono::milliseconds build_at,
|
std::chrono::milliseconds build_at,
|
||||||
std::vector<IncludeLocation> include_locations,
|
std::vector<IncludeLocation> include_locations,
|
||||||
FileIndex& index,
|
FileIndex& index);
|
||||||
llvm::StringRef content);
|
|
||||||
|
|
||||||
/// Merge the index with given header context.
|
/// Merge the index with given header context.
|
||||||
void merge(this Self& self,
|
void merge(this Self& self, std::uint32_t path_id, std::uint32_t include_id, FileIndex& index);
|
||||||
std::uint32_t path_id,
|
|
||||||
std::uint32_t include_id,
|
|
||||||
FileIndex& index,
|
|
||||||
llvm::StringRef content);
|
|
||||||
|
|
||||||
friend bool operator==(MergedIndex& lhs, MergedIndex& rhs);
|
friend bool operator==(MergedIndex& lhs, MergedIndex& rhs);
|
||||||
|
|
||||||
@@ -1,16 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include "TUIndex.h"
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "index/tu_index.h"
|
|
||||||
|
|
||||||
#include "llvm/ADT/DenseMap.h"
|
|
||||||
#include "llvm/ADT/SmallString.h"
|
|
||||||
#include "llvm/ADT/SmallVector.h"
|
|
||||||
#include "llvm/ADT/StringRef.h"
|
|
||||||
#include "llvm/Support/Allocator.h"
|
|
||||||
|
|
||||||
namespace clice::index {
|
namespace clice::index {
|
||||||
|
|
||||||
@@ -30,17 +20,6 @@ struct PathPool {
|
|||||||
|
|
||||||
auto path_id(llvm::StringRef path) {
|
auto path_id(llvm::StringRef path) {
|
||||||
assert(!path.empty());
|
assert(!path.empty());
|
||||||
|
|
||||||
// Normalize backslashes to forward slashes so that paths from different
|
|
||||||
// sources (URI decoding, CDB, clang FileManager) compare equal on
|
|
||||||
// Windows where native separators are backslashes.
|
|
||||||
llvm::SmallString<256> normalized;
|
|
||||||
if(path.contains('\\')) {
|
|
||||||
normalized = path;
|
|
||||||
std::replace(normalized.begin(), normalized.end(), '\\', '/');
|
|
||||||
path = normalized;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto [it, success] = cache.try_emplace(path, paths.size());
|
auto [it, success] = cache.try_emplace(path, paths.size());
|
||||||
if(!success) {
|
if(!success) {
|
||||||
return it->second;
|
return it->second;
|
||||||
@@ -55,18 +34,6 @@ struct PathPool {
|
|||||||
llvm::StringRef path(std::uint32_t id) {
|
llvm::StringRef path(std::uint32_t id) {
|
||||||
return paths[id];
|
return paths[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Look up a path in the cache, normalizing backslashes first.
|
|
||||||
/// Returns cache.end() if the path is not interned.
|
|
||||||
auto find(llvm::StringRef path) {
|
|
||||||
llvm::SmallString<256> normalized;
|
|
||||||
if(path.contains('\\')) {
|
|
||||||
normalized = path;
|
|
||||||
std::replace(normalized.begin(), normalized.end(), '\\', '/');
|
|
||||||
path = normalized;
|
|
||||||
}
|
|
||||||
return cache.find(path);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FileInfo {
|
struct FileInfo {
|
||||||
@@ -1,18 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <bit>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "index/include_graph.h"
|
#include "IncludeGraph.h"
|
||||||
#include "semantic/relation_kind.h"
|
#include "AST/RelationKind.h"
|
||||||
#include "semantic/symbol_kind.h"
|
#include "AST/SourceCode.h"
|
||||||
#include "support/bitmap.h"
|
#include "AST/SymbolKind.h"
|
||||||
|
#include "Support/Bitmap.h"
|
||||||
#include "llvm/Support/raw_ostream.h"
|
|
||||||
|
|
||||||
namespace clice::index {
|
namespace clice::index {
|
||||||
|
|
||||||
@@ -79,17 +73,9 @@ struct TUIndex {
|
|||||||
|
|
||||||
llvm::DenseMap<clang::FileID, FileIndex> file_indices;
|
llvm::DenseMap<clang::FileID, FileIndex> file_indices;
|
||||||
|
|
||||||
/// File indices keyed by path_id, populated by from() for deserialized data.
|
|
||||||
/// When built from AST, this is empty and file_indices (keyed by FileID) is used.
|
|
||||||
llvm::DenseMap<std::uint32_t, FileIndex> path_file_indices;
|
|
||||||
|
|
||||||
FileIndex main_file_index;
|
FileIndex main_file_index;
|
||||||
|
|
||||||
static TUIndex build(CompilationUnitRef unit, bool interested_only = false);
|
static TUIndex build(CompilationUnitRef unit);
|
||||||
|
|
||||||
void serialize(llvm::raw_ostream& os) const;
|
|
||||||
|
|
||||||
static TUIndex from(const void* data);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace clice::index
|
} // namespace clice::index
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user