Compare commits
68 Commits
v0.1.0-alp
...
raw-foldin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
183b90d572 | ||
|
|
939ab6d0d4 | ||
|
|
e1202d2fa5 | ||
|
|
17e68010a0 | ||
|
|
3fa653bcaf | ||
|
|
592b37417e | ||
|
|
418e190fa0 | ||
|
|
d42d9d5b29 | ||
|
|
9c89d20e76 | ||
|
|
8bafaa8171 | ||
|
|
92dae18fd4 | ||
|
|
e554660c06 | ||
|
|
342d82a7aa | ||
|
|
3dab2ead93 | ||
|
|
bd238fe59c | ||
|
|
8b3e3a9595 | ||
|
|
2bbdf6c02b | ||
|
|
9c9e6b0bcb | ||
|
|
bb0b160a28 | ||
|
|
ada202e489 | ||
|
|
836f415e50 | ||
|
|
1627b96d2b | ||
|
|
a40c0b3bf8 | ||
|
|
d253c1f099 | ||
|
|
0c107fc2c5 | ||
|
|
018bad4ea8 | ||
|
|
e239b0d32c | ||
|
|
aae246e465 | ||
|
|
d04bc6f774 | ||
|
|
8d4ad26834 | ||
|
|
b6886d222b | ||
|
|
c14b8de18f | ||
|
|
3838bedcbf | ||
|
|
31d9c609b6 | ||
|
|
a1b6c0632d | ||
|
|
1dd94e54c0 | ||
|
|
e24eff6c16 | ||
|
|
c697ffcf91 | ||
|
|
94bc872cdb | ||
|
|
e43bb14998 | ||
|
|
21a969af27 | ||
|
|
084f3b2d22 | ||
|
|
eb0a6b35ee | ||
|
|
bc04845293 | ||
|
|
0a891d8b4a | ||
|
|
6d3b6acc82 | ||
|
|
7ed558c1e7 | ||
|
|
a536865fca | ||
|
|
f8a39147a7 | ||
|
|
46ba1e4db6 | ||
|
|
498c975042 | ||
|
|
848065265c | ||
|
|
f7a8d104ce | ||
|
|
020c2cb3cc | ||
|
|
73afcfbb58 | ||
|
|
ce2f355988 | ||
|
|
5b016b1317 | ||
|
|
c0ffd2369b | ||
|
|
d6733dd43d | ||
|
|
53689f2256 | ||
|
|
f30f68f573 | ||
|
|
dd8f0dd90d | ||
|
|
dee5e136b7 | ||
|
|
4d16cf7b0a | ||
|
|
c6d87cccf3 | ||
|
|
aa3e5111de | ||
|
|
7a29560065 | ||
|
|
7105e36803 |
@@ -1,5 +1,4 @@
|
||||
# clang-format configuration
|
||||
# compatible with clang-format 18
|
||||
|
||||
UseTab: Never
|
||||
ColumnLimit: 100
|
||||
@@ -101,7 +100,7 @@ SortIncludes: true
|
||||
SortUsingDeclarations: Never
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^["<](spdlog|toml\+\+|coraing|cpptrace|flatbuffers)/'
|
||||
- Regex: '^["<](spdlog|toml\+\+|coraing|cpptrace|flatbuffers|kota)/'
|
||||
Priority: 30
|
||||
SortPriority: 31
|
||||
|
||||
@@ -140,3 +139,15 @@ KeepEmptyLines:
|
||||
AtEndOfFile: false
|
||||
AtStartOfBlock: false
|
||||
AtStartOfFile: false
|
||||
|
||||
StatementMacros:
|
||||
- DECO_CFG_START
|
||||
- DECO_CFG
|
||||
- DECO_CFG_END
|
||||
- DecoKV
|
||||
- DecoFlag
|
||||
- DecoComma
|
||||
- DecoInput
|
||||
- DecoPack
|
||||
- DecoKVStyled
|
||||
- DecoMulti
|
||||
|
||||
50
.clang-tidy
Normal file
50
.clang-tidy
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
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
|
||||
268
.claude/CLAUDE.md
Normal file
268
.claude/CLAUDE.md
Normal file
@@ -0,0 +1,268 @@
|
||||
# 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&`.
|
||||
15
.claude/commands/build.md
Normal file
15
.claude/commands/build.md
Normal file
@@ -0,0 +1,15 @@
|
||||
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
|
||||
5
.claude/commands/format.md
Normal file
5
.claude/commands/format.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Format all project source files.
|
||||
|
||||
Run: `pixi run format`
|
||||
|
||||
Formats C++, Python, Lua, JS/TS, Markdown, JSON, TOML, and YAML files.
|
||||
19
.claude/commands/test.md
Normal file
19
.claude/commands/test.md
Normal file
@@ -0,0 +1,19 @@
|
||||
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)
|
||||
7
.coderabbit.yaml
Normal file
7
.coderabbit.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
chat:
|
||||
auto_reply: false
|
||||
reviews:
|
||||
auto_review:
|
||||
enabled: true
|
||||
summary:
|
||||
enabled: false
|
||||
7
.gitattributes
vendored
7
.gitattributes
vendored
@@ -1,2 +1,9 @@
|
||||
# SCM syntax highlighting & preventing 3-way merges
|
||||
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
|
||||
|
||||
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,10 +1,9 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
title: ""
|
||||
labels: ""
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
@@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
@@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/feature_request.md
vendored
7
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,10 +1,9 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
title: ""
|
||||
labels: ""
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
20
.github/actions/setup-pixi/action.yml
vendored
Normal file
20
.github/actions/setup-pixi/action.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: "Setup Pixi"
|
||||
description: "setup pixi"
|
||||
|
||||
inputs:
|
||||
environments:
|
||||
description: "The pixi environments to install (e.g. default, docs, test)"
|
||||
required: false
|
||||
default: "default"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Pixi
|
||||
uses: prefix-dev/setup-pixi@v0.9.3
|
||||
with:
|
||||
pixi-version: v0.67.0
|
||||
environments: ${{ inputs.environments }}
|
||||
activate-environment: true
|
||||
cache: true
|
||||
locked: true
|
||||
44
.github/workflows/benchmark.yml
vendored
Normal file
44
.github/workflows/benchmark.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
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
|
||||
363
.github/workflows/build-llvm.yml
vendored
363
.github/workflows/build-llvm.yml
vendored
@@ -1,6 +1,22 @@
|
||||
name: build llvm
|
||||
|
||||
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:
|
||||
# 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.
|
||||
@@ -12,38 +28,68 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-2025
|
||||
llvm_mode: Debug
|
||||
lto: OFF
|
||||
# Native builds
|
||||
- os: windows-2025
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: OFF
|
||||
- os: windows-2025
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: ON
|
||||
- os: ubuntu-24.04
|
||||
llvm_mode: Debug
|
||||
lto: OFF
|
||||
- os: ubuntu-24.04
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: OFF
|
||||
- os: ubuntu-24.04
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: ON
|
||||
- os: macos-15
|
||||
llvm_mode: Debug
|
||||
lto: OFF
|
||||
- os: macos-15
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: OFF
|
||||
- os: macos-15
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: ON
|
||||
|
||||
- os: ubuntu-24.04
|
||||
llvm_mode: Debug
|
||||
lto: OFF
|
||||
- os: ubuntu-24.04
|
||||
# Cross-compilation builds
|
||||
# macOS x64 (from arm64 macos-15)
|
||||
- os: macos-15
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: OFF
|
||||
- os: ubuntu-24.04
|
||||
target_triple: x86_64-apple-darwin
|
||||
- os: macos-15
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: ON
|
||||
target_triple: x86_64-apple-darwin
|
||||
|
||||
- os: macos-15
|
||||
llvm_mode: Debug
|
||||
lto: OFF
|
||||
- os: macos-15
|
||||
# Linux aarch64 (from x64 ubuntu-24.04)
|
||||
- os: ubuntu-24.04
|
||||
llvm_mode: RelWithDebInfo
|
||||
lto: OFF
|
||||
- os: macos-15
|
||||
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 }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -71,49 +117,91 @@ jobs:
|
||||
free -h
|
||||
df -h
|
||||
|
||||
- name: Setup Pixi
|
||||
uses: prefix-dev/setup-pixi@v0.9.3
|
||||
- uses: ./.github/actions/setup-pixi
|
||||
with:
|
||||
pixi-version: v0.59.0
|
||||
environments: package
|
||||
activate-environment: true
|
||||
cache: true
|
||||
locked: true
|
||||
environments: ${{ matrix.pixi_env || 'package' }}
|
||||
|
||||
- name: Clone llvm-project (21.1.4)
|
||||
- name: Clone llvm-project
|
||||
shell: bash
|
||||
run: |
|
||||
git clone --branch llvmorg-21.1.4 --depth 1 https://github.com/llvm/llvm-project.git .llvm
|
||||
VERSION="${{ inputs.llvm_version || '21.1.8' }}"
|
||||
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)
|
||||
shell: bash
|
||||
run: |
|
||||
pixi run build-llvm --llvm-src=.llvm --mode="${{ matrix.llvm_mode }}" --lto="${{ matrix.lto }}" --build-dir=build
|
||||
ENV="${{ matrix.pixi_env || 'package' }}"
|
||||
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
|
||||
if: ${{ !matrix.target_triple }}
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=${{ matrix.llvm_mode }} \
|
||||
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake \
|
||||
-DCLICE_ENABLE_TEST=ON \
|
||||
-DCLICE_CI_ENVIRONMENT=ON \
|
||||
-DCLICE_ENABLE_LTO=${{ matrix.lto }} \
|
||||
-DLLVM_INSTALL_PATH=".llvm/build-install"
|
||||
cmake --build build
|
||||
pixi run cmake-config ${{ matrix.llvm_mode }} ON -- \
|
||||
"-DCLICE_ENABLE_LTO=${{ matrix.lto }}" \
|
||||
"-DLLVM_INSTALL_PATH=.llvm/build-install"
|
||||
pixi run cmake-build ${{ matrix.llvm_mode }}
|
||||
|
||||
- name: Build clice using installed LLVM (cross-compile)
|
||||
if: ${{ matrix.target_triple }}
|
||||
shell: bash
|
||||
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
|
||||
if: ${{ !matrix.target_triple }}
|
||||
shell: bash
|
||||
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}
|
||||
run: pixi run test ${{ matrix.llvm_mode }}
|
||||
|
||||
# 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)
|
||||
if: matrix.llvm_mode == 'Debug' || (matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF')
|
||||
if: (!matrix.target_triple) && (matrix.llvm_mode == 'Debug' || (matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'))
|
||||
shell: bash
|
||||
run: |
|
||||
MANIFEST="pruned-libs-${{ matrix.os }}.json"
|
||||
@@ -121,13 +209,13 @@ jobs:
|
||||
python3 scripts/prune-llvm-bin.py \
|
||||
--action discover \
|
||||
--install-dir ".llvm/build-install/lib" \
|
||||
--build-dir "build" \
|
||||
--build-dir "build/${{ matrix.llvm_mode }}" \
|
||||
--max-attempts 60 \
|
||||
--sleep-seconds 60 \
|
||||
--manifest "${MANIFEST}"
|
||||
|
||||
- name: Upload pruned-libs manifest
|
||||
if: matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'
|
||||
if: (!matrix.target_triple) && matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'OFF'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: llvm-pruned-libs-${{ matrix.os }}
|
||||
@@ -135,8 +223,8 @@ jobs:
|
||||
if-no-files-found: error
|
||||
compression-level: 0
|
||||
|
||||
- name: Apply pruned-libs manifest (RelWithDebInfo + LTO)
|
||||
if: matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'ON'
|
||||
- name: Apply pruned-libs manifest (RelWithDebInfo + LTO, native only)
|
||||
if: (!matrix.target_triple) && matrix.llvm_mode == 'RelWithDebInfo' && matrix.lto == 'ON'
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -146,7 +234,27 @@ jobs:
|
||||
--action apply \
|
||||
--manifest "${MANIFEST}" \
|
||||
--install-dir ".llvm/build-install/lib" \
|
||||
--build-dir "build" \
|
||||
--build-dir "build/${{ matrix.llvm_mode }}" \
|
||||
--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-artifact "llvm-pruned-libs-${{ matrix.os }}" \
|
||||
--gh-download-dir "artifacts" \
|
||||
@@ -161,23 +269,35 @@ jobs:
|
||||
MODE_TAG="debug"
|
||||
fi
|
||||
|
||||
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"
|
||||
# Determine arch/platform/toolchain from target triple or runner OS
|
||||
if [[ -n "${{ matrix.target_triple }}" ]]; then
|
||||
case "${{ matrix.target_triple }}" in
|
||||
x86_64-apple-darwin)
|
||||
ARCH="x64"; PLATFORM="macos"; TOOLCHAIN="clang" ;;
|
||||
aarch64-linux-gnu)
|
||||
ARCH="aarch64"; PLATFORM="linux"; TOOLCHAIN="gnu" ;;
|
||||
aarch64-pc-windows-msvc)
|
||||
ARCH="aarch64"; PLATFORM="windows"; TOOLCHAIN="msvc" ;;
|
||||
esac
|
||||
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
|
||||
|
||||
SUFFIX=""
|
||||
if [[ "${{ matrix.lto }}" == "ON" ]]; then
|
||||
SUFFIX="-lto"
|
||||
fi
|
||||
if [[ "${{ matrix.llvm_mode }}" == "Debug" ]]; then
|
||||
if [[ "${{ matrix.llvm_mode }}" == "Debug" && "${{ matrix.os }}" != windows-* ]]; then
|
||||
SUFFIX="${SUFFIX}-asan"
|
||||
fi
|
||||
|
||||
@@ -193,3 +313,134 @@ jobs:
|
||||
name: ${{ env.LLVM_INSTALL_ARCHIVE }}
|
||||
path: ${{ env.LLVM_INSTALL_ARCHIVE }}
|
||||
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
|
||||
|
||||
56
.github/workflows/check-format.yml
vendored
56
.github/workflows/check-format.yml
vendored
@@ -1,52 +1,40 @@
|
||||
name: format
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
check-format:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Pixi
|
||||
uses: prefix-dev/setup-pixi@v0.9.3
|
||||
- uses: ./.github/actions/setup-pixi
|
||||
with:
|
||||
pixi-version: v0.61.0
|
||||
environments: develop
|
||||
activate-environment: true
|
||||
cache: true
|
||||
locked: true
|
||||
environments: format
|
||||
|
||||
- name: Run check
|
||||
id: precommit
|
||||
- name: Validate update-llvm-version.py can still patch package.cmake
|
||||
run: |
|
||||
pixi run ci-check
|
||||
python3 scripts/update-llvm-version.py --check \
|
||||
--manifest-dest config/llvm-manifest.json \
|
||||
--package-cmake cmake/package.cmake
|
||||
|
||||
- name: Run formatter
|
||||
run: pixi run format
|
||||
continue-on-error: true
|
||||
|
||||
- name: Show diff on failure
|
||||
if: steps.precommit.outcome == 'failure'
|
||||
run: |
|
||||
echo "❌ pre-commit found issues that it auto-fixed."
|
||||
echo " Please run 'pre-commit run --all-files' locally,"
|
||||
echo " commit the changes, and push again."
|
||||
echo ""
|
||||
echo "👇 The required changes are shown below:"
|
||||
git diff
|
||||
|
||||
- name: Fail the job if pre-commit failed
|
||||
if: steps.precommit.outcome == 'failure'
|
||||
run: |
|
||||
echo "pre-commit checks failed."
|
||||
exit 1
|
||||
|
||||
- name: Auto correct
|
||||
uses: huacnlee/autocorrect-action@main
|
||||
uses: huacnlee/autocorrect-action@v2
|
||||
with:
|
||||
args: --lint ./docs
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check diff
|
||||
run: |
|
||||
if ! git diff --quiet; then
|
||||
echo "::error::Formatting changes detected. Please run 'pixi run format' and commit the result."
|
||||
git --no-pager diff --stat
|
||||
git --no-pager diff
|
||||
exit 1
|
||||
fi
|
||||
|
||||
57
.github/workflows/cmake.yml
vendored
57
.github/workflows/cmake.yml
vendored
@@ -1,57 +0,0 @@
|
||||
name: cmake
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/workflows/cmake.yml"
|
||||
- "include/**"
|
||||
- "src/**"
|
||||
- "tests/**"
|
||||
- "CMakeLists.txt"
|
||||
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/workflows/cmake.yml"
|
||||
- "include/**"
|
||||
- "src/**"
|
||||
- "tests/**"
|
||||
- "config/**"
|
||||
- "CMakeLists.txt"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-2025
|
||||
build_type: RelWithDebInfo
|
||||
- os: ubuntu-24.04
|
||||
build_type: Debug
|
||||
- os: macos-15
|
||||
build_type: Debug
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Pixi
|
||||
uses: prefix-dev/setup-pixi@v0.9.3
|
||||
with:
|
||||
pixi-version: v0.61.0
|
||||
environments: develop
|
||||
activate-environment: true
|
||||
cache: true
|
||||
locked: true
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pixi run ci-cmake-build ${{ matrix.build_type }}
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
pixi run ci-cmake-test
|
||||
30
.github/workflows/deploy-docs.yml
vendored
Normal file
30
.github/workflows/deploy-docs.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: deploy
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/setup-pixi
|
||||
with:
|
||||
environments: node
|
||||
|
||||
- name: Build docs
|
||||
run: pixi run build-docs
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
personal_token: ${{ secrets.PUBLISH_DOCS }}
|
||||
external_repository: clice-io/docs
|
||||
publish_dir: ./docs/.vitepress/dist
|
||||
destination_dir: clice
|
||||
keep_files: true
|
||||
43
.github/workflows/deploy.yml
vendored
43
.github/workflows/deploy.yml
vendored
@@ -1,43 +0,0 @@
|
||||
name: deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/deploy.yml"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: "npm"
|
||||
cache-dependency-path: docs/package-lock.json
|
||||
|
||||
- name: Install dependencies in docs
|
||||
run: |
|
||||
cd docs
|
||||
npm install
|
||||
|
||||
- name: Build docs
|
||||
run: |
|
||||
cd docs
|
||||
npm run docs:build
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: docs/.vitepress/dist
|
||||
cname: clice.io
|
||||
137
.github/workflows/main.yml
vendored
Normal file
137
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
name: main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ["v*"]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
outputs:
|
||||
format: ${{ steps.filter.outputs.format }}
|
||||
docs: ${{ steps.filter.outputs.docs }}
|
||||
clice: ${{ steps.filter.outputs.clice }}
|
||||
vscode: ${{ steps.filter.outputs.vscode }}
|
||||
cmake: ${{ steps.filter.outputs.cmake }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
format:
|
||||
- '**/*.{h,c,cpp,hpp,ts,js,lua,md,yml,yaml}'
|
||||
docs:
|
||||
- 'docs/**'
|
||||
- '.github/workflows/deploy-docs.yml'
|
||||
clice:
|
||||
- 'src/**'
|
||||
- 'include/**'
|
||||
- 'CMakeLists.txt'
|
||||
- '.github/workflows/publish-clice.yml'
|
||||
vscode:
|
||||
- 'editors/vscode/**'
|
||||
- '.github/workflows/publish-vscode.yml'
|
||||
cmake:
|
||||
- 'CMakeLists.txt'
|
||||
- 'src/**'
|
||||
- 'include/**'
|
||||
- 'tests/**'
|
||||
- 'config/**'
|
||||
- '.github/workflows/test-cmake.yml'
|
||||
|
||||
conventional-commit:
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check conventional commit format
|
||||
env:
|
||||
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:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.format == 'true' }}
|
||||
uses: ./.github/workflows/check-format.yml
|
||||
|
||||
deploy:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.docs == 'true' }}
|
||||
permissions:
|
||||
contents: write
|
||||
uses: ./.github/workflows/deploy-docs.yml
|
||||
secrets: inherit
|
||||
|
||||
# clice:
|
||||
# needs: changes
|
||||
# if: ${{ needs.changes.outputs.clice == 'true' }}
|
||||
# uses: ./.github/workflows/publish-clice.yml
|
||||
|
||||
vscode:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.vscode == 'true' }}
|
||||
uses: ./.github/workflows/publish-vscode.yml
|
||||
|
||||
cmake:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.cmake == 'true' }}
|
||||
uses: ./.github/workflows/test-cmake.yml
|
||||
|
||||
release-clice:
|
||||
permissions:
|
||||
contents: write
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: ./.github/workflows/publish-clice.yml
|
||||
secrets: inherit
|
||||
|
||||
release-vscode:
|
||||
permissions:
|
||||
contents: write
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: ./.github/workflows/publish-vscode.yml
|
||||
secrets: inherit
|
||||
|
||||
checks-passed:
|
||||
if: ${{ always() && !startsWith(github.ref, 'refs/tags/') }}
|
||||
needs:
|
||||
- conventional-commit
|
||||
- format
|
||||
- deploy
|
||||
# - clice
|
||||
- vscode
|
||||
- cmake
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check results
|
||||
uses: re-actors/alls-green@release/v1
|
||||
with:
|
||||
allowed-skips: conventional-commit,format,deploy,clice,vscode,cmake
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
97
.github/workflows/package.yml
vendored
97
.github/workflows/package.yml
vendored
@@ -1,97 +0,0 @@
|
||||
name: package
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/workflows/package.yml"
|
||||
|
||||
jobs:
|
||||
package:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-2025
|
||||
artifact_name: clice.zip
|
||||
asset_name: clice-x64-windows-msvc.zip
|
||||
symbol_artifact_name: clice-symbol.zip
|
||||
symbol_asset_name: clice-x64-windows-msvc-symbol.zip
|
||||
toolchain: clang-cl
|
||||
|
||||
- os: ubuntu-24.04
|
||||
artifact_name: clice.tar.gz
|
||||
asset_name: clice-x86_64-linux-gnu.tar.gz
|
||||
symbol_artifact_name: clice-symbol.tar.gz
|
||||
symbol_asset_name: clice-x86_64-linux-gnu-symbol.tar.gz
|
||||
toolchain: clang-20
|
||||
|
||||
- os: macos-15
|
||||
artifact_name: clice.tar.gz
|
||||
asset_name: clice-arm64-macos-darwin.tar.gz
|
||||
symbol_artifact_name: clice-symbol.tar.gz
|
||||
symbol_asset_name: clice-arm64-macos-darwin-symbol.tar.gz
|
||||
toolchain: clang
|
||||
|
||||
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 }}-pkg-release-v1
|
||||
build-cache: true
|
||||
build-cache-key: ${{ matrix.os }}-build-release-v1
|
||||
|
||||
- name: Setup Pixi
|
||||
uses: prefix-dev/setup-pixi@v0.9.3
|
||||
with:
|
||||
pixi-version: v0.61.0
|
||||
activate-environment: true
|
||||
cache: true
|
||||
locked: true
|
||||
|
||||
- name: Remove ci llvm toolchain on Windows
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
# @see https://github.com/xmake-io/xmake/issues/7158
|
||||
xmake lua os.rmdir "C:/Program Files/Microsoft Visual Studio/2022/Enterprise/VC/Tools/Llvm"
|
||||
xmake lua os.rmdir "C:/Program Files/LLVM"
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
pixi run package
|
||||
|
||||
- name: Upload Main Package to Release
|
||||
if: github.event_name == 'push'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: build/xpack/clice/${{ matrix.artifact_name }}
|
||||
asset_name: ${{ matrix.asset_name }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
|
||||
- name: Upload Symbol Package to Release
|
||||
if: github.event_name == 'push'
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: build/xpack/clice/${{ matrix.symbol_artifact_name }}
|
||||
asset_name: ${{ matrix.symbol_asset_name }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
100
.github/workflows/publish-clice.yml
vendored
Normal file
100
.github/workflows/publish-clice.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
name: clice
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
publish-clice:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Native builds
|
||||
- os: windows-2025
|
||||
artifact_name: clice.zip
|
||||
asset_name: clice-x64-windows-msvc.zip
|
||||
symbol_artifact_name: clice-symbol.zip
|
||||
symbol_asset_name: clice-x64-windows-msvc-symbol.zip
|
||||
|
||||
- os: ubuntu-24.04
|
||||
artifact_name: clice.tar.gz
|
||||
asset_name: clice-x86_64-linux-gnu.tar.gz
|
||||
symbol_artifact_name: clice-symbol.tar.gz
|
||||
symbol_asset_name: clice-x86_64-linux-gnu-symbol.tar.gz
|
||||
|
||||
- os: macos-15
|
||||
artifact_name: clice.tar.gz
|
||||
asset_name: clice-arm64-macos-darwin.tar.gz
|
||||
symbol_artifact_name: clice-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 }}
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/setup-pixi
|
||||
with:
|
||||
environments: ${{ matrix.pixi_env || 'package' }}
|
||||
|
||||
- name: Package (native)
|
||||
if: ${{ !matrix.target_triple }}
|
||||
run: pixi run package
|
||||
|
||||
- name: Package (cross-compile)
|
||||
if: ${{ matrix.target_triple }}
|
||||
run: |
|
||||
ENV="${{ matrix.pixi_env }}"
|
||||
pixi run -e "$ENV" package-config -- \
|
||||
"-DCLICE_TARGET_TRIPLE=${{ matrix.target_triple }}"
|
||||
pixi run -e "$ENV" cmake-build
|
||||
|
||||
- name: Upload Main Package to Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: build/RelWithDebInfo/${{ matrix.artifact_name }}
|
||||
asset_name: ${{ matrix.asset_name }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
|
||||
- name: Upload Symbol Package to Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: build/RelWithDebInfo/${{ matrix.symbol_artifact_name }}
|
||||
asset_name: ${{ matrix.symbol_asset_name }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
@@ -1,53 +1,36 @@
|
||||
name: CI/CD VS Code Extension
|
||||
name: vscode
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
branches: ["main"]
|
||||
paths: ["editors/vscode/**"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
paths: ["editors/vscode/**"]
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
publish-vscode:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: editors/vscode
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
- uses: ./.github/actions/setup-pixi
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install vsce
|
||||
run: npm install -g vsce
|
||||
environments: node
|
||||
|
||||
- name: Publish and Package to Marketplace
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCE_PAT }}
|
||||
run: |
|
||||
FLAG="${{ contains(github.ref_name, '-') && '--pre-release' || '' }}"
|
||||
npm run package -- $FLAG
|
||||
pixi run build-vscode $FLAG
|
||||
|
||||
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
|
||||
npm run publish -- -p "$VSCE_PAT" $FLAG
|
||||
pixi run publish-vscode -p "$VSCE_PAT" $FLAG
|
||||
fi
|
||||
|
||||
- name: Upload .vsix to Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
files: "editors/vscode/*.vsix"
|
||||
tag_name: ${{ github.ref }}
|
||||
170
.github/workflows/test-cmake.yml
vendored
Normal file
170
.github/workflows/test-cmake.yml
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
name: cmake
|
||||
|
||||
on:
|
||||
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:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# Native builds
|
||||
- os: windows-2025
|
||||
build_type: RelWithDebInfo
|
||||
- os: ubuntu-24.04
|
||||
build_type: Debug
|
||||
- os: ubuntu-24.04
|
||||
build_type: RelWithDebInfo
|
||||
- os: macos-15
|
||||
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 }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: ./.github/actions/setup-pixi
|
||||
with:
|
||||
environments: ${{ matrix.pixi_env || 'default' }}
|
||||
|
||||
- name: Restore compiler cache
|
||||
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
|
||||
|
||||
- name: Build (cross-compile)
|
||||
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: Unit tests
|
||||
if: ${{ !matrix.build_only }}
|
||||
timeout-minutes: 5
|
||||
run: pixi run unit-test ${{ matrix.build_type }}
|
||||
|
||||
- name: Integration tests
|
||||
if: ${{ !matrix.build_only }}
|
||||
timeout-minutes: 20
|
||||
run: pixi run integration-test ${{ matrix.build_type }}
|
||||
|
||||
- name: Smoke tests
|
||||
if: ${{ !matrix.build_only }}
|
||||
timeout-minutes: 15
|
||||
run: pixi run smoke-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: Unit tests
|
||||
timeout-minutes: 5
|
||||
run: pixi run -e test-run unit-test ${{ matrix.build_type }}
|
||||
|
||||
- name: Integration tests
|
||||
timeout-minutes: 20
|
||||
run: pixi run -e test-run integration-test ${{ matrix.build_type }}
|
||||
|
||||
- name: Smoke tests
|
||||
timeout-minutes: 10
|
||||
run: pixi run -e test-run smoke-test ${{ matrix.build_type }}
|
||||
71
.github/workflows/xmake.yml
vendored
71
.github/workflows/xmake.yml
vendored
@@ -1,71 +0,0 @@
|
||||
name: xmake
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/workflows/xmake.yml"
|
||||
- "include/**"
|
||||
- "src/**"
|
||||
- "tests/**"
|
||||
- "xmake.lua"
|
||||
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/workflows/xmake.yml"
|
||||
- "include/**"
|
||||
- "src/**"
|
||||
- "tests/**"
|
||||
- "config/**"
|
||||
- "xmake.lua"
|
||||
|
||||
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 }}
|
||||
|
||||
- name: Setup Pixi
|
||||
uses: prefix-dev/setup-pixi@v0.9.3
|
||||
with:
|
||||
pixi-version: v0.61.0
|
||||
environments: develop
|
||||
activate-environment: true
|
||||
cache: true
|
||||
locked: true
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
pixi run ci-xmake-build ${{ matrix.build_type }}
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
pixi run xmake-test
|
||||
|
||||
# Clean up llvm package to reduce cache size
|
||||
- name: Remove llvm package (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: xmake require --uninstall clice-llvm
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -35,7 +35,7 @@
|
||||
*build*/
|
||||
temp/
|
||||
.cache/
|
||||
.xmake/
|
||||
|
||||
.llvm*/
|
||||
.clice/
|
||||
compile_commands.json
|
||||
@@ -56,12 +56,19 @@ __pycache__/
|
||||
tests/unit/Local/
|
||||
|
||||
# IDEs & Editors
|
||||
/.vscode/
|
||||
/.vscode/*
|
||||
!/.vscode/launch.json
|
||||
!/.vscode/tasks.json
|
||||
.vs/
|
||||
.idea/
|
||||
.claude
|
||||
.clangd
|
||||
|
||||
# pixi environments
|
||||
.env
|
||||
.pixi/*
|
||||
!.pixi/config.toml
|
||||
|
||||
.codex/
|
||||
.claude/*
|
||||
!.claude/CLAUDE.md
|
||||
!.claude/commands/
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# .pre-commit-config.yaml
|
||||
exclude: "\\.patch$"
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- id: check-merge-conflict
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.12
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v21.1.0
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types_or: [c++, c]
|
||||
|
||||
- repo: https://github.com/JohnnyMorganz/StyLua
|
||||
rev: v2.1.0
|
||||
hooks:
|
||||
- id: stylua-github # or stylua-system / stylua-github
|
||||
files: '(^xmake\.lua$|^editors/nvim/(lua|plugin|test)/.*\.lua$)'
|
||||
23
.prettierrc.yaml
Normal file
23
.prettierrc.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
# .prettierrc.yaml
|
||||
|
||||
printWidth: 100
|
||||
tabWidth: 4
|
||||
useTabs: false
|
||||
semi: true
|
||||
singleQuote: false
|
||||
jsxSingleQuote: false
|
||||
quoteProps: "as-needed"
|
||||
trailingComma: "all"
|
||||
bracketSpacing: true
|
||||
arrowParens: "always"
|
||||
endOfLine: "lf"
|
||||
|
||||
overrides:
|
||||
- files: "*.md"
|
||||
options:
|
||||
proseWrap: "preserve"
|
||||
tabWidth: 2
|
||||
|
||||
- files: ["*.json", "*.yaml", "*.yml", ".clang-format", ".clang-tidy"]
|
||||
options:
|
||||
tabWidth: 2
|
||||
83
.vscode/launch.json
vendored
Normal file
83
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"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
Normal file
42
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
229
CMakeLists.txt
229
CMakeLists.txt
@@ -13,14 +13,45 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib")
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin")
|
||||
|
||||
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)
|
||||
string(APPEND CMAKE_CXX_FLAGS " -stdlib=libc++")
|
||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -stdlib=libc++")
|
||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -stdlib=libc++")
|
||||
endif()
|
||||
|
||||
if(CLICE_RELEASE)
|
||||
set(CLICE_ENABLE_LTO ON)
|
||||
endif()
|
||||
|
||||
if(CLICE_ENABLE_LTO)
|
||||
string(APPEND CMAKE_C_FLAGS " -flto=thin")
|
||||
string(APPEND CMAKE_CXX_FLAGS " -flto=thin")
|
||||
@@ -29,17 +60,12 @@ if(CLICE_ENABLE_LTO)
|
||||
string(APPEND CMAKE_MODULE_LINKER_FLAGS " -flto=thin")
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
add_compile_options(-fsanitize=address)
|
||||
|
||||
if(NOT WIN32)
|
||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
|
||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
|
||||
endif()
|
||||
|
||||
if(MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
# clang-cl (MSVC frontend): manually link ASan runtime since clang-cl
|
||||
# doesn't handle -fsanitize=address linking automatically.
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_CXX_COMPILER} --print-resource-dir
|
||||
OUTPUT_VARIABLE CLANG_RESOURCE_DIR
|
||||
@@ -49,27 +75,47 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
link_directories(${ASAN_LIB_PATH})
|
||||
|
||||
set(ASAN_LINK_FLAGS "")
|
||||
|
||||
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")
|
||||
|
||||
foreach(flag ${ASAN_LINK_FLAGS})
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${flag}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${flag}")
|
||||
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${flag}")
|
||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " ${flag}")
|
||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " ${flag}")
|
||||
string(APPEND CMAKE_MODULE_LINKER_FLAGS " ${flag}")
|
||||
endforeach()
|
||||
else()
|
||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -fsanitize=address")
|
||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -fsanitize=address")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
# https://conda-forge.org/docs/maintainer/knowledge_base/#newer-c-features-with-old-sdk
|
||||
string(APPEND CMAKE_CXX_FLAGS " -D_LIBCPP_DISABLE_AVAILABILITY=1")
|
||||
if(WIN32)
|
||||
# Disable Identical COMDAT Folding in Debug to avoid ASan ODR false positives.
|
||||
string(APPEND CMAKE_EXE_LINKER_FLAGS " -Wl,/OPT:NOICF")
|
||||
string(APPEND CMAKE_SHARED_LINKER_FLAGS " -Wl,/OPT:NOICF")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include("${PROJECT_SOURCE_DIR}/cmake/package.cmake")
|
||||
|
||||
# Project-specific options (not applied to third-party deps).
|
||||
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)
|
||||
target_compile_definitions(clice_options INTERFACE CLICE_ENABLE_TEST=1)
|
||||
endif()
|
||||
@@ -78,113 +124,54 @@ if(CLICE_CI_ENVIRONMENT)
|
||||
target_compile_definitions(clice_options INTERFACE CLICE_CI_ENVIRONMENT=1)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(clice_options INTERFACE version ntdll)
|
||||
endif()
|
||||
set(FBS_SCHEMA_FILE "${PROJECT_SOURCE_DIR}/src/index/schema.fbs")
|
||||
set(GENERATED_HEADER "${PROJECT_BINARY_DIR}/generated/schema_generated.h")
|
||||
|
||||
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
|
||||
)
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
find_program(FLATC_EXECUTABLE flatc REQUIRED)
|
||||
set(FLATC_CMD "${FLATC_EXECUTABLE}")
|
||||
else()
|
||||
target_compile_options(clice_options INTERFACE
|
||||
-fno-rtti
|
||||
-fno-exceptions
|
||||
-Wno-deprecated-declarations
|
||||
-Wno-undefined-inline
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
)
|
||||
set(FLATC_CMD "$<TARGET_FILE:flatc>")
|
||||
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(
|
||||
OUTPUT ${GENERATED_HEADER}
|
||||
COMMAND $<TARGET_FILE:flatc> --cpp -o ${CMAKE_CURRENT_BINARY_DIR}/generated ${FBS_SCHEMA_FILE}
|
||||
DEPENDS ${FBS_SCHEMA_FILE}
|
||||
OUTPUT "${GENERATED_HEADER}"
|
||||
COMMAND ${FLATC_CMD} --cpp -o "${PROJECT_BINARY_DIR}/generated" "${FBS_SCHEMA_FILE}"
|
||||
DEPENDS "${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 DEPENDS "${GENERATED_HEADER}")
|
||||
|
||||
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}
|
||||
)
|
||||
|
||||
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)
|
||||
file(GLOB_RECURSE CLICE_CORE_SOURCES CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/src/*.cpp")
|
||||
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}/include"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/generated"
|
||||
"${PROJECT_SOURCE_DIR}/src"
|
||||
"${PROJECT_BINARY_DIR}/generated"
|
||||
)
|
||||
target_link_libraries(clice-core PUBLIC
|
||||
clice_options
|
||||
libuv::libuv
|
||||
llvm-libs
|
||||
spdlog::spdlog
|
||||
tomlplusplus::tomlplusplus
|
||||
roaring::roaring
|
||||
flatbuffers
|
||||
llvm-libs
|
||||
kota::ipc::lsp
|
||||
kota::codec::toml
|
||||
simdjson::simdjson
|
||||
)
|
||||
|
||||
add_executable(clice "${PROJECT_SOURCE_DIR}/bin/clice.cc")
|
||||
target_link_libraries(clice PRIVATE clice-core)
|
||||
add_executable(clice "${PROJECT_SOURCE_DIR}/src/clice.cc")
|
||||
target_link_libraries(clice PRIVATE clice::core kota::deco)
|
||||
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
message(STATUS "Copying resource directory for development build")
|
||||
file(
|
||||
COPY "${LLVM_INSTALL_PATH}/lib/clang"
|
||||
DESTINATION "${PROJECT_BINARY_DIR}/lib"
|
||||
add_custom_target(copy_clang_resource ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
"${LLVM_INSTALL_PATH}/lib/clang"
|
||||
"${PROJECT_BINARY_DIR}/lib/clang"
|
||||
COMMENT "Copying clang resource directory"
|
||||
)
|
||||
install(
|
||||
DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang"
|
||||
@@ -193,11 +180,35 @@ install(
|
||||
|
||||
if(CLICE_ENABLE_TEST)
|
||||
file(GLOB_RECURSE CLICE_TEST_SOURCES CONFIGURE_DEPENDS
|
||||
"${PROJECT_SOURCE_DIR}/tests/unit/*/*.cpp")
|
||||
add_executable(unit_tests
|
||||
"${CLICE_TEST_SOURCES}"
|
||||
"${PROJECT_SOURCE_DIR}/bin/unit_tests.cc"
|
||||
"${PROJECT_SOURCE_DIR}/tests/unit/*/*_tests.cpp"
|
||||
)
|
||||
target_include_directories(unit_tests PUBLIC "${PROJECT_SOURCE_DIR}")
|
||||
target_link_libraries(unit_tests PRIVATE clice-core cpptrace::cpptrace)
|
||||
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
|
||||
"${PROJECT_SOURCE_DIR}/tests/unit/unit_tests.cc"
|
||||
${CLICE_TEST_SOURCES}
|
||||
${CLICE_TEST_SUPPORT_SOURCES}
|
||||
)
|
||||
target_include_directories(unit_tests PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/src"
|
||||
"${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()
|
||||
|
||||
12
README.md
12
README.md
@@ -5,7 +5,7 @@
|
||||

|
||||
[](https://github.com/clice-io/clice/blob/main/LICENSE)
|
||||
[](https://github.com/clice-io/clice/actions)
|
||||
[](https://clice.io)
|
||||
[](https://docs.clice.io/clice/)
|
||||
[](https://deepwiki.com/clice-io/clice)
|
||||
[](https://discord.gg/PA3UxW2VA3)
|
||||
|
||||
@@ -22,11 +22,11 @@ Download the latest `clice` binary from the [releases page](https://github.com/c
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// Optional: Set this to an empty string to turn off the clangd.
|
||||
"clangd.path": "",
|
||||
// Optional: Set this to an empty string to turn off the clangd.
|
||||
"clangd.path": "",
|
||||
|
||||
// Point this to the clice binary you downloaded.
|
||||
"clice.executable": "/path/to/your/clice/executable",
|
||||
// Point this to the clice binary you downloaded.
|
||||
"clice.executable": "/path/to/your/clice/executable",
|
||||
}
|
||||
```
|
||||
|
||||
@@ -35,4 +35,4 @@ Download the latest `clice` binary from the [releases page](https://github.com/c
|
||||
|
||||
## 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://clice.io/).
|
||||
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/).
|
||||
|
||||
411
benchmarks/scan_benchmark.cpp
Normal file
411
benchmarks/scan_benchmark.cpp
Normal file
@@ -0,0 +1,411 @@
|
||||
/// 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/json.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;
|
||||
}
|
||||
136
bin/clice.cc
136
bin/clice.cc
@@ -1,136 +0,0 @@
|
||||
#include "Server/Server.h"
|
||||
#include "Server/Version.h"
|
||||
#include "Support/Format.h"
|
||||
#include "Support/Logging.h"
|
||||
|
||||
#include "llvm/ADT/StringSwitch.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/InitLLVM.h"
|
||||
#include "llvm/Support/Process.h"
|
||||
|
||||
namespace cl = llvm::cl;
|
||||
using namespace clice;
|
||||
|
||||
namespace {
|
||||
|
||||
static cl::OptionCategory category("clice options");
|
||||
|
||||
enum class Mode {
|
||||
Pipe,
|
||||
Socket,
|
||||
Indexer,
|
||||
};
|
||||
|
||||
cl::opt<Mode> mode{
|
||||
"mode",
|
||||
cl::cat(category),
|
||||
cl::value_desc("string"),
|
||||
cl::init(Mode::Pipe),
|
||||
cl::values(clEnumValN(Mode::Pipe, "pipe", "pipe mode, clice will listen on stdio"),
|
||||
clEnumValN(Mode::Socket, "socket", "socket mode, clice will listen on host:port")),
|
||||
/// clEnumValN(Mode::Indexer, "indexer", "indexer mode, to implement")
|
||||
cl::desc("The mode of clice, default is pipe, socket is usually used for debugging"),
|
||||
};
|
||||
|
||||
cl::opt<std::string> host{
|
||||
"host",
|
||||
cl::cat(category),
|
||||
cl::value_desc("string"),
|
||||
cl::init("127.0.0.1"),
|
||||
cl::desc("The host to connect to (default: 127.0.0.1)"),
|
||||
};
|
||||
|
||||
cl::opt<unsigned int> port{
|
||||
"port",
|
||||
cl::cat(category),
|
||||
cl::value_desc("unsigned int"),
|
||||
cl::init(50051),
|
||||
cl::desc("The port to connect to"),
|
||||
};
|
||||
|
||||
cl::opt<logging::ColorMode> log_color{
|
||||
"log-color",
|
||||
cl::cat(category),
|
||||
cl::value_desc("always|auto|never"),
|
||||
cl::init(logging::ColorMode::automatic),
|
||||
cl::values(clEnumValN(logging::ColorMode::automatic, "auto", ""),
|
||||
clEnumValN(logging::ColorMode::always, "always", ""),
|
||||
clEnumValN(logging::ColorMode::never, "never", "")),
|
||||
cl::desc("When to use terminal colors, default is auto"),
|
||||
};
|
||||
|
||||
cl::opt<logging::Level> log_level{
|
||||
"log-level",
|
||||
cl::cat(category),
|
||||
cl::value_desc("trace|debug|info|warn|error"),
|
||||
cl::init(logging::Level::info),
|
||||
cl::values(clEnumValN(logging::Level::trace, "trace", ""),
|
||||
clEnumValN(logging::Level::debug, "debug", ""),
|
||||
clEnumValN(logging::Level::info, "info", ""),
|
||||
clEnumValN(logging::Level::warn, "warn", ""),
|
||||
clEnumValN(logging::Level::err, "error", ""),
|
||||
clEnumValN(logging::Level::off, "off", "")),
|
||||
cl::desc("The log level, default is info"),
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
llvm::InitLLVM guard(argc, argv);
|
||||
llvm::setBugReportMsg(
|
||||
"Please report bugs to https://github.com/clice-io/clice/issues and include the crash backtrace");
|
||||
cl::SetVersionPrinter([](llvm::raw_ostream& os) {
|
||||
os << std::format("clice version: {}\nllvm version: {}\n",
|
||||
clice::config::version,
|
||||
clice::config::llvm_version);
|
||||
});
|
||||
cl::HideUnrelatedOptions(category);
|
||||
cl::ParseCommandLineOptions(argc,
|
||||
argv,
|
||||
"clice is a new generation of language server for C/C++");
|
||||
|
||||
logging::options.color = log_color;
|
||||
logging::options.level = log_level;
|
||||
logging::stderr_logger("clice", logging::options);
|
||||
|
||||
if(auto result = fs::init_resource_dir(argv[0]); !result) {
|
||||
LOG_FATAL("Cannot find default resource directory, because {}", result.error());
|
||||
}
|
||||
|
||||
for(int i = 0; i < argc; ++i) {
|
||||
LOG_INFO("argv[{}] = {}", i, argv[i]);
|
||||
}
|
||||
|
||||
async::init();
|
||||
|
||||
/// The global server instance.
|
||||
static Server instance;
|
||||
auto loop = [&](json::Value value) -> async::Task<> {
|
||||
co_await instance.on_receive(value);
|
||||
};
|
||||
|
||||
switch(mode) {
|
||||
case Mode::Pipe: {
|
||||
async::net::listen(loop);
|
||||
LOG_INFO("Server starts listening on stdin/stdout");
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::Socket: {
|
||||
async::net::listen(host.c_str(), port, loop);
|
||||
LOG_INFO("Server starts listening on {}:{}", host.getValue(), port.getValue());
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode::Indexer: {
|
||||
/// TODO:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async::run();
|
||||
|
||||
LOG_INFO("clice exit normally!");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
#include "Test/Test.h"
|
||||
#include "Support/GlobPattern.h"
|
||||
#include "Support/Logging.h"
|
||||
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/Signals.h"
|
||||
|
||||
using namespace clice;
|
||||
using namespace clice::testing;
|
||||
|
||||
namespace {
|
||||
|
||||
namespace cl = llvm::cl;
|
||||
|
||||
cl::OptionCategory unittest_category("clice Unittest Options");
|
||||
|
||||
cl::opt<std::string> test_dir{
|
||||
"test-dir",
|
||||
cl::desc("Specify the test source directory path"),
|
||||
cl::value_desc("path"),
|
||||
cl::Required,
|
||||
cl::cat(unittest_category),
|
||||
};
|
||||
|
||||
cl::opt<std::string> test_filter{
|
||||
"test-filter",
|
||||
cl::desc("A glob pattern to run subset of tests"),
|
||||
cl::cat(unittest_category),
|
||||
};
|
||||
|
||||
cl::opt<bool> enable_example{
|
||||
"enable-example",
|
||||
cl::init(false),
|
||||
cl::cat(unittest_category),
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
|
||||
llvm::cl::HideUnrelatedOptions(unittest_category);
|
||||
llvm::cl::ParseCommandLineOptions(argc, argv, "clice test\n");
|
||||
|
||||
logging::stderr_logger("clice", logging::options);
|
||||
|
||||
if(auto result = fs::init_resource_dir(argv[0]); !result) {
|
||||
std::println("Failed to get resource directory, because {}", result.error());
|
||||
return 1;
|
||||
}
|
||||
|
||||
using namespace clice::testing;
|
||||
return Runner2::instance().run_tests(test_filter);
|
||||
}
|
||||
17
cmake/archive.cmake
Normal file
17
cmake/archive.cmake
Normal file
@@ -0,0 +1,17 @@
|
||||
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}")
|
||||
@@ -25,6 +25,22 @@ function(setup_llvm LLVM_VERSION)
|
||||
list(APPEND LLVM_SETUP_ARGS "--offline")
|
||||
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(
|
||||
COMMAND "${Python3_EXECUTABLE}" "${LLVM_SETUP_SCRIPT}" ${LLVM_SETUP_ARGS}
|
||||
RESULT_VARIABLE LLVM_SETUP_RESULT
|
||||
@@ -101,8 +117,15 @@ function(setup_llvm LLVM_VERSION)
|
||||
clangToolingSyntax
|
||||
)
|
||||
else()
|
||||
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/*${CMAKE_STATIC_LIBRARY_SUFFIX}")
|
||||
target_link_libraries(llvm-libs INTERFACE ${LLVM_LIBRARIES})
|
||||
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}LLVM[a-zA-Z]*${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}")
|
||||
# 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)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@@ -1,56 +1,28 @@
|
||||
include_guard()
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/llvm.cmake)
|
||||
setup_llvm("21.1.4+r1")
|
||||
setup_llvm("21.1.8")
|
||||
|
||||
# install dependencies
|
||||
include(FetchContent)
|
||||
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
|
||||
FetchContent_Declare(
|
||||
spdlog
|
||||
GIT_REPOSITORY https://github.com/gabime/spdlog.git
|
||||
GIT_TAG v1.15.3
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
|
||||
# tomlplusplus
|
||||
FetchContent_Declare(
|
||||
tomlplusplus
|
||||
GIT_REPOSITORY https://github.com/marzer/tomlplusplus.git
|
||||
GIT_TAG v3.4.0
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_TAG v1.15.3
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(SPDLOG_USE_STD_FORMAT ON CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
|
||||
# croaring
|
||||
FetchContent_Declare(
|
||||
croaring
|
||||
GIT_REPOSITORY https://github.com/RoaringBitmap/CRoaring.git
|
||||
GIT_TAG v4.4.2
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_TAG v4.4.2
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(ENABLE_ROARING_TESTS OFF CACHE INTERNAL "" FORCE)
|
||||
set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
|
||||
@@ -59,39 +31,26 @@ set(ENABLE_ROARING_MICROBENCHMARKS OFF CACHE INTERNAL "" FORCE)
|
||||
FetchContent_Declare(
|
||||
flatbuffers
|
||||
GIT_REPOSITORY https://github.com/google/flatbuffers.git
|
||||
GIT_TAG v25.9.23
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_TAG v25.9.23
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(FLATBUFFERS_BUILD_GRPC OFF CACHE BOOL "" FORCE)
|
||||
set(FLATBUFFERS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(FLATBUFFERS_BUILD_FLATHASH OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# cpptrace
|
||||
FetchContent_Declare(
|
||||
cpptrace
|
||||
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
|
||||
GIT_TAG v1.0.4
|
||||
GIT_SHALLOW TRUE
|
||||
kotatsu
|
||||
GIT_REPOSITORY https://github.com/clice-io/kotatsu
|
||||
GIT_TAG main
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
set(CPPTRACE_DISABLE_CXX_20_MODULES ON CACHE BOOL "" FORCE)
|
||||
|
||||
FetchContent_MakeAvailable(libuv spdlog tomlplusplus croaring flatbuffers cpptrace)
|
||||
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)
|
||||
|
||||
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
|
||||
)
|
||||
FetchContent_MakeAvailable(kotatsu spdlog croaring flatbuffers)
|
||||
|
||||
78
cmake/release.cmake
Normal file
78
cmake/release.cmake
Normal file
@@ -0,0 +1,78 @@
|
||||
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,44 +1,73 @@
|
||||
cmake_minimum_required(VERSION 3.30)
|
||||
|
||||
if(WIN32)
|
||||
set(CMAKE_C_COMPILER clang-cl CACHE STRING "C compiler")
|
||||
set(CMAKE_CXX_COMPILER clang-cl CACHE STRING "C++ compiler")
|
||||
set(AR_PROGRAM_NAME "llvm-lib")
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "MSVC runtime")
|
||||
|
||||
find_program(LLVM_LLD_LINK_PATH "lld-link")
|
||||
if(LLVM_LLD_LINK_PATH)
|
||||
set(CMAKE_LINKER "${LLVM_LLD_LINK_PATH}" CACHE FILEPATH "Linker")
|
||||
# Cross-compilation support via CLICE_TARGET_TRIPLE.
|
||||
# Examples:
|
||||
# -DCLICE_TARGET_TRIPLE=x86_64-apple-darwin (macOS x64 from arm64)
|
||||
# -DCLICE_TARGET_TRIPLE=aarch64-linux-gnu (Linux arm64 from x64)
|
||||
# -DCLICE_TARGET_TRIPLE=aarch64-pc-windows-msvc (Windows arm64 from x64)
|
||||
if(DEFINED CLICE_TARGET_TRIPLE)
|
||||
if(CLICE_TARGET_TRIPLE MATCHES "^x86_64-apple-darwin")
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE STRING "")
|
||||
elseif(CLICE_TARGET_TRIPLE MATCHES "^aarch64-.*linux")
|
||||
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()
|
||||
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()
|
||||
|
||||
find_program(LLVM_AR_PATH ${AR_PROGRAM_NAME})
|
||||
set(CMAKE_C_COMPILER clang CACHE STRING "")
|
||||
set(CMAKE_CXX_COMPILER clang++ CACHE STRING "")
|
||||
|
||||
find_program(LLVM_AR_PATH "llvm-ar")
|
||||
if(LLVM_AR_PATH)
|
||||
set(CMAKE_AR "${LLVM_AR_PATH}" CACHE FILEPATH "Archiver")
|
||||
set(CMAKE_C_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "C archiver")
|
||||
set(CMAKE_CXX_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "C++ archiver")
|
||||
set(CMAKE_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
||||
set(CMAKE_C_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
||||
set(CMAKE_CXX_COMPILER_AR "${LLVM_AR_PATH}" CACHE FILEPATH "")
|
||||
endif()
|
||||
|
||||
find_program(LLVM_RANLIB_PATH "llvm-ranlib")
|
||||
if(LLVM_RANLIB_PATH)
|
||||
set(CMAKE_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "Ranlib")
|
||||
set(CMAKE_C_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "C ranlib")
|
||||
set(CMAKE_CXX_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "C++ ranlib")
|
||||
set(CMAKE_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
||||
set(CMAKE_C_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
||||
set(CMAKE_CXX_COMPILER_RANLIB "${LLVM_RANLIB_PATH}" CACHE FILEPATH "")
|
||||
endif()
|
||||
|
||||
find_program(LLVM_NM_PATH "llvm-nm")
|
||||
if(LLVM_NM_PATH)
|
||||
set(CMAKE_NM "${LLVM_NM_PATH}" CACHE FILEPATH "Symbol lister")
|
||||
set(CMAKE_NM "${LLVM_NM_PATH}" CACHE FILEPATH "")
|
||||
endif()
|
||||
|
||||
find_program(LLVM_RC_PATH "llvm-rc")
|
||||
if(LLVM_RC_PATH)
|
||||
set(CMAKE_RC_COMPILER "${LLVM_RC_PATH}" CACHE FILEPATH "Resource compiler")
|
||||
set(CMAKE_RC_COMPILER "${LLVM_RC_PATH}" CACHE FILEPATH "")
|
||||
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()
|
||||
|
||||
@@ -1,83 +1,142 @@
|
||||
[
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "arm64-macos-clang-debug-asan.tar.xz",
|
||||
"sha256": "7da4b7d63edefecaf11773e7e701c575140d1a07329bbbb038673b6ee4516ff5",
|
||||
"lto": false,
|
||||
"asan": true,
|
||||
"platform": "macosx",
|
||||
"build_type": "Debug"
|
||||
},
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "arm64-macos-clang-releasedbg-lto.tar.xz",
|
||||
"sha256": "300455b169448f9f01ae95e3bc269f489558a4ca3955e3032171cc75feca0e30",
|
||||
"lto": true,
|
||||
"asan": false,
|
||||
"platform": "macosx",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "arm64-macos-clang-releasedbg.tar.xz",
|
||||
"sha256": "9abfc6cd65b957d734ffb97610a634fb4a66d3fbe0fcfb5a1c9124ef693c1495",
|
||||
"lto": false,
|
||||
"asan": false,
|
||||
"platform": "macosx",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "x64-linux-gnu-debug-asan.tar.xz",
|
||||
"sha256": "c1ad3ec476911596a842ac67dd9c9c9475ce9f0a77b81101d3c801840292e7bc",
|
||||
"lto": false,
|
||||
"asan": true,
|
||||
"platform": "linux",
|
||||
"build_type": "Debug"
|
||||
},
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "x64-linux-gnu-releasedbg-lto.tar.xz",
|
||||
"sha256": "8a869c2184d139dbba704e2d712e7a68336458ad2d70622b3eb906c3e3511e54",
|
||||
"lto": true,
|
||||
"asan": false,
|
||||
"platform": "linux",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "x64-linux-gnu-releasedbg.tar.xz",
|
||||
"sha256": "552bab86f715d4f2c027f07eaaf5b3d6b8e430af0b74b470142f3f00da4feec6",
|
||||
"lto": false,
|
||||
"asan": false,
|
||||
"platform": "linux",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "x64-windows-msvc-debug-asan.tar.xz",
|
||||
"sha256": "093667a493d336c22ff3c604c5f1fea2a7d2c927c1179cec44e9a03726906ac1",
|
||||
"lto": false,
|
||||
"asan": true,
|
||||
"platform": "windows",
|
||||
"build_type": "Debug"
|
||||
},
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "x64-windows-msvc-releasedbg-lto.tar.xz",
|
||||
"sha256": "010539e85621dc3c6ecf359d899feb4075aeca5d0bba6625cdbec0e570e79129",
|
||||
"lto": true,
|
||||
"asan": false,
|
||||
"platform": "windows",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.4+r1",
|
||||
"filename": "x64-windows-msvc-releasedbg.tar.xz",
|
||||
"sha256": "f473c09fbea10053fac00be409d75dc228d4a38bcbc5e4aeb58b56a4b0dde78e",
|
||||
"lto": false,
|
||||
"asan": false,
|
||||
"platform": "windows",
|
||||
"build_type": "RelWithDebInfo"
|
||||
}
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"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",
|
||||
"sha256": "b02d20e4f7294ee33f49a09dfdd765b3b44135e003ef50e3a760aeee39e3f993",
|
||||
"lto": false,
|
||||
"asan": true,
|
||||
"platform": "macosx",
|
||||
"arch": "arm64",
|
||||
"build_type": "Debug"
|
||||
},
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"filename": "arm64-macos-clang-releasedbg-lto.tar.xz",
|
||||
"sha256": "e40c21eb0d0b91d9d4ab31212a5cb01ea46707f5c29839414567857e4147604d",
|
||||
"lto": true,
|
||||
"asan": false,
|
||||
"platform": "macosx",
|
||||
"arch": "arm64",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"filename": "arm64-macos-clang-releasedbg.tar.xz",
|
||||
"sha256": "e1b01de34f0edfd41c118e4981a93afb35556ae369597e864f4a393db623b926",
|
||||
"lto": false,
|
||||
"asan": false,
|
||||
"platform": "macosx",
|
||||
"arch": "arm64",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"filename": "x64-linux-gnu-debug-asan.tar.xz",
|
||||
"sha256": "76bb82d822b5377fb5e0fac8abcfba125142e6a0acc02bb36d1fa1532a268646",
|
||||
"lto": false,
|
||||
"asan": true,
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"build_type": "Debug"
|
||||
},
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"filename": "x64-linux-gnu-releasedbg-lto.tar.xz",
|
||||
"sha256": "32f5edddec1e689124f045b586fb402ae30febc05203af7391b088bc8494cd53",
|
||||
"lto": true,
|
||||
"asan": false,
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"filename": "x64-linux-gnu-releasedbg.tar.xz",
|
||||
"sha256": "8ba3c84f23a2a81a86c54780754a61adf99048aa2ac0dc9b9708d0f842d553de",
|
||||
"lto": false,
|
||||
"asan": false,
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"filename": "x64-macos-clang-releasedbg-lto.tar.xz",
|
||||
"sha256": "97e81d6296896d7237f118f728d05291707b9e4e5791e07ce4be8aee0517505d",
|
||||
"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,
|
||||
"asan": false,
|
||||
"platform": "macosx",
|
||||
"arch": "x64",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"filename": "x64-windows-msvc-releasedbg-lto.tar.xz",
|
||||
"sha256": "16bcf0e4cbc3d2b1204edd619a3837004dacea28eeff0a101c8d0212f936427d",
|
||||
"lto": true,
|
||||
"asan": false,
|
||||
"platform": "windows",
|
||||
"arch": "x64",
|
||||
"build_type": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"version": "21.1.8",
|
||||
"filename": "x64-windows-msvc-releasedbg.tar.xz",
|
||||
"sha256": "81d31fad05e200726c8178314b0b2045c947483dddd8cb974f4c376ae5f441fa",
|
||||
"lto": false,
|
||||
"asan": false,
|
||||
"platform": "windows",
|
||||
"arch": "x64",
|
||||
"build_type": "RelWithDebInfo"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,40 +1,38 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { genSidebar } from './theme/sidebar'
|
||||
import { defineConfig } from "vitepress";
|
||||
import { genSidebar } from "./theme/sidebar";
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: "clice",
|
||||
description: "a powerful and modern C++ language server",
|
||||
cleanUrls: true,
|
||||
base: '/',
|
||||
rewrites: {
|
||||
'en/:rest*': ':rest*',
|
||||
},
|
||||
locales: {
|
||||
root: { label: 'English' },
|
||||
zh: { label: '简体中文' },
|
||||
},
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
],
|
||||
sidebar: {
|
||||
"/zh/": [
|
||||
genSidebar('zh', 'design', { title: 'Design' }),
|
||||
genSidebar('zh', 'dev', { title: 'Development' }),
|
||||
genSidebar('zh', 'guide', { title: 'Guide' }),
|
||||
],
|
||||
"/": [
|
||||
genSidebar('en', 'design', { title: 'Design' }),
|
||||
genSidebar('en', 'dev', { title: 'Development' }),
|
||||
genSidebar('en', 'guide', { title: 'Guide' }),
|
||||
],
|
||||
title: "clice",
|
||||
description: "a powerful and modern C++ language server",
|
||||
cleanUrls: true,
|
||||
base: "/clice/",
|
||||
rewrites: {
|
||||
"en/:rest*": ":rest*",
|
||||
},
|
||||
socialLinks: [
|
||||
{ icon: 'discord', link: 'https://discord.gg/PA3UxW2VA3' },
|
||||
{ icon: 'github', link: 'https://github.com/clice-io/clice' },
|
||||
],
|
||||
outline: 'deep',
|
||||
},
|
||||
})
|
||||
locales: {
|
||||
root: { label: "English" },
|
||||
zh: { label: "简体中文" },
|
||||
},
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
nav: [{ text: "Home", link: "/" }],
|
||||
sidebar: {
|
||||
"/zh/": [
|
||||
genSidebar("zh", "design", { title: "Design" }),
|
||||
genSidebar("zh", "dev", { title: "Development" }),
|
||||
genSidebar("zh", "guide", { title: "Guide" }),
|
||||
],
|
||||
"/": [
|
||||
genSidebar("en", "design", { title: "Design" }),
|
||||
genSidebar("en", "dev", { title: "Development" }),
|
||||
genSidebar("en", "guide", { title: "Guide" }),
|
||||
],
|
||||
},
|
||||
socialLinks: [
|
||||
{ icon: "discord", link: "https://discord.gg/PA3UxW2VA3" },
|
||||
{ icon: "github", link: "https://github.com/clice-io/clice" },
|
||||
],
|
||||
outline: "deep",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// https://vitepress.dev/guide/custom-theme
|
||||
import { h } from 'vue'
|
||||
import type { Theme } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import './style.css'
|
||||
import { h } from "vue";
|
||||
import type { Theme } from "vitepress";
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
import "./style.css";
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout: () => {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
||||
})
|
||||
},
|
||||
enhanceApp({ app, router, siteData }) {
|
||||
// ...
|
||||
}
|
||||
} satisfies Theme
|
||||
extends: DefaultTheme,
|
||||
Layout: () => {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
// https://vitepress.dev/guide/extending-default-theme#layout-slots
|
||||
});
|
||||
},
|
||||
enhanceApp({ app, router, siteData }) {
|
||||
// ...
|
||||
},
|
||||
} satisfies Theme;
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { DefaultTheme } from 'vitepress'
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { DefaultTheme } from "vitepress";
|
||||
|
||||
export const genSidebar = (
|
||||
lang: string,
|
||||
dirPath: string,
|
||||
options: {
|
||||
title: string
|
||||
collapsible?: boolean
|
||||
ignore?: string[]
|
||||
}
|
||||
title: string;
|
||||
collapsible?: boolean;
|
||||
ignore?: string[];
|
||||
},
|
||||
): DefaultTheme.SidebarItem => {
|
||||
const sidebarPath = path.resolve(process.cwd(), lang, dirPath)
|
||||
const ignore = options.ignore || ['index.md']
|
||||
const sidebarPath = path.resolve(process.cwd(), lang, dirPath);
|
||||
const ignore = options.ignore || ["index.md"];
|
||||
|
||||
const files = fs
|
||||
.readdirSync(sidebarPath)
|
||||
.filter((file) => file.endsWith('.md') && !ignore.includes(file))
|
||||
.filter((file) => file.endsWith(".md") && !ignore.includes(file));
|
||||
|
||||
const items = files.map((file) => {
|
||||
const content = fs.readFileSync(path.resolve(sidebarPath, file), 'utf-8')
|
||||
const match = content.match(/^#\s+(.*)/)
|
||||
const title = match ? match[1] : file.replace('.md', '')
|
||||
const content = fs.readFileSync(path.resolve(sidebarPath, file), "utf-8");
|
||||
const match = content.match(/^#\s+(.*)/);
|
||||
const title = match ? match[1] : file.replace(".md", "");
|
||||
|
||||
let prefix = '/';
|
||||
if (lang != 'en') {
|
||||
let prefix = "/";
|
||||
if (lang != "en") {
|
||||
prefix += lang;
|
||||
prefix += '/';
|
||||
prefix += "/";
|
||||
}
|
||||
|
||||
return {
|
||||
text: title,
|
||||
/// Make sure link for en is actually root, beacuse Github Pages
|
||||
/// doesn't support redirect url.
|
||||
link: `${prefix}${dirPath}/${file.replace('.md', '')}`
|
||||
}
|
||||
})
|
||||
link: `${prefix}${dirPath}/${file.replace(".md", "")}`,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
text: options.title,
|
||||
collapsed: options.collapsible || false,
|
||||
items
|
||||
}
|
||||
}
|
||||
items,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -44,30 +44,30 @@
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-c-default-1: var(--vp-c-gray-1);
|
||||
--vp-c-default-2: var(--vp-c-gray-2);
|
||||
--vp-c-default-3: var(--vp-c-gray-3);
|
||||
--vp-c-default-soft: var(--vp-c-gray-soft);
|
||||
--vp-c-default-1: var(--vp-c-gray-1);
|
||||
--vp-c-default-2: var(--vp-c-gray-2);
|
||||
--vp-c-default-3: var(--vp-c-gray-3);
|
||||
--vp-c-default-soft: var(--vp-c-gray-soft);
|
||||
|
||||
--vp-c-brand-1: var(--vp-c-indigo-1);
|
||||
--vp-c-brand-2: var(--vp-c-indigo-2);
|
||||
--vp-c-brand-3: var(--vp-c-indigo-3);
|
||||
--vp-c-brand-soft: var(--vp-c-indigo-soft);
|
||||
--vp-c-brand-1: var(--vp-c-indigo-1);
|
||||
--vp-c-brand-2: var(--vp-c-indigo-2);
|
||||
--vp-c-brand-3: var(--vp-c-indigo-3);
|
||||
--vp-c-brand-soft: var(--vp-c-indigo-soft);
|
||||
|
||||
--vp-c-tip-1: var(--vp-c-brand-1);
|
||||
--vp-c-tip-2: var(--vp-c-brand-2);
|
||||
--vp-c-tip-3: var(--vp-c-brand-3);
|
||||
--vp-c-tip-soft: var(--vp-c-brand-soft);
|
||||
--vp-c-tip-1: var(--vp-c-brand-1);
|
||||
--vp-c-tip-2: var(--vp-c-brand-2);
|
||||
--vp-c-tip-3: var(--vp-c-brand-3);
|
||||
--vp-c-tip-soft: var(--vp-c-brand-soft);
|
||||
|
||||
--vp-c-warning-1: var(--vp-c-yellow-1);
|
||||
--vp-c-warning-2: var(--vp-c-yellow-2);
|
||||
--vp-c-warning-3: var(--vp-c-yellow-3);
|
||||
--vp-c-warning-soft: var(--vp-c-yellow-soft);
|
||||
--vp-c-warning-1: var(--vp-c-yellow-1);
|
||||
--vp-c-warning-2: var(--vp-c-yellow-2);
|
||||
--vp-c-warning-3: var(--vp-c-yellow-3);
|
||||
--vp-c-warning-soft: var(--vp-c-yellow-soft);
|
||||
|
||||
--vp-c-danger-1: var(--vp-c-red-1);
|
||||
--vp-c-danger-2: var(--vp-c-red-2);
|
||||
--vp-c-danger-3: var(--vp-c-red-3);
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
--vp-c-danger-1: var(--vp-c-red-1);
|
||||
--vp-c-danger-2: var(--vp-c-red-2);
|
||||
--vp-c-danger-3: var(--vp-c-red-3);
|
||||
--vp-c-danger-soft: var(--vp-c-red-soft);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,15 +75,15 @@
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||
--vp-button-brand-border: transparent;
|
||||
--vp-button-brand-text: var(--vp-c-white);
|
||||
--vp-button-brand-bg: var(--vp-c-brand-3);
|
||||
--vp-button-brand-hover-border: transparent;
|
||||
--vp-button-brand-hover-text: var(--vp-c-white);
|
||||
--vp-button-brand-hover-bg: var(--vp-c-brand-2);
|
||||
--vp-button-brand-active-border: transparent;
|
||||
--vp-button-brand-active-text: var(--vp-c-white);
|
||||
--vp-button-brand-active-bg: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,31 +91,23 @@
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(
|
||||
120deg,
|
||||
#bd34fe 30%,
|
||||
#41d1ff
|
||||
);
|
||||
--vp-home-hero-name-color: transparent;
|
||||
--vp-home-hero-name-background: -webkit-linear-gradient(120deg, #bd34fe 30%, #41d1ff);
|
||||
|
||||
--vp-home-hero-image-background-image: linear-gradient(
|
||||
-45deg,
|
||||
#bd34fe 50%,
|
||||
#47caff 50%
|
||||
);
|
||||
--vp-home-hero-image-filter: blur(44px);
|
||||
--vp-home-hero-image-background-image: linear-gradient(-45deg, #bd34fe 50%, #47caff 50%);
|
||||
--vp-home-hero-image-filter: blur(44px);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px);
|
||||
}
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(56px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(68px);
|
||||
}
|
||||
:root {
|
||||
--vp-home-hero-image-filter: blur(68px);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,10 +115,10 @@
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
--vp-custom-block-tip-border: transparent;
|
||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||
--vp-custom-block-tip-border: transparent;
|
||||
--vp-custom-block-tip-text: var(--vp-c-text-1);
|
||||
--vp-custom-block-tip-bg: var(--vp-c-brand-soft);
|
||||
--vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,5 +126,5 @@
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
.DocSearch {
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
--docsearch-primary-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
### clice configuration
|
||||
## # clice configuration
|
||||
|
||||
# This section outlines the supported built-in variables for clice.
|
||||
# These variables can be referenced in strings using the syntax `${var}`.
|
||||
@@ -9,47 +9,39 @@
|
||||
# - `${workspace}`: The workspace directory provided by the client.
|
||||
|
||||
[project]
|
||||
# Enable experimental clang-tidy diagnostics.
|
||||
# This feature is tracked in https://github.com/clice-project/clice/issues/90.
|
||||
clang_tidy = false
|
||||
|
||||
# Maximum number of active files to keep in memory. If the number of active files
|
||||
# exceeds this limit, the least recently used files will be removed.
|
||||
# The default value is 8. Whatever the number you set, the minimum is 1, the maximum is 512.
|
||||
max_active_file = 8
|
||||
|
||||
# Directory for storing PCH and PCM files.
|
||||
cache_dir = "${workspace}/.clice/cache"
|
||||
|
||||
# Directory for storing index files.
|
||||
index_dir = "${workspace}/.clice/index"
|
||||
|
||||
logging_dir = "${workspace}/.clice/logging"
|
||||
|
||||
# Compile commands directories to search for compile_commands.json files.
|
||||
compile_commands_dirs = ["${workspace}/build"]
|
||||
|
||||
# Enable experimental clang-tidy diagnostics.
|
||||
# This feature is tracked in https://github.com/clice-project/clice/issues/90.
|
||||
clang_tidy = false
|
||||
# Maximum number of active files to keep in memory. If the number of active files
|
||||
# exceeds this limit, the least recently used files will be removed.
|
||||
# The default value is 8. Whatever the number you set, the minimum is 1, the maximum is 512.
|
||||
max_active_file = 8
|
||||
# Directory for storing PCH and PCM files.
|
||||
cache_dir = "${workspace}/.clice/cache"
|
||||
# Directory for storing index files.
|
||||
index_dir = "${workspace}/.clice/index"
|
||||
logging_dir = "${workspace}/.clice/logs"
|
||||
# Compile commands files or directories to search for compile_commands.json files.
|
||||
compile_commands_paths = ["${workspace}/build"]
|
||||
|
||||
# Control the behavior for specific files. Note that Clice matches rules
|
||||
# in order. If you want to add your own rules, either delete this rule
|
||||
# or insert your rule before it.
|
||||
[[rules]]
|
||||
# Files matching the specified pattern will have this rule applied.
|
||||
#
|
||||
# Patterns can use the following syntax:
|
||||
# - `*`: Matches one or more characters in a path segment.
|
||||
# - `?`: Matches a single character in a path segment.
|
||||
# - `**`: Matches any number of path segments, including none.
|
||||
# - `{}`: Groups conditions (e.g., `**/*.{ts,js}` matches all TypeScript
|
||||
# and JavaScript files).
|
||||
# - `[]`: Declares a range of characters to match in a path segment
|
||||
# (e.g., `example.[0-9]` matches `example.0`, `example.1`, etc.).
|
||||
# - `[!...]`: Negates a range of characters to match in a path segment
|
||||
# (e.g., `example.[!0-9]` matches `example.a`, `example.b`, but not `example.0`).
|
||||
patterns = ["**/*"]
|
||||
|
||||
# Commands to append to the original command list (e.g., ["-std=c++17"]).
|
||||
append = []
|
||||
|
||||
# Commands to remove from the original command list.
|
||||
remove = []
|
||||
# Files matching the specified pattern will have this rule applied.
|
||||
#
|
||||
# Patterns can use the following syntax:
|
||||
# - `*`: Matches one or more characters in a path segment.
|
||||
# - `?`: Matches a single character in a path segment.
|
||||
# - `**`: Matches any number of path segments, including none.
|
||||
# - `{}`: Groups conditions (e.g., `**/*.{ts,js}` matches all TypeScript
|
||||
# and JavaScript files).
|
||||
# - `[]`: Declares a range of characters to match in a path segment
|
||||
# (e.g., `example.[0-9]` matches `example.0`, `example.1`, etc.).
|
||||
# - `[!...]`: Negates a range of characters to match in a path segment
|
||||
# (e.g., `example.[!0-9]` matches `example.a`, `example.b`, but not `example.0`).
|
||||
patterns = ["**/*"]
|
||||
# Commands to append to the original command list (e.g., ["-std=c++17"]).
|
||||
append = []
|
||||
# Commands to remove from the original command list.
|
||||
remove = []
|
||||
|
||||
183
docs/en/architecture.md
Normal file
183
docs/en/architecture.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 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 |
|
||||
@@ -1,82 +1,73 @@
|
||||
# Build from Source
|
||||
|
||||
## Supported Platforms
|
||||
clice depends on C++23 features and requires a modern C++ toolchain. We also need to link against LLVM/Clang to parse ASTs. To speed up builds, the default configuration downloads our published [clice-llvm](https://github.com/clice-io/clice-llvm) prebuilt package. This assumes your local environment matches the prebuilt environment closely (especially when enabling Address Sanitizer or LTO).
|
||||
|
||||
- Windows
|
||||
- Linux
|
||||
- MacOS
|
||||
To simplify setup and keep builds reproducible, we **strongly recommend** [pixi](https://pixi.prefix.dev/latest) to manage the development environment. Dependency versions are pinned in `pixi.toml`.
|
||||
|
||||
## Prerequisite
|
||||
If you prefer not to use pixi, see [Manual Build](#manual-build) below.
|
||||
|
||||
- cmake/xmake
|
||||
- clang, lld >= 20
|
||||
- c++23 **compatible** standard library
|
||||
- MSVC STL >= 19.44(VS 2022 17.4)
|
||||
- GCC libstdc++ >= 14
|
||||
- Clang libc++ >= 20
|
||||
## Quick Start
|
||||
|
||||
clice uses C++23 as its language standard. Please ensure you have a clang 20 (or higher) compiler and a C++23 compatible standard library available. clice depends on lld as its linker. Please ensure your clang toolchain can find it (clang distributions usually bundle lld, or you may need to install the lld-20 package separately).
|
||||
Install pixi following the [official guide](https://pixi.prefix.dev/latest/installation).
|
||||
|
||||
> clice is currently only guaranteed to compile with clang (as ensured by CI testing). We do our best to maintain compatibility with gcc and msvc, but we do not add corresponding tests in CI. Contributions are welcome if you encounter any issues.
|
||||
|
||||
## CMake
|
||||
|
||||
Use the following commands to build clice
|
||||
We ship several tasks; the commands below configure, build, and run tests:
|
||||
|
||||
```shell
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
cmake --build build
|
||||
# configure && build (default RelWithDebInfo)
|
||||
pixi run build
|
||||
|
||||
# unit && integration
|
||||
pixi run test
|
||||
```
|
||||
|
||||
For finer-grained tasks (first argument sets the build type):
|
||||
|
||||
```shell
|
||||
pixi run cmake-config Debug
|
||||
pixi run cmake-build Debug
|
||||
pixi run unit-test Debug
|
||||
pixi run integration-test Debug
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> If you want to develop directly with `cmake`, `ninja`, `clang++`, etc., run `pixi shell` to enter a shell with all env vars configured.
|
||||
|
||||
## Manual Build
|
||||
|
||||
If you plan to build manually, first ensure your toolchain matches the versions defined in `pixi.toml`.
|
||||
|
||||
> Compatibility: In theory clice does not rely on compiler-specific extensions, so mainstream compilers (GCC/Clang/MSVC) should work. However, CI only guarantees specific versions of Clang. Other compilers or versions are supported on a **best-effort** basis. Please open an issue or PR if you hit problems.
|
||||
|
||||
### CMake
|
||||
|
||||
```shell
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake \
|
||||
-DCLICE_ENABLE_TEST=ON
|
||||
```
|
||||
|
||||
> Note: `CMAKE_TOOLCHAIN_FILE` is optional. If your toolchain exactly matches ours, you can use the predefined `cmake/toolchain.cmake`; otherwise remove that flag.
|
||||
|
||||
Optional build options:
|
||||
|
||||
| Option | Default | Description |
|
||||
| :------------------- | :------ | :----------------------------------------------------------------------------------------------------------------------------- |
|
||||
| LLVM_INSTALL_PATH | "" | Build clice using llvm libs from a custom path |
|
||||
| CLICE_ENABLE_TEST | OFF | Whether to build clice's unit tests |
|
||||
| CLICE_USE_LIBCXX | OFF | Whether to build clice with libc++ (adds `-std=libc++`). If enabled, ensure that the llvm libs were also compiled with libc++. |
|
||||
| CLICE_CI_ENVIRONMENT | OFF | Whether to enable the `CLICE_CI_ENVIRONMENT` macro. Some tests only run in a CI environment. |
|
||||
| Option | Default | Effect |
|
||||
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| LLVM_INSTALL_PATH | "" | Build clice with LLVM from a custom path |
|
||||
| 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_CI_ENVIRONMENT | OFF | Enable the `CLICE_CI_ENVIRONMENT` macro; some tests only run in CI |
|
||||
|
||||
## XMake
|
||||
## About LLVM
|
||||
|
||||
Use the following commands to build clice
|
||||
clice calls Clang APIs to parse C++ code, so it must link against LLVM/Clang. Because clice uses Clang's private headers (usually absent from distro packages), the system LLVM package cannot be used directly.
|
||||
|
||||
```bash
|
||||
xmake f -c --mode=releasedbg --toolchain=clang
|
||||
xmake build --all
|
||||
```
|
||||
Two ways to satisfy this dependency:
|
||||
|
||||
Optional build options:
|
||||
|
||||
| Option | Default | Description |
|
||||
| :------------ | :------ | :--------------------------------------------- |
|
||||
| --llvm | "" | Build clice using llvm libs from a custom path |
|
||||
| --enable_test | false | Whether to build clice's unit tests |
|
||||
| --ci | false | Whether to enable `CLICE_CI_ENVIRONMENT` |
|
||||
|
||||
## A Note on LLVM Libs
|
||||
|
||||
Due to the complexity of C++ syntax, writing a new parser from scratch is unrealistic. clice calls clang's APIs to parse C++ source files and obtain the AST, which means it needs to link against llvm/clang libs. Because clice uses clang's private headers, which are not included in the binary releases published by LLVM, you cannot use the system's llvm package directly.
|
||||
|
||||
1. We publish pre-compiled binaries for the LLVM version we use on [clice-llvm](https://github.com/clice-io/clice-llvm/releases), which are used for CI or release builds. By default, cmake and xmake will download and use the llvm libs from here during the build.
|
||||
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.
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> For debug builds of llvm libs, we enable address sanitizer. Address sanitizer depends on compiler-rt, which is highly sensitive to the compiler version.
|
||||
>
|
||||
> Therefore, if you use a debug build, please ensure your clang's compiler-rt version is **strictly identical** to the one used in our build.
|
||||
>
|
||||
> - Windows does not currently have debug builds for llvm libs, as it does not support building clang as a dynamic library. Related progress is tracked [here](https://github.com/clice-io/clice/issues/42).
|
||||
> - Linux uses clang20
|
||||
> - MacOS uses homebrew llvm@20. **Do not use apple clang**.
|
||||
>
|
||||
> You can refer to the [cmake](https://github.com/clice-io/clice/blob/main/.github/workflows/cmake.yml) and [xmake](https://github.com/clice-io/clice/blob/main/.github/workflows/xmake.yml) files in our CI as a reference, as they maintain an environment strictly consistent with the pre-compiled llvm libs.
|
||||
> For debug LLVM builds, we enable address sanitizer, which depends on compiler-rt and is very sensitive to compiler version. If you use a debug build, ensure your clang compiler-rt version matches the one defined in `pixi.toml`.
|
||||
|
||||
2. Build llvm/clang yourself to match your current environment. If the default pre-compiled binaries (Method 1) fail to run on your system due to ABI or library version (e.g., glibc) incompatibility, or if you need a custom Debug build, we recommend you use this method to compile llvm libs from scratch. We provide a script to build the llvm libs required by clice: [build-llvm-libs.py](https://github.com/clice-io/clice/blob/main/scripts/build-llvm-libs.py).
|
||||
|
||||
```bash
|
||||
cd llvm-project
|
||||
python3 <clice>/scripts/build-llvm-libs.py debug
|
||||
```
|
||||
|
||||
You can also refer to LLVM's official build tutorial: [Building LLVM with CMake](https://llvm.org/docs/CMake.html).
|
||||
2. Build LLVM/Clang yourself to match your environment. If the default prebuilt binaries fail due to ABI or library version mismatches, or you need a custom debug build, use this approach. We provide `scripts/build-llvm.py` to build the required LLVM libs, or refer to LLVM's official guide [Building LLVM with CMake](https://llvm.org/docs/CMake.html).
|
||||
|
||||
@@ -7,5 +7,6 @@ Please refer to [build](./build.md) to build clice, refer to [test and debug](./
|
||||
## Code Style
|
||||
|
||||
Naming:
|
||||
|
||||
- Variable names: lowercase with underscores
|
||||
- Class names, enum names: PascalCase
|
||||
|
||||
70
docs/en/dev/extension.md
Normal file
70
docs/en/dev/extension.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Extension
|
||||
|
||||
This section covers development and release workflows for the editor extensions (VSCode / Neovim / Zed).
|
||||
|
||||
## VSCode
|
||||
|
||||
The VSCode extension uses the Node/PNPM/VSCE toolchain. Work inside the pixi `node` environment for consistent versions.
|
||||
|
||||
```shell
|
||||
# prepare environment (install pixi first)
|
||||
pixi shell -e node
|
||||
|
||||
# install deps (uses pnpm-lock)
|
||||
pixi run install-vscode
|
||||
|
||||
# package the extension; outputs editors/vscode/*.vsix
|
||||
pixi run build-vscode
|
||||
```
|
||||
|
||||
Publish to the VSCode Marketplace (`VSCE_PAT` env var required):
|
||||
|
||||
```shell
|
||||
pixi run publish-vscode
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> If clice is already built locally, set `clice.executable` in VSCode settings to point the extension to your custom binary.
|
||||
|
||||
Develop and debug:
|
||||
|
||||
1. `pixi shell -e node`
|
||||
2. In `editors/vscode`, run `pnpm run watch` for incremental builds
|
||||
3. In VSCode, use the “Run Extension/Launch Extension” configs, or run `code --extensionDevelopmentPath=$(pwd)/editors/vscode`
|
||||
|
||||
Common scripts (inside `pixi shell -e node`):
|
||||
|
||||
```bash
|
||||
pnpm run package # same as pixi run build-vscode
|
||||
pnpm run publish # same as pixi run publish-vscode
|
||||
```
|
||||
|
||||
If you skip pixi, install node.js >= 20 and pnpm yourself, then in `editors/vscode` run:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm run package
|
||||
```
|
||||
|
||||
## Neovim
|
||||
|
||||
The Neovim extension lives in `editors/nvim` and is written in Lua. It is still evolving.
|
||||
|
||||
- Add the repo path to `runtimepath`, e.g. `set rtp+=/path/to/clice/editors/nvim`
|
||||
- Or create a local symlink: `~/.config/nvim/pack/clice/start/clice` -> `<repo>/editors/nvim`
|
||||
- Ensure the `clice` executable is discoverable in `$PATH`
|
||||
|
||||
Dev tips: the codebase is small—load it directly in Neovim and watch `:messages`/LSP logs; format with `stylua` (config included).
|
||||
|
||||
## Zed
|
||||
|
||||
The Zed extension lives in `editors/zed` and uses Rust plus `zed_extension_api`.
|
||||
|
||||
Suggested local verification:
|
||||
|
||||
```bash
|
||||
cd editors/zed
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Then load the local extension per Zed's official guide (Zed CLI required). Make sure `clice` is on `PATH` before launching. Follow the Zed extension publishing flow when releasing.
|
||||
@@ -18,13 +18,6 @@ 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
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
@@ -53,24 +46,24 @@ You can also connect to a running clice service by configuring the clice-vscode
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// Point this to the clice binary you downloaded.
|
||||
"clice.executable": "/path/to/your/clice/executable",
|
||||
// Point this to the clice binary you downloaded.
|
||||
"clice.executable": "/path/to/your/clice/executable",
|
||||
|
||||
// Enable socket mode.
|
||||
"clice.mode": "socket",
|
||||
"clice.port": 50051,
|
||||
// Enable socket mode.
|
||||
"clice.mode": "socket",
|
||||
"clice.port": 50051,
|
||||
|
||||
// Optional: Set this to an empty string to turn off the clangd.
|
||||
"clangd.path": "",
|
||||
// Optional: Set this to an empty string to turn off the clangd.
|
||||
"clangd.path": "",
|
||||
}
|
||||
```
|
||||
|
||||
3. Reload Window: After modifying the configuration, execute the `Developer: Reload Window` command in VS Code for the settings to take effect. The extension will automatically connect to the clice instance listening on port 50051.
|
||||
|
||||
|
||||
If you need to modify or debug the clice-vscode extension itself, follow these steps:
|
||||
|
||||
1. Clone and install dependencies:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/clice-io/clice-vscode
|
||||
$ cd clice-vscode
|
||||
|
||||
@@ -35,7 +35,7 @@ Glob patterns for matching file paths, following LSP's [standard](https://micros
|
||||
- `{}`: Used for grouping conditions (e.g., `**/*.{ts,js}` matches all TypeScript and JavaScript files).
|
||||
- `[]`: Declares a character range to match in a path segment (e.g., `example.[0-9]` matches `example.0`, `example.1`, etc.).
|
||||
- `[!...]`: Excludes a character range to match in a path segment (e.g., `example.[!0-9]` matches `example.a`, `example.b`, but not `example.0`).
|
||||
<br>
|
||||
<br>
|
||||
|
||||
| Name | Type | Default |
|
||||
| ---------------- | ------------------- | ------- |
|
||||
|
||||
@@ -54,14 +54,73 @@ bazel run @hedron_compile_commands//:refresh_all
|
||||
|
||||
### Visual Studio
|
||||
|
||||
TODO:
|
||||
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`:
|
||||
|
||||
```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
|
||||
|
||||
TODO:
|
||||
For Makefile-based projects, use [bear](https://github.com/rizsotto/Bear) to intercept compilation commands:
|
||||
|
||||
```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
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -16,8 +16,8 @@ hero:
|
||||
text: Contribution
|
||||
link: /dev/contribution
|
||||
image:
|
||||
src: /image.png
|
||||
alt: clice
|
||||
src: /image.png
|
||||
alt: clice
|
||||
|
||||
features:
|
||||
- icon: T
|
||||
|
||||
2239
docs/package-lock.json
generated
2239
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/node": "^24.10.4",
|
||||
"vitepress": "^1.6.4"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -11,9 +11,12 @@
|
||||
"docs:build": "vitepress build",
|
||||
"docs:preview": "vitepress preview"
|
||||
},
|
||||
"overrides": {
|
||||
"vite": {
|
||||
"esbuild": "0.25.9"
|
||||
}
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"esbuild": "~0.25.0"
|
||||
},
|
||||
"ignoredBuiltDependencies": [
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1629
docs/pnpm-lock.yaml
generated
Normal file
1629
docs/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,5 +16,4 @@ int main () {
|
||||
|
||||
`iostream` 这个头文件大概有 2w 行代码,clice 会先把 `#include <iostream>` 这一行代码构建成 PCH 文件,在完成之后在使用这个 PCH 文件来解析后面的代码。这样的话后续重新解析的代码量就只剩 5 行了,而不是原本的 2w 行,速度会变得非常快。除非你修改了 preamble 部分的代码,导致需要构建新的 preamble。
|
||||
|
||||
|
||||
## Cancel Compilation
|
||||
|
||||
@@ -28,5 +28,4 @@ void foo(std::vector<std::vector<T>> vec2) {
|
||||
2. 它只进行名称查找而不进行模板实例化,就算找到了最后的结果,也没法把它和最初的模板参数映射起来
|
||||
3. 不考虑默认模板参数,无法处理由默认模板参数导致的依赖名
|
||||
|
||||
|
||||
尽管我们可以对标准库的类型开洞来提供相关的支持,但是我希望用户的代码能和标准库的代码有相同的地位,那么我们就需要一种通用的算法来处理依赖类型。为了解决这个问题,我编写了一个伪实例化器(pseudo instantiator)。它能在没有具体类型的前提下对依赖类型进行实例化,从而达到化简的目的。比如上面这个例子里面的`std::vector<std::vector<T>>::reference`就能被化简为`std::vector<T>&`,进一步就能为用户提供代码补全选项。
|
||||
|
||||
@@ -1,81 +1,73 @@
|
||||
# Build from Source
|
||||
|
||||
## Supported Platforms
|
||||
clice 依赖 C++23 特性,需要使用高版本的 C++ 编译器。同时,我们需要链接 LLVM/Clang 库来解析 AST。为了加快构建速度,默认配置会下载我们发布的 [clice-llvm](https://github.com/clice-io/clice-llvm) 预编译包。这要求你的本地环境与预编译环境保持较高的一致性(尤其是开启 Address Sanitizer 或 LTO 时)。
|
||||
|
||||
- Windows
|
||||
- Linux
|
||||
- MacOS
|
||||
为了简化环境设置并保证可复现性,我们**强烈推荐**使用 [pixi](https://pixi.prefix.dev/latest) 来管理开发环境。所有的依赖版本均严格定义在 `pixi.toml` 中。
|
||||
|
||||
## Prerequisite
|
||||
如果你不想使用 pixi,请参考下方的 [Manual Build](#manual-build) 章节。
|
||||
|
||||
- cmake/xmake
|
||||
- clang, lld >= 20
|
||||
- c++23 **compatible** standard library
|
||||
- MSVC STL >= 19.44(VS 2022 17.4)
|
||||
- GCC libstdc++ >= 14
|
||||
- Clang libc++ >= 20
|
||||
## Quick Start
|
||||
|
||||
clice 使用 C++23 作为语言标准,请确保有可用的 clang 20 以及以上的编译器,以及兼容 C++23 的标准库。clice 依赖 lld 作为链接器。请确保你的 clang 工具链可以找到它(通常 clang 发行版会自带 lld,或者你需要单独安装 lld-20 包)。
|
||||
请参考 [pixi](https://pixi.prefix.dev/latest/installation) 官方指南安装 pixi。
|
||||
|
||||
> clice 目前只保证能使用 clang 编译(CI 测试保证)。对于 gcc 和 msvc 的兼容,我们尽力而为,但不会在 CI 中添加对应的测试。如果遇到任何问题,欢迎贡献。
|
||||
|
||||
## CMake
|
||||
|
||||
使用如下的命令构建 clice
|
||||
我们内置了一系列任务,以下命令可直接完成编译并运行测试:
|
||||
|
||||
```shell
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
|
||||
cmake --build build
|
||||
# configure && build (default RelWithDebInfo)
|
||||
pixi run build
|
||||
|
||||
# unit && integration
|
||||
pixi run test
|
||||
```
|
||||
|
||||
细粒度任务:上述命令由多个子任务组成,你也可以单独运行它们,并支持通过第一个参数指定构建类型:
|
||||
|
||||
```shell
|
||||
pixi run cmake-config Debug
|
||||
pixi run cmake-build Debug
|
||||
pixi run unit-test Debug
|
||||
pixi run integration-test Debug
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> 如果你想直接使用 `cmake`, `ninja`, `clang++` 等命令进行开发,请运行 `pixi shell` 进入已配置好环境变量的终端
|
||||
|
||||
## Manual Build
|
||||
|
||||
如果你打算手动构建,请务必先确认你的工具链满足 pixi.toml 中定义的版本要求。
|
||||
|
||||
> 兼容性说明:理论上 clice 不依赖特定编译器的扩展,可以使用主流编译器(GCC/Clang/MSVC)编译。但我们仅在 CI 中保证特定版本的 Clang 能通过测试。对于其他编译器或版本,我们提供**尽力而为 (Best Effort)** 的支持。如果遇到问题,欢迎提交 Issue 或 PR
|
||||
|
||||
### CMake
|
||||
|
||||
```shell
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake \
|
||||
-DCLICE_ENABLE_TEST=ON
|
||||
```
|
||||
|
||||
> 注意:`CMAKE_TOOLCHAIN_FILE` 是可选的。如果你使用的工具链与我们完全一致,可以使用预定义的 `cmake/toolchain.cmake`,否则请移除该选项
|
||||
|
||||
可选的构建选项:
|
||||
|
||||
| 选项 | 默认值 | 效果 |
|
||||
| -------------------- | ------ | ----------------------------------------------------------------------------------------------------- |
|
||||
| LLVM_INSTALL_PATH | "" | 使用自定义路径的 llvm libs 来构建 clice |
|
||||
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
|
||||
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice(添加 `-std=libc++`),如果开启,请确保 llvm libs 也是使用 libc++ 编译的 |
|
||||
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
|
||||
| 选项 | 默认值 | 效果 |
|
||||
| -------------------- | ------ | -------------------------------------------------------------------------------------------------- |
|
||||
| LLVM_INSTALL_PATH | "" | 使用自定义路径的 LLVM 库来构建 clice |
|
||||
| CLICE_ENABLE_TEST | OFF | 是否构建 clice 的单元测试 |
|
||||
| CLICE_USE_LIBCXX | OFF | 是否使用 libc++ 来构建 clice(添加 `-std=libc++`),如果开启,请确保 LLVM 库也是使用 libc++ 编译的 |
|
||||
| CLICE_CI_ENVIRONMENT | OFF | 是否打开 `CLICE_CI_ENVIRONMENT` 这个宏,有些测试在 CI 环境才会执行 |
|
||||
|
||||
## XMake
|
||||
## About LLVM
|
||||
|
||||
使用如下的命令即可构建 clice
|
||||
clice 调用 Clang API 来解析 C++ 代码,因此必须链接 LLVM/Clang 库。由于 clice 使用了 Clang 的私有头文件(这些文件通常不包含在发行版中),不能直接使用系统安装的 LLVM 包。
|
||||
|
||||
```bash
|
||||
xmake f -c --mode=releasedbg --toolchain=clang
|
||||
xmake build --all
|
||||
```
|
||||
主要有两种方式解决这个依赖问题:
|
||||
|
||||
可选的构建选项:
|
||||
|
||||
| 选项 | 默认值 | 效果 |
|
||||
| ------------- | ------ | --------------------------------------- |
|
||||
| --llvm | "" | 使用自定义路径的 llvm libs 来构建 clice |
|
||||
| --enable_test | false | 是否构建 clice 的单元测试 |
|
||||
| --ci | false | 是否打开 `CLICE_CI_ENVIRONMENT` |
|
||||
|
||||
|
||||
## A Note on LLVM Libs
|
||||
|
||||
由于 C++ 的语法太过复杂,自己编写一个新的 parser 是不现实的。clice 调用 clang 的 API 来 parse C++ 源文件获取 AST,这意味它需要链接 llvm/clang libs。由于 clice 使用了 clang 的私有头文件,这些私有头文件在 llvm 发布的 binary release 中是没有的,所以不能直接使用系统的 llvm package。
|
||||
|
||||
1. 我们在 [clice-llvm](https://github.com/clice-io/clice-llvm/releases) 上会发布使用的 llvm 版本的预编译二进制,用于 CI 或者 release 构建。在构建时 cmake 和 xmake 默认会从此处下载 llvm libs 然后使用,
|
||||
1. 我们在 [clice-llvm](https://github.com/clice-io/clice-llvm/releases) 上会发布使用的 LLVM 版本的预编译二进制,用于 CI 或者 release 构建。在构建时 cmake 默认会从此处下载 LLVM 库然后使用。
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> 对于 debug 版本的 llvm libs,构建的时候我们开启了 address sanitizer,而 address sanitizer 依赖于 compiler rt,它对编译器版本十分敏感。所以如果使用 debug 版本,请确保你的 clang 的 compiler rt 版本和我们构建的时候**严格一致**。
|
||||
>
|
||||
> - Windows 暂时没有 debug 构建的 llvm libs,因为它不支持将 clang 构建为动态库,相关的进展在 [这里](https://github.com/clice-io/clice/issues/42) 跟踪
|
||||
> - Linux 使用 clang20
|
||||
> - MacOS 使用 homebrew llvm@20,**不要使用 apple clang**
|
||||
>
|
||||
> 可以参考 CI 中的 [cmake](https://github.com/clice-io/clice/blob/main/.github/workflows/cmake.yml) 和 [xmake](https://github.com/clice-io/clice/blob/main/.github/workflows/xmake.yml) 文件作为参考,它们与预编译 llvm libs 的环境保持严格一致。
|
||||
> 对于 debug 版本的 LLVM 库,构建的时候我们开启了 address sanitizer,而 address sanitizer 依赖于 compiler rt,它对编译器版本十分敏感。所以如果使用 debug 版本,请确保你的 clang 的 compiler rt 版本与 `pixi.toml` 中的定义严格一致。
|
||||
|
||||
2. 自己重新一个与当前环境一致的 llvm/clang。如果默认的预编译二进制文件(方法 1)在你的系统上因 ABI 或库版本(如 glibc)不兼容而运行失败,或者你需要一个自定义的 Debug 版本,那么我们推荐你使用此方法从头编译 llvm libs。我们提供了一个脚本,用于构建 clice 所需要的 llvm libs:[build-llvm-libs.py](https://github.com/clice-io/clice/blob/main/scripts/build-llvm-libs.py)。
|
||||
|
||||
```bash
|
||||
cd llvm-project
|
||||
python3 <clice>/scripts/build-llvm-libs.py debug
|
||||
```
|
||||
|
||||
也可以参考 llvm 的官方构建教程 [Building LLVM with CMake](https://llvm.org/docs/CMake.html)。
|
||||
2. 自行构建一套与当前环境一致的 LLVM/Clang。如果默认的预编译二进制文件在你的系统上因 ABI 或库版本不兼容而运行失败,或者你需要一个自定义的 Debug 版本,那么我们推荐你使用此方法从头编译 LLVM 库。我们提供了一个脚本 `scripts/build-llvm.py` 用于构建所需要的 LLVM 库,也可以参考 LLVM 的官方构建教程 [Building LLVM with CMake](https://llvm.org/docs/CMake.html)。
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
## Code Style
|
||||
|
||||
命名
|
||||
|
||||
- 变量名:小写下换线
|
||||
- 类名,枚举名:大驼峰
|
||||
|
||||
70
docs/zh/dev/extension.md
Normal file
70
docs/zh/dev/extension.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Extension
|
||||
|
||||
本节汇总各编辑器插件的开发与发布流程。目前包含 VSCode / Neovim / Zed。
|
||||
|
||||
## VSCode
|
||||
|
||||
VSCode 插件使用 Node/PNPM/VSCE 链路。推荐在 pixi 的 `node` 环境下操作以获得一致的工具链版本。
|
||||
|
||||
```shell
|
||||
# 准备环境(先安装 pixi)
|
||||
pixi shell -e node
|
||||
|
||||
# 安装依赖(基于 pnpm-lock)
|
||||
pixi run install-vscode
|
||||
|
||||
# 打包扩展,产物位于 editors/vscode/*.vsix
|
||||
pixi run build-vscode
|
||||
```
|
||||
|
||||
发布到 VSCode Marketplace(需要 `VSCE_PAT` 环境变量):
|
||||
|
||||
```shell
|
||||
pixi run publish-vscode
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> 若已编译 clice,本地调试时可在 VSCode 设置中填写 `clice.executable`,使扩展指向你的自定义构建。
|
||||
|
||||
开发与调试:
|
||||
|
||||
1. `pixi shell -e node`
|
||||
2. 在 `editors/vscode` 下运行 `pnpm run watch`(增量构建)
|
||||
3. VSCode 中使用”Run Extension/Launch Extension”调试配置,或执行 `code --extensionDevelopmentPath=$(pwd)/editors/vscode`
|
||||
|
||||
常用脚本(在 `pixi shell -e node` 下):
|
||||
|
||||
```bash
|
||||
pnpm run package # 等价于 pixi run build-vscode
|
||||
pnpm run publish # 等价于 pixi run publish-vscode
|
||||
```
|
||||
|
||||
如果不使用 pixi,请自行准备 node.js >= 20、pnpm,然后在 `editors/vscode` 目录执行:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm run package
|
||||
```
|
||||
|
||||
## Neovim
|
||||
|
||||
Neovim 插件位于 `editors/nvim`,使用 Lua 编写。目前功能仍在演进中。
|
||||
|
||||
- 将仓库路径加入 `runtimepath`,例如:`set rtp+=/path/to/clice/editors/nvim`
|
||||
- 或在本地创建软链接:`~/.config/nvim/pack/clice/start/clice` -> `<repo>/editors/nvim`
|
||||
- 需要 `clice` 可执行文件可在 `$PATH` 中被找到
|
||||
|
||||
开发提示:代码量较小,可直接在 Neovim 中加载并通过 `:messages`/LSP 日志观察效果;格式化可使用 `stylua`(仓库中已提供 `stylua.toml`)。
|
||||
|
||||
## Zed
|
||||
|
||||
Zed 插件位于 `editors/zed`,使用 Rust 和 `zed_extension_api`。
|
||||
|
||||
建议的本地验证流程:
|
||||
|
||||
```bash
|
||||
cd editors/zed
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
随后按 Zed 官方指南加载本地扩展(需安装 Zed CLI),在启动前确保 `clice` 已在 PATH 中。发布时同样遵循 Zed 扩展发布流程。
|
||||
@@ -18,13 +18,6 @@ $ ./build/bin/unit_tests --test-dir="./tests/data"
|
||||
$ 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
|
||||
|
||||
如果想在 clice 上附加调试器并进行调试,推荐先单独以 socket 模式启动 clice,然后再将客户端连接到 clice 上
|
||||
@@ -51,31 +44,31 @@ $ pytest -s --log-cli-level=INFO tests/integration/test_file_operation.py::test_
|
||||
|
||||
2. 配置 `settings.json`: 在你的项目根目录下创建 `.vscode/settings.json` 文件,并填入以下内容:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// Point this to the clice binary you downloaded.
|
||||
"clice.executable": "/path/to/your/clice/executable",
|
||||
```jsonc
|
||||
{
|
||||
// Point this to the clice binary you downloaded.
|
||||
"clice.executable": "/path/to/your/clice/executable",
|
||||
|
||||
// Enable socket mode.
|
||||
"clice.mode": "socket",
|
||||
"clice.port": 50051,
|
||||
// Enable socket mode.
|
||||
"clice.mode": "socket",
|
||||
"clice.port": 50051,
|
||||
|
||||
// Optional: Set this to an empty string to turn off the clangd.
|
||||
"clangd.path": "",
|
||||
}
|
||||
```
|
||||
// Optional: Set this to an empty string to turn off the clangd.
|
||||
"clangd.path": "",
|
||||
}
|
||||
```
|
||||
|
||||
3. 重新加载窗口:修改配置后,在 vscode 中执行 Developer: Reload Window 命令使配置生效。插件会自动连接到正在 50051 端口监听的 clice。
|
||||
|
||||
|
||||
如果你需要修改或调试 clice-vscode 插件本身,可以按以下步骤操作:
|
||||
|
||||
1. 克隆并安装依赖:
|
||||
```shell
|
||||
$ git clone https://github.com/clice-io/clice-vscode
|
||||
$ cd clice-vscode
|
||||
$ npm install
|
||||
```
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/clice-io/clice-vscode
|
||||
$ cd clice-vscode
|
||||
$ npm install
|
||||
```
|
||||
|
||||
2. 使用 vscode 打开插件项目:用一个新的 vscode 窗口打开 clice-vscode 文件夹
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
- `{}`: 用于分组条件 (例如,`**/*.{ts,js}` 匹配所有 TypeScript 和 JavaScript 文件)。
|
||||
- `[]`: 声明要匹配的路径段中的字符范围 (例如,`example.[0-9]` 匹配 `example.0`, `example.1` 等)。
|
||||
- `[!...]`: 排除要匹配的路径段中的字符范围 (例如,`example.[!0-9]` 匹配 `example.a`, `example.b`,但不匹配 `example.0`)。
|
||||
<br>
|
||||
<br>
|
||||
|
||||
| 名称 | 类型 | 默认值 |
|
||||
| ---------------- | ------------------- | ------ |
|
||||
|
||||
@@ -26,7 +26,6 @@ clice 实现了 [Language Server Protocol](https://microsoft.github.io/language-
|
||||
|
||||
自己从源码编译 clice,具体的步骤参考 [build](../dev/build.md)。
|
||||
|
||||
|
||||
## Project Setup
|
||||
|
||||
为了让 clice 能正确理解你的代码(例如找到头文件的位置),需要为 clice 提供一份 `compile_commands.json` 文件,也就说所谓的 [编译数据库](https://clang.llvm.org/docs/JSONCompilationDatabase.html)。编译数据库中提供了每个源文件的编译选项。
|
||||
@@ -55,14 +54,73 @@ bazel run @hedron_compile_commands//:refresh_all
|
||||
|
||||
### Visual Studio
|
||||
|
||||
TODO:
|
||||
Visual Studio(2019 16.1+)可以通过 CMake 集成来生成编译数据库。将项目作为 CMake 项目打开,然后在 `CMakeSettings.json` 中配置:
|
||||
|
||||
```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
|
||||
|
||||
TODO:
|
||||
对于基于 Makefile 的项目,使用 [bear](https://github.com/rizsotto/Bear) 来拦截编译命令:
|
||||
|
||||
```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
|
||||
|
||||
用下列任意方法生成编译数据库。
|
||||
|
||||
#### 命令行手动生成
|
||||
|
||||
在命令行中执行以下命令:
|
||||
|
||||
```bash
|
||||
xmake project -k compile_commands --lsp=clangd build
|
||||
```
|
||||
|
||||
> 通过这种方法生成的编译数据库无法自动更新,需要在项目编译配置更改时手动重新生成。
|
||||
|
||||
#### VSCode 扩展
|
||||
|
||||
Xmake 提供的官方 VSCode 扩展会在 `xmake.lua` 更新时自动生成编译数据库。然而默认情况下,它将编译数据库生成到了 `.vscode` 文件夹。在 `settings.json` 中添加以下配置:
|
||||
|
||||
```json
|
||||
"xmake.compileCommandsDirectory": "build"
|
||||
```
|
||||
|
||||
以将编译数据库的生成目录调整到 `build`,供 clice 使用。
|
||||
|
||||
### Others
|
||||
|
||||
对于任意其它的构建系统,可以尝试使用 [bear](https://github.com/rizsotto/Bear) 或者 [scan-build](https://github.com/rizsotto/scan-build) 来拦截编译命令并获取到编译数据库(不保证成功)。我们计划在未来编写一个**新的工具**,通过假编译器的方式来实现编译命令的捕获。
|
||||
对于任意其它的构建系统,可以使用 [catter](https://github.com/clice-io/catter) 来生成编译数据库。它通过伪装编译器的方式来捕获编译命令,能够可靠地与任何调用编译器可执行文件的构建系统配合工作。
|
||||
|
||||
@@ -16,8 +16,8 @@ hero:
|
||||
text: 参与贡献
|
||||
link: /zh/dev/contribution
|
||||
image:
|
||||
src: /image.png
|
||||
alt: clice
|
||||
src: /image.png
|
||||
alt: clice
|
||||
|
||||
features:
|
||||
- icon: T
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{
|
||||
"selector": "import",
|
||||
"format": [ "camelCase", "PascalCase" ]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/semi": "warn",
|
||||
"curly": "warn",
|
||||
"eqeqeq": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"semi": "off"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"out",
|
||||
"dist",
|
||||
"**/*.d.ts"
|
||||
]
|
||||
}
|
||||
5
editors/vscode/.vscode/extensions.json
vendored
5
editors/vscode/.vscode/extensions.json
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
// 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"]
|
||||
}
|
||||
21
editors/vscode/.vscode/launch.json
vendored
21
editors/vscode/.vscode/launch.json
vendored
@@ -1,21 +0,0 @@
|
||||
// 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
13
editors/vscode/.vscode/settings.json
vendored
@@ -1,13 +0,0 @@
|
||||
// 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"
|
||||
}
|
||||
40
editors/vscode/.vscode/tasks.json
vendored
40
editors/vscode/.vscode/tasks.json
vendored
@@ -1,40 +0,0 @@
|
||||
// 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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
5182
editors/vscode/package-lock.json
generated
5182
editors/vscode/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"commands": [],
|
||||
"commands": [
|
||||
{
|
||||
"command": "clice.restart",
|
||||
"title": "Clice: Restart Language Server"
|
||||
}
|
||||
],
|
||||
"semanticTokenTypes": [
|
||||
{
|
||||
"id": "character",
|
||||
@@ -141,28 +146,25 @@
|
||||
"compile": "webpack",
|
||||
"watch": "webpack --watch",
|
||||
"vscode:prepublish": "webpack --mode production --devtool hidden-source-map",
|
||||
"package": "vsce package --baseImagesUrl https://raw.githubusercontent.com/clice-project/clice-vscode/main/",
|
||||
"publish": "vsce publish --baseImagesUrl https://raw.githubusercontent.com/clice-project/clice-vscode/main/",
|
||||
"pretest": "npm run compile && npm run lint",
|
||||
"lint": "eslint src --ext ts",
|
||||
"package": "vsce package --no-dependencies --baseImagesUrl https://raw.githubusercontent.com/clice-io/clice/main/",
|
||||
"publish": "vsce publish --no-dependencies --baseImagesUrl https://raw.githubusercontent.com/clice-io/clice/main/",
|
||||
"pretest": "pnpm run compile",
|
||||
"test": "vscode-test",
|
||||
"release:patch": "npm version patch -m \"release: v%s\" && git push --follow-tags",
|
||||
"release:minor": "npm version minor -m \"release: v%s\" && git push --follow-tags"
|
||||
"release:patch": "pnpm version patch -m \"release: v%s\" && git push --follow-tags",
|
||||
"release:minor": "pnpm version minor -m \"release: v%s\" && git push --follow-tags"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/mocha": "^10.0.7",
|
||||
"@types/node": "20.x",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "~25.0.3",
|
||||
"@types/vscode": "^1.80.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.11.0",
|
||||
"@vscode/test-cli": "^0.0.12",
|
||||
"@vscode/test-electron": "^2.4.0",
|
||||
"eslint": "^8.57.0",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.4.5",
|
||||
"webpack": "^5.92.1",
|
||||
"webpack-cli": "^5.1.4"
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "^3.7.1",
|
||||
"ts-loader": "^9.5.4",
|
||||
"typescript": "~5.5.4",
|
||||
"webpack": "^5.104.1",
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"decompress": "^4.2.1",
|
||||
@@ -170,5 +172,11 @@
|
||||
},
|
||||
"overrides": {
|
||||
"glob": "^11.1.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@vscode/vsce-sign",
|
||||
"keytar"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
4373
editors/vscode/pnpm-lock.yaml
generated
Normal file
4373
editors/vscode/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as https from 'https';
|
||||
import * as os from 'os';
|
||||
import * as vscode from "vscode";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as https from "https";
|
||||
import * as os from "os";
|
||||
// @ts-ignore
|
||||
import decompress = require('decompress');
|
||||
import decompress = require("decompress");
|
||||
|
||||
interface GitHubRelease {
|
||||
tag_name: string;
|
||||
@@ -14,12 +14,10 @@ interface GitHubRelease {
|
||||
}[];
|
||||
}
|
||||
|
||||
|
||||
export async function ensureServerBinary(
|
||||
context: vscode.ExtensionContext,
|
||||
channel: vscode.OutputChannel
|
||||
channel: vscode.OutputChannel,
|
||||
): Promise<string | undefined> {
|
||||
|
||||
const storagePath = context.globalStorageUri.fsPath;
|
||||
const platform = os.platform();
|
||||
const arch = os.arch();
|
||||
@@ -27,20 +25,20 @@ export async function ensureServerBinary(
|
||||
channel.appendLine(`[Download] Initializing clice downloader...`);
|
||||
channel.appendLine(`[Download] Platform: ${platform}, Arch: ${arch}, Storage: ${storagePath}`);
|
||||
|
||||
let platformKeyword = '';
|
||||
let archKeyword = '';
|
||||
let binaryName = 'clice';
|
||||
let platformKeyword = "";
|
||||
let archKeyword = "";
|
||||
let binaryName = "clice";
|
||||
|
||||
if (platform === 'win32') {
|
||||
platformKeyword = 'windows';
|
||||
archKeyword = 'x64';
|
||||
binaryName = 'clice.exe';
|
||||
} else if (platform === 'darwin') {
|
||||
platformKeyword = 'macos';
|
||||
if (platform === "win32") {
|
||||
platformKeyword = "windows";
|
||||
archKeyword = "x64";
|
||||
binaryName = "clice.exe";
|
||||
} else if (platform === "darwin") {
|
||||
platformKeyword = "macos";
|
||||
archKeyword = arch;
|
||||
} else if (platform === 'linux') {
|
||||
platformKeyword = 'linux';
|
||||
archKeyword = arch === 'x64' ? 'x86_64' : arch;
|
||||
} else if (platform === "linux") {
|
||||
platformKeyword = "linux";
|
||||
archKeyword = arch === "x64" ? "x86_64" : arch;
|
||||
} else {
|
||||
const msg = `Unsupported platform: ${platform}`;
|
||||
channel.appendLine(`[Download] Error: ${msg}`);
|
||||
@@ -71,15 +69,19 @@ export async function ensureServerBinary(
|
||||
const release = await fetchReleaseInfo(channel);
|
||||
channel.appendLine(`[Download] Latest tag: ${release.tag_name}`);
|
||||
|
||||
const asset = release.assets.find(a => {
|
||||
const asset = release.assets.find((a) => {
|
||||
const name = a.name.toLowerCase();
|
||||
return name.includes(platformKeyword) &&
|
||||
return (
|
||||
name.includes(platformKeyword) &&
|
||||
name.includes(archKeyword) &&
|
||||
!name.includes('symbol');
|
||||
!name.includes("symbol")
|
||||
);
|
||||
});
|
||||
|
||||
if (!asset) {
|
||||
throw new Error(`No compatible asset found for ${platform}-${archKeyword} in release ${release.tag_name}`);
|
||||
throw new Error(
|
||||
`No compatible asset found for ${platform}-${archKeyword} in release ${release.tag_name}`,
|
||||
);
|
||||
}
|
||||
|
||||
channel.appendLine(`[Download] Found asset: ${asset.name}`);
|
||||
@@ -103,15 +105,16 @@ export async function ensureServerBinary(
|
||||
throw new Error(`Executable not found at ${executablePath} after extraction.`);
|
||||
}
|
||||
|
||||
if (platform !== 'win32') {
|
||||
if (platform !== "win32") {
|
||||
channel.appendLine(`[Download] Setting executable permissions (chmod 755)...`);
|
||||
fs.chmodSync(executablePath, '755');
|
||||
fs.chmodSync(executablePath, "755");
|
||||
}
|
||||
|
||||
channel.appendLine(`[Download] Setup successful. Binary ready at: ${executablePath}`);
|
||||
vscode.window.showInformationMessage(`Clice language server updated to ${release.tag_name}`);
|
||||
vscode.window.showInformationMessage(
|
||||
`Clice language server updated to ${release.tag_name}`,
|
||||
);
|
||||
return executablePath;
|
||||
|
||||
} catch (error) {
|
||||
channel.appendLine(`[Download] CRITICAL ERROR during setup:`);
|
||||
if (error instanceof Error) {
|
||||
@@ -123,11 +126,16 @@ export async function ensureServerBinary(
|
||||
channel.appendLine(`[Download] Unknown error: ${JSON.stringify(error)}`);
|
||||
}
|
||||
|
||||
vscode.window.showErrorMessage(`Failed to download clice server. Check "clice" output channel for details.`, "Open Output").then(selection => {
|
||||
if (selection === "Open Output") {
|
||||
channel.show();
|
||||
}
|
||||
});
|
||||
vscode.window
|
||||
.showErrorMessage(
|
||||
`Failed to download clice server. Check "clice" output channel for details.`,
|
||||
"Open Output",
|
||||
)
|
||||
.then((selection) => {
|
||||
if (selection === "Open Output") {
|
||||
channel.show();
|
||||
}
|
||||
});
|
||||
|
||||
return undefined;
|
||||
} finally {
|
||||
@@ -135,60 +143,73 @@ export async function ensureServerBinary(
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFile(url: string, destPath: string, channel: vscode.OutputChannel, maxRedirects = 5): Promise<void> {
|
||||
function downloadFile(
|
||||
url: string,
|
||||
destPath: string,
|
||||
channel: vscode.OutputChannel,
|
||||
maxRedirects = 5,
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (maxRedirects <= 0) {
|
||||
reject(new Error('Too many redirects'));
|
||||
reject(new Error("Too many redirects"));
|
||||
return;
|
||||
}
|
||||
|
||||
const file = fs.createWriteStream(destPath);
|
||||
channel.appendLine(`[Download] Start downloading to ${destPath}`);
|
||||
|
||||
https.get(url, { headers: { 'User-Agent': 'VSCode-Extension' } }, (response) => {
|
||||
if (response.statusCode === 302 || response.statusCode === 301) {
|
||||
channel.appendLine(`[Download] Redirecting to ${response.headers.location}`);
|
||||
file.close();
|
||||
downloadFile(response.headers.location!, destPath, channel, maxRedirects - 1).then(resolve).catch(reject);
|
||||
return;
|
||||
}
|
||||
if (response.statusCode !== 200) {
|
||||
reject(new Error(`Download failed with status code ${response.statusCode}`));
|
||||
return;
|
||||
}
|
||||
https
|
||||
.get(url, { headers: { "User-Agent": "VSCode-Extension" } }, (response) => {
|
||||
if (response.statusCode === 302 || response.statusCode === 301) {
|
||||
channel.appendLine(`[Download] Redirecting to ${response.headers.location}`);
|
||||
file.close();
|
||||
downloadFile(response.headers.location!, destPath, channel, maxRedirects - 1)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
return;
|
||||
}
|
||||
if (response.statusCode !== 200) {
|
||||
reject(new Error(`Download failed with status code ${response.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
response.pipe(file);
|
||||
file.on("finish", () => {
|
||||
file.close();
|
||||
channel.appendLine(`[Download] Download finished.`);
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.on("error", (err) => {
|
||||
file.close();
|
||||
channel.appendLine(`[Download] Download finished.`);
|
||||
resolve();
|
||||
fs.unlink(destPath, () => {});
|
||||
reject(err);
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
file.close();
|
||||
fs.unlink(destPath, () => { });
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchReleaseInfo(channel: vscode.OutputChannel): Promise<GitHubRelease> {
|
||||
try {
|
||||
channel.appendLine('[Download] Attempting to fetch latest stable release...');
|
||||
const release = await fetchJson<GitHubRelease>('/repos/clice-io/clice/releases/latest');
|
||||
channel.appendLine("[Download] Attempting to fetch latest stable release...");
|
||||
const release = await fetchJson<GitHubRelease>("/repos/clice-io/clice/releases/latest");
|
||||
channel.appendLine(`[Download] Found stable release: ${release.tag_name}`);
|
||||
return release;
|
||||
} catch (error: any) {
|
||||
if (error.message && error.message.includes('404')) {
|
||||
channel.appendLine('[Download] Latest stable release not found (404). Checking for pre-releases...');
|
||||
if (error.message && error.message.includes("404")) {
|
||||
channel.appendLine(
|
||||
"[Download] Latest stable release not found (404). Checking for pre-releases...",
|
||||
);
|
||||
|
||||
const releases = await fetchJson<GitHubRelease[]>('/repos/clice-io/clice/releases?per_page=1');
|
||||
const releases = await fetchJson<GitHubRelease[]>(
|
||||
"/repos/clice-io/clice/releases?per_page=1",
|
||||
);
|
||||
|
||||
if (Array.isArray(releases) && releases.length > 0) {
|
||||
const latestPre = releases[0];
|
||||
channel.appendLine(`[Download] Found pre-release: ${latestPre.tag_name}`);
|
||||
return latestPre;
|
||||
} else {
|
||||
throw new Error('No releases found in repository.');
|
||||
throw new Error("No releases found in repository.");
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
@@ -198,27 +219,29 @@ async function fetchReleaseInfo(channel: vscode.OutputChannel): Promise<GitHubRe
|
||||
function fetchJson<T>(apiPath: string): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: 'api.github.com',
|
||||
hostname: "api.github.com",
|
||||
path: apiPath,
|
||||
headers: { 'User-Agent': 'VSCode-Extension' }
|
||||
headers: { "User-Agent": "VSCode-Extension" },
|
||||
};
|
||||
|
||||
https.get(options, (res) => {
|
||||
let data = '';
|
||||
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
||||
res.resume();
|
||||
reject(new Error(`GitHub API returned ${res.statusCode} for ${apiPath}`));
|
||||
return;
|
||||
}
|
||||
|
||||
res.on('data', (chunk) => data += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (e) {
|
||||
reject(new Error(`Failed to parse GitHub API response: ${e}`));
|
||||
https
|
||||
.get(options, (res) => {
|
||||
let data = "";
|
||||
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
||||
res.resume();
|
||||
reject(new Error(`GitHub API returned ${res.statusCode} for ${apiPath}`));
|
||||
return;
|
||||
}
|
||||
});
|
||||
}).on('error', reject);
|
||||
|
||||
res.on("data", (chunk) => (data += chunk));
|
||||
res.on("end", () => {
|
||||
try {
|
||||
resolve(JSON.parse(data));
|
||||
} catch (e) {
|
||||
reject(new Error(`Failed to parse GitHub API response: ${e}`));
|
||||
}
|
||||
});
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,93 +1,95 @@
|
||||
import * as net from 'net';
|
||||
import * as vscode from 'vscode';
|
||||
import { workspace, window, ExtensionContext } from 'vscode';
|
||||
import { LanguageClient, LanguageClientOptions, ServerOptions, StreamInfo } from 'vscode-languageclient/node';
|
||||
import { getSetting } from './setting';
|
||||
import { ensureServerBinary } from './download'
|
||||
import * as net from "net";
|
||||
import * as vscode from "vscode";
|
||||
import { workspace, window, ExtensionContext } from "vscode";
|
||||
import {
|
||||
LanguageClient,
|
||||
LanguageClientOptions,
|
||||
ServerOptions,
|
||||
StreamInfo,
|
||||
} from "vscode-languageclient/node";
|
||||
import { getSetting } from "./setting";
|
||||
import { ensureServerBinary } from "./download";
|
||||
|
||||
let client: LanguageClient;
|
||||
|
||||
export async function registerCommands(client: LanguageClient, context: ExtensionContext) {
|
||||
context.subscriptions.push(vscode.commands.registerCommand("clice.restart", async () => {
|
||||
await client.restart();
|
||||
}));
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("clice.restart", async () => {
|
||||
await client.restart();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function activate(context: ExtensionContext) {
|
||||
console.log('Congratulations, your extension "clice" is now active!');
|
||||
console.log('Congratulations, your extension "clice" is now active!');
|
||||
|
||||
const channel = window.createOutputChannel('clice');
|
||||
const verboseChannel = window.createOutputChannel('clice-verbose');
|
||||
const channel = window.createOutputChannel("clice");
|
||||
const verboseChannel = window.createOutputChannel("clice-verbose");
|
||||
|
||||
const setting = getSetting();
|
||||
if (!setting) {
|
||||
return;
|
||||
}
|
||||
const setting = getSetting();
|
||||
if (!setting) {
|
||||
return;
|
||||
}
|
||||
|
||||
let executable = setting.executable
|
||||
let serverOptions: ServerOptions | (() => Promise<StreamInfo>);
|
||||
let executable = setting.executable;
|
||||
let serverOptions: ServerOptions | (() => Promise<StreamInfo>);
|
||||
|
||||
if (setting.mode === "pipe") {
|
||||
if (!executable || executable === "") {
|
||||
const downloadedPath = await ensureServerBinary(context, channel);
|
||||
if (downloadedPath) {
|
||||
executable = downloadedPath;
|
||||
} else {
|
||||
window.showErrorMessage("Could not find or download clice executable.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (setting.mode === "pipe") {
|
||||
if (!executable || executable === "") {
|
||||
const downloadedPath = await ensureServerBinary(context, channel);
|
||||
if (downloadedPath) {
|
||||
executable = downloadedPath;
|
||||
} else {
|
||||
window.showErrorMessage("Could not find or download clice executable.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let args = ["--mode=pipe"];
|
||||
serverOptions = {
|
||||
run: { command: executable, args: args },
|
||||
debug: { command: executable, args: args }
|
||||
};
|
||||
} else if (setting.mode === "socket") {
|
||||
serverOptions = (): Promise<StreamInfo> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = new net.Socket();
|
||||
client.connect(setting.port, setting.host, () => {
|
||||
resolve({
|
||||
reader: client,
|
||||
writer: client,
|
||||
});
|
||||
});
|
||||
client.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
} else {
|
||||
vscode.window.showErrorMessage("Invalid mode, please set the mode to 'pipe' or 'socket'.");
|
||||
return
|
||||
}
|
||||
let args = ["--mode=pipe"];
|
||||
serverOptions = {
|
||||
run: { command: executable, args: args },
|
||||
debug: { command: executable, args: args },
|
||||
};
|
||||
} else if (setting.mode === "socket") {
|
||||
serverOptions = (): Promise<StreamInfo> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = new net.Socket();
|
||||
client.connect(setting.port, setting.host, () => {
|
||||
resolve({
|
||||
reader: client,
|
||||
writer: client,
|
||||
});
|
||||
});
|
||||
client.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
} else {
|
||||
vscode.window.showErrorMessage("Invalid mode, please set the mode to 'pipe' or 'socket'.");
|
||||
return;
|
||||
}
|
||||
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
documentSelector: [{ scheme: 'file', language: 'cpp' }],
|
||||
outputChannel: channel,
|
||||
traceOutputChannel: verboseChannel,
|
||||
synchronize: {
|
||||
fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
|
||||
}
|
||||
};
|
||||
const clientOptions: LanguageClientOptions = {
|
||||
documentSelector: [{ scheme: "file", language: "cpp" }],
|
||||
outputChannel: channel,
|
||||
traceOutputChannel: verboseChannel,
|
||||
synchronize: {
|
||||
fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
|
||||
},
|
||||
};
|
||||
|
||||
client = new LanguageClient(
|
||||
'clice',
|
||||
'clice',
|
||||
serverOptions,
|
||||
clientOptions
|
||||
);
|
||||
client = new LanguageClient("clice", "clice", serverOptions, clientOptions);
|
||||
|
||||
await registerCommands(client, context);
|
||||
await registerCommands(client, context);
|
||||
|
||||
await client.start();
|
||||
await client.start();
|
||||
}
|
||||
|
||||
export function deactivate(): Thenable<void> | undefined {
|
||||
if (!client) {
|
||||
return undefined;
|
||||
}
|
||||
let ret = client.stop();
|
||||
return ret;
|
||||
if (!client) {
|
||||
return undefined;
|
||||
}
|
||||
let ret = client.stop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,35 +1,37 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { DocumentUri } from 'vscode-languageclient/node';
|
||||
import * as vscode from "vscode";
|
||||
import { DocumentUri } from "vscode-languageclient/node";
|
||||
|
||||
let provider: HeaderContextProvider | undefined = undefined;
|
||||
|
||||
export type HeaderContext = {
|
||||
file: string,
|
||||
index: number,
|
||||
include: number
|
||||
file: string;
|
||||
index: number;
|
||||
include: number;
|
||||
};
|
||||
|
||||
export type HeaderContextSwitchParams = {
|
||||
header: DocumentUri,
|
||||
context: HeaderContext,
|
||||
header: DocumentUri;
|
||||
context: HeaderContext;
|
||||
};
|
||||
|
||||
export type IncludeLocation = {
|
||||
line: number,
|
||||
filename: string
|
||||
line: number;
|
||||
filename: string;
|
||||
};
|
||||
|
||||
export class TreeItem extends vscode.TreeItem {
|
||||
children: Array<TreeItem> = []
|
||||
children: Array<TreeItem> = [];
|
||||
context: HeaderContext | undefined = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export class HeaderContextProvider implements vscode.TreeDataProvider<TreeItem> {
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<TreeItem | undefined | void> = new vscode.EventEmitter<TreeItem | undefined | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<TreeItem | undefined | void> = this._onDidChangeTreeData.event;
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<TreeItem | undefined | void> =
|
||||
new vscode.EventEmitter<TreeItem | undefined | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<TreeItem | undefined | void> =
|
||||
this._onDidChangeTreeData.event;
|
||||
|
||||
header: string = ""
|
||||
items: Array<TreeItem> = []
|
||||
header: string = "";
|
||||
items: Array<TreeItem> = [];
|
||||
|
||||
update(contexts: Array<Array<HeaderContext>>) {
|
||||
/// Create groups
|
||||
@@ -60,11 +62,11 @@ export class HeaderContextProvider implements vscode.TreeDataProvider<TreeItem>
|
||||
getChildren(element?: TreeItem) {
|
||||
return element ? element.children : this.items;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
export function registerHeaderContextView() {
|
||||
provider = new HeaderContextProvider();
|
||||
let treeView = vscode.window.createTreeView("header-contexts", { treeDataProvider: provider });
|
||||
let treeView = vscode.window.createTreeView("header-contexts", {
|
||||
treeDataProvider: provider,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as vscode from "vscode";
|
||||
|
||||
const rainbowColors = [
|
||||
"#56B6C2",
|
||||
"#61AFEF",
|
||||
"#C678DD",
|
||||
"#E06C75",
|
||||
"#98C379",
|
||||
"#D19A66",
|
||||
"#E5C07B"
|
||||
];
|
||||
const rainbowColors = ["#56B6C2", "#61AFEF", "#C678DD", "#E06C75", "#98C379", "#D19A66", "#E5C07B"];
|
||||
|
||||
const textEditorDecorationTypes = rainbowColors.map((color) => {
|
||||
return vscode.window.createTextEditorDecorationType({
|
||||
color: color
|
||||
color: color,
|
||||
});
|
||||
});
|
||||
|
||||
export function highlightDocument(document: vscode.TextDocument, legend: vscode.SemanticTokensLegend, semanticTokens: vscode.SemanticTokens) {
|
||||
export function highlightDocument(
|
||||
document: vscode.TextDocument,
|
||||
legend: vscode.SemanticTokensLegend,
|
||||
semanticTokens: vscode.SemanticTokens,
|
||||
) {
|
||||
const editor = vscode.window.activeTextEditor;
|
||||
if (!editor || editor.document !== document) { return; }
|
||||
const angleIndex = legend?.tokenTypes.indexOf('angle');
|
||||
const leftIndex = legend?.tokenModifiers.indexOf('left');
|
||||
const rightIndex = legend?.tokenModifiers.indexOf('right');
|
||||
if (leftIndex === undefined || rightIndex === undefined || angleIndex === undefined) { return; }
|
||||
if (!editor || editor.document !== document) {
|
||||
return;
|
||||
}
|
||||
const angleIndex = legend?.tokenTypes.indexOf("angle");
|
||||
const leftIndex = legend?.tokenModifiers.indexOf("left");
|
||||
const rightIndex = legend?.tokenModifiers.indexOf("right");
|
||||
if (leftIndex === undefined || rightIndex === undefined || angleIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const decorations = new Map<number, vscode.Range[]>();
|
||||
let level = 0;
|
||||
@@ -32,7 +32,8 @@ export function highlightDocument(document: vscode.TextDocument, legend: vscode.
|
||||
|
||||
// [line, startCharacter, length, tokenType, tokenModifiers]
|
||||
for (let i = 0; i < semanticTokens.data.length; i += 5) {
|
||||
const [lineDelta, startDelta, length, tokenType, tokenModifiers] = semanticTokens.data.slice(i, i + 5);
|
||||
const [lineDelta, startDelta, length, tokenType, tokenModifiers] =
|
||||
semanticTokens.data.slice(i, i + 5);
|
||||
|
||||
lastLine += lineDelta;
|
||||
lastStart = lineDelta === 0 ? lastStart + startDelta : startDelta;
|
||||
@@ -52,15 +53,11 @@ export function highlightDocument(document: vscode.TextDocument, legend: vscode.
|
||||
|
||||
if (tokenModifiers & (1 << leftIndex)) {
|
||||
level += 1;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [level, ranges] of decorations) {
|
||||
editor.setDecorations(
|
||||
textEditorDecorationTypes[level],
|
||||
ranges
|
||||
);
|
||||
editor.setDecorations(textEditorDecorationTypes[level], ranges);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as vscode from "vscode";
|
||||
|
||||
interface Setting {
|
||||
executable: string | undefined,
|
||||
mode: string,
|
||||
host: string,
|
||||
port: number,
|
||||
executable: string | undefined;
|
||||
mode: string;
|
||||
host: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export function getSetting(): Setting | undefined {
|
||||
const setting = vscode.workspace.getConfiguration('clice')
|
||||
const executable = setting.get<string>('executable');
|
||||
const mode = setting.get<string>('mode');
|
||||
const setting = vscode.workspace.getConfiguration("clice");
|
||||
const executable = setting.get<string>("executable");
|
||||
const mode = process.env.CLICE_MODE || setting.get<string>("mode");
|
||||
|
||||
if (mode !== "pipe" && mode !== "socket") {
|
||||
vscode.window.showErrorMessage(`Unexpected mode: ${mode}`);
|
||||
return undefined
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const host = setting.get<string>('host')!;
|
||||
const port = setting.get<number>('port')!;
|
||||
const host = setting.get<string>("host")!;
|
||||
const port = setting.get<number>("port")!;
|
||||
|
||||
if (mode === "socket" && (!host || !port)) {
|
||||
vscode.window.showErrorMessage('Socket mode requires both host and port to be configured.');
|
||||
vscode.window.showErrorMessage("Socket mode requires both host and port to be configured.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
executable, mode, host, port,
|
||||
}
|
||||
executable,
|
||||
mode,
|
||||
host,
|
||||
port,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as assert from 'assert';
|
||||
import * as assert from "assert";
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
import * as vscode from "vscode";
|
||||
// import * as myExtension from '../../extension';
|
||||
|
||||
suite('Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
suite("Extension Test Suite", () => {
|
||||
vscode.window.showInformationMessage("Start all tests.");
|
||||
|
||||
test('Sample test', () => {
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
test("Sample test", () => {
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
||||
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "Node16",
|
||||
"target": "ES2022",
|
||||
"lib": [
|
||||
"ES2022"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true /* enable all strict type-checking options */
|
||||
/* Additional Checks */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
}
|
||||
"compilerOptions": {
|
||||
"module": "Node16",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true /* enable all strict type-checking options */
|
||||
/* Additional Checks */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,48 @@
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
"use strict";
|
||||
|
||||
const path = require('path');
|
||||
const path = require("path");
|
||||
|
||||
//@ts-check
|
||||
/** @typedef {import('webpack').Configuration} WebpackConfig **/
|
||||
|
||||
/** @type WebpackConfig */
|
||||
const extensionConfig = {
|
||||
target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
|
||||
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
|
||||
target: "node", // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
|
||||
mode: "none", // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
|
||||
|
||||
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
|
||||
output: {
|
||||
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'extension.js',
|
||||
libraryTarget: 'commonjs2'
|
||||
},
|
||||
externals: {
|
||||
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
|
||||
// modules added here also need to be added in the .vscodeignore file
|
||||
},
|
||||
resolve: {
|
||||
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
|
||||
extensions: ['.ts', '.js']
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
devtool: 'nosources-source-map',
|
||||
infrastructureLogging: {
|
||||
level: "log", // enables logging required for problem matchers
|
||||
},
|
||||
entry: "./src/extension.ts", // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
|
||||
output: {
|
||||
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "extension.js",
|
||||
libraryTarget: "commonjs2",
|
||||
},
|
||||
externals: {
|
||||
vscode: "commonjs vscode", // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
|
||||
// modules added here also need to be added in the .vscodeignore file
|
||||
},
|
||||
resolve: {
|
||||
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
|
||||
extensions: [".ts", ".js"],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: "ts-loader",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
devtool: "nosources-source-map",
|
||||
infrastructureLogging: {
|
||||
level: "log", // enables logging required for problem matchers
|
||||
},
|
||||
};
|
||||
module.exports = [ extensionConfig ];
|
||||
module.exports = [extensionConfig];
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
name = "zed_clice"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "src/clice.rs"
|
||||
crate-type = ["cdylib"]
|
||||
path = "src/clice.rs"
|
||||
|
||||
[dependencies]
|
||||
zed_extension_api = "0.1.0"
|
||||
|
||||
@@ -4,34 +4,12 @@ struct CliceExtension;
|
||||
|
||||
struct CliceBinary {
|
||||
path: String,
|
||||
resource_dir: String,
|
||||
}
|
||||
|
||||
impl CliceExtension {
|
||||
fn find_clice_binary(&self, worktree: &Worktree) -> Result<CliceBinary> {
|
||||
if let Some(path_str) = worktree.which("clice") {
|
||||
// The std::path module seems to behave unexpectedly in the Zed sandbox,
|
||||
// failing to correctly parse parent directories for full paths on Windows.
|
||||
// To ensure reliability, we revert to manual string manipulation to find the parent directory.
|
||||
let separator_pos = path_str.rfind(|c| c == '\\' || c == '/');
|
||||
|
||||
if let Some(pos) = separator_pos {
|
||||
let parent_dir = &path_str[..pos];
|
||||
// We use std::path::MAIN_SEPARATOR to construct the path in a cross-platform way,
|
||||
// avoiding the hardcoded '\' from the original implementation.
|
||||
let resource_dir = format!("{}{}{}", parent_dir, std::path::MAIN_SEPARATOR, "lib");
|
||||
|
||||
Ok(CliceBinary {
|
||||
path: path_str,
|
||||
resource_dir,
|
||||
})
|
||||
} else {
|
||||
Err(format!(
|
||||
"clice found as `{}`,
|
||||
but a full path is required to find the `lib` directory.",
|
||||
path_str
|
||||
))
|
||||
}
|
||||
Ok(CliceBinary { path: path_str })
|
||||
} else {
|
||||
Err(
|
||||
"`clice` not found in your PATH. Please install it and add it to your system's PATH environment variable.".to_string()
|
||||
@@ -55,8 +33,6 @@ impl zed::Extension for CliceExtension {
|
||||
Ok(zed::Command {
|
||||
command: binary.path,
|
||||
args: vec![
|
||||
"--resource-dir".to_string(),
|
||||
binary.resource_dir,
|
||||
"--mode".to_string(),
|
||||
"pipe".to_string(),
|
||||
],
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#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
|
||||
@@ -1,193 +0,0 @@
|
||||
#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 diretive 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,10 +0,0 @@
|
||||
#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"
|
||||
@@ -1,96 +0,0 @@
|
||||
#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
|
||||
@@ -1,54 +0,0 @@
|
||||
#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 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
|
||||
@@ -1,95 +0,0 @@
|
||||
#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
|
||||
@@ -1,143 +0,0 @@
|
||||
#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
|
||||
@@ -1,79 +0,0 @@
|
||||
#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
|
||||
@@ -1,23 +0,0 @@
|
||||
#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
|
||||
@@ -1,37 +0,0 @@
|
||||
#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
|
||||
@@ -1,334 +0,0 @@
|
||||
#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
|
||||
@@ -1,60 +0,0 @@
|
||||
#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
|
||||
@@ -1,79 +0,0 @@
|
||||
#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
|
||||
@@ -1,125 +0,0 @@
|
||||
#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<const char*> 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,46 +0,0 @@
|
||||
#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
|
||||
@@ -1,40 +0,0 @@
|
||||
#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
|
||||
@@ -1,34 +0,0 @@
|
||||
#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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user