14 Commits

Author SHA1 Message Date
ykiko
f16867902c build: update package workflow (#307)
Co-authored-by: star9029 <hengxings783@gmail.com>
2025-11-17 00:45:44 +08:00
Shiyu
4d07bad2f2 build: add guard for clang-tidy-config.h in xmake rules (#306)
Co-authored-by: ykiko <ykikoykikoykiko@gmail.com>
2025-11-16 02:32:00 +08:00
ykiko
4a2a6aa65a build: update llvm checksum and refactor ci (#304) 2025-11-16 02:17:37 +08:00
star9029
3c53d3bc72 build: update llvm 21.1.4 checksum (#303) 2025-11-09 11:35:03 +08:00
ykiko
9e1039f861 refactor: improve logging (#301) 2025-11-08 00:37:07 +08:00
star9029
8a2ef62596 build: enable xmake ci package cache (#295) 2025-11-05 23:33:28 +08:00
ykiko
336ca639f0 refactor: CompilationDatabase and scan (#286) 2025-11-05 23:01:28 +08:00
ClSlaid
9c43285d0d fix: xmake spdlog force non-system (#299)
Signed-off-by: 蔡略 <cailue@apache.org>
2025-11-05 20:56:15 +08:00
ykiko
39ec9bf7c5 fix: reset clang dependency output options (#293) 2025-11-02 23:25:39 +08:00
ykiko
397eb71dad build: update llvm to 21.1.4 (#292) 2025-11-02 22:23:11 +08:00
Myriad-Dreamin
3b1e379408 Fix warnings in Compiler (#290) 2025-10-31 20:52:16 +08:00
Myriad-Dreamin
8b998e658c [Fix] Use server's encoding kind (#289) 2025-10-31 20:51:11 +08:00
Myriad-Dreamin
9806e45fa3 [Feature] Enable clang-tidy (#200)
Co-authored-by: star9029 <hengxings783@gmail.com>
2025-10-31 20:50:07 +08:00
Perdixky
7d71c0f689 [Fix] GCC compilation issue caused by template specialization (#287) 2025-10-29 09:45:42 +08:00
70 changed files with 2581 additions and 1737 deletions

View File

@@ -7,7 +7,6 @@ on:
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest

View File

@@ -9,7 +9,6 @@ on:
- "src/**"
- "tests/**"
- "CMakeLists.txt"
pull_request:
branches: [main]
paths:
@@ -17,44 +16,56 @@ on:
- "include/**"
- "src/**"
- "tests/**"
- "config/**"
- "CMakeLists.txt"
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, windows-2025, macos-15]
include:
- os: windows-2025
build_type: RelWithDebInfo
cc: clang
cxx: clang++
- os: ubuntu-24.04
build_type: Debug
cc: clang-20
cxx: clang++-20
- os: macos-15
build_type: Debug
cc: clang
cxx: clang++
runs-on: ${{ matrix.os }}
steps:
- name: Setup ninja
if: matrix.os == 'windows-2025'
- name: Setup dependencies (Windows)
if: runner.os == 'Windows'
uses: MinoruSekine/setup-scoop@v4.0.1
with:
buckets: main
apps: ninja
- name: Setup llvm & libstdc++ & cmake & ninja
if: matrix.os == 'ubuntu-24.04'
- name: Setup dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install -y gcc-14 g++-14 libstdc++-14-dev
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
sudo update-alternatives --set gcc /usr/bin/gcc-14
sudo update-alternatives --set g++ /usr/bin/g++-14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20 all
sudo apt install -y cmake ninja-build
- name: Setup llvm@20 and lld
if: matrix.os == 'macos-15'
- name: Setup dependencies (MacOS)
if: runner.os == 'macOS'
env:
HOMEBREW_NO_AUTO_UPDATE: 1
run: |
brew install llvm@20 lld@20
@@ -62,40 +73,35 @@ jobs:
uses: actions/checkout@v4
- name: Setup msvc sysroot for cmake
if: matrix.os == 'windows-2025'
if: runner.os == 'Windows'
uses: ilammy/msvc-dev-cmd@v1
- name: Build clice (release, windows)
if: matrix.os == 'windows-2025'
- name: Build clice
shell: bash
run: |
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCLICE_ENABLE_TEST=ON -DCLICE_CI_ENVIRONMENT=ON
cmake --build build
if [[ "${{ runner.os }}" == "macOS" ]]; then
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
fi
- name: Build clice (debug, linux)
if: matrix.os == 'ubuntu-24.04'
run: |
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DCLICE_ENABLE_TEST=ON -DCLICE_CI_ENVIRONMENT=ON
cmake --build build
cmake -B build -G Ninja \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DCMAKE_C_COMPILER=${{ matrix.cc }} \
-DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \
-DCLICE_ENABLE_TEST=ON \
-DCLICE_CI_ENVIRONMENT=ON
- name: Build clice (debug, macos)
if: matrix.os == 'macos-15'
run: |
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCLICE_ENABLE_TEST=ON -DCLICE_CI_ENVIRONMENT=ON
cmake --build build
- name: Install uv for integration tests
uses: astral-sh/setup-uv@v6
- name: Run tests
if: matrix.os == 'windows-2025'
run: |
./build/bin/unit_tests.exe --test-dir="./tests/data"
uv run pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice.exe
shell: bash
- name: Run tests
if: matrix.os == 'ubuntu-24.04' || matrix.os == 'macos-15'
run: |
./build/bin/unit_tests --test-dir="./tests/data"
uv run pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice
EXE_EXT=""
if [[ "${{ runner.os }}" == "Windows" ]]; then
EXE_EXT=".exe"
fi
./build/bin/unit_tests${EXE_EXT} --test-dir="./tests/data"
uv run pytest -s --log-cli-level=INFO tests/integration --executable=./build/bin/clice${EXE_EXT}

137
.github/workflows/package.yml vendored Normal file
View File

@@ -0,0 +1,137 @@
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: Free disk space (Linux)
if: runner.os == 'Linux'
uses: jlumbroso/free-disk-space@main
- name: Increase swap file size (Linux)
if: runner.os == 'Linux'
run: |
echo "===== Initial Status ====="
sudo swapon --show
free -h
echo "===== Creating Swap File ====="
sudo swapoff -a
sudo fallocate -l 16G /mnt/swapfile
sudo chmod 600 /mnt/swapfile
sudo mkswap /mnt/swapfile
sudo swapon /mnt/swapfile
echo "===== Final Status ====="
sudo swapon --show
free -h
df -h
- name: Setup dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install -y gcc-14 g++-14 libstdc++-14-dev cmake ninja-build
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
sudo update-alternatives --set gcc /usr/bin/gcc-14
sudo update-alternatives --set g++ /usr/bin/g++-14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20 all
- name: Setup dependencies (MacOS)
if: runner.os == 'macOS'
env:
HOMEBREW_NO_AUTO_UPDATE: 1
run: |
brew install llvm@20 lld@20
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup xmake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: 3.0.4
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: Configure and Package
shell: bash
run: |
if [[ "${{ runner.os }}" == "Windows" ]]; then
xmake config --yes --enable_test=n --dev=n --release=y --mode=releasedbg --toolchain=${{ matrix.toolchain }} -p windows
elif [[ "${{ runner.os }}" == "Linux" ]]; then
xmake config --yes --enable_test=n --dev=n --release=y --mode=releasedbg --toolchain=${{ matrix.toolchain }}
elif [[ "${{ runner.os }}" == "macOS" ]]; then
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
xmake config --yes --enable_test=n --dev=n --release=y --mode=releasedbg --toolchain=${{ matrix.toolchain }} --sdk=/opt/homebrew/opt/llvm@20
fi
xmake pack -v
- 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

View File

@@ -1,96 +0,0 @@
name: release
permissions:
contents: write
on:
push:
tags:
- "v*"
pull_request:
branches: [main]
paths:
- ".github/workflows/release.yml"
jobs:
package:
strategy:
matrix:
include:
- os: windows-2025
artifact_name: clice.7z
asset_name: clice-x64-windows-msvc.7z
toolchain: clang-cl
- os: ubuntu-24.04
artifact_name: clice.tar.xz
asset_name: clice-x86_64-linux-gnu.tar.xz
toolchain: clang-20
- os: macos-15
artifact_name: clice.tar.xz
asset_name: clice-arm64-macos-darwin.tar.xz
toolchain: clang
runs-on: ${{ matrix.os }}
steps:
- name: Setup llvm & libstdc++ & cmake & ninja
if: matrix.os == 'ubuntu-24.04'
run: |
sudo apt remove llvm-16-linker-tools llvm-16 llvm-17-linker-tools llvm-17 llvm-18-linker-tools llvm-18
sudo apt update
sudo apt install -y gcc-14 g++-14 libstdc++-14-dev cmake ninja-build
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-14 100
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
sudo update-alternatives --set gcc /usr/bin/gcc-14
sudo update-alternatives --set g++ /usr/bin/g++-14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20 all
- name: Setup llvm@20
if: matrix.os == 'macos-15'
run: |
brew install llvm@20
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup xmake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: branch@master
actions-cache-folder: ".xmake-cache"
actions-cache-key: ${{ matrix.os }}
- name: Package
if: matrix.os != 'macos-15'
run: |
xmake config --yes --toolchain=${{ matrix.toolchain }} --enable_test=n --dev=n --release=y
xmake pack
- name: Package
if: matrix.os == 'macos-15'
run: |
xmake config --yes --toolchain=${{ matrix.toolchain }} --sdk=/opt/homebrew/opt/llvm@20 --enable_test=n --dev=n --release=y
xmake pack
- name: Install uv for integration tests
uses: astral-sh/setup-uv@v6
- name: Run tests
run: xmake test --verbose
- name: Upload binaries 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

View File

@@ -9,7 +9,6 @@ on:
- "src/**"
- "tests/**"
- "xmake.lua"
pull_request:
branches: [main]
paths:
@@ -17,55 +16,25 @@ on:
- "include/**"
- "src/**"
- "tests/**"
- "config/**"
- "xmake.lua"
jobs:
windows:
build:
strategy:
fail-fast: false
matrix:
os: [windows-2025]
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: branch@master
actions-cache-folder: ".xmake-cache"
actions-cache-key: ${{ matrix.os }}
package-cache: true
package-cache-key: ${{ matrix.os }}
build-cache: true
build-cache-key: ${{ matrix.os }}-${{ matrix.build_type }}
- name: Xmake configure
run: |
xmake config --yes --ci=y --toolchain=clang
- name: Build clice
run: |
xmake build --verbose --diagnosis --all
- name: Install uv for integration tests
uses: astral-sh/setup-uv@v6
- name: Run tests
run: xmake test --verbose
linux:
strategy:
matrix:
os: [ubuntu-24.04]
build_type: [release, debug]
runs-on: ${{ matrix.os }}
steps:
- name: Setup llvm & libstdc++ & cmake & ninja
- name: Setup dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install -y gcc-14 g++-14 libstdc++-14-dev
@@ -73,20 +42,25 @@ jobs:
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-14 100
sudo update-alternatives --set gcc /usr/bin/gcc-14
sudo update-alternatives --set g++ /usr/bin/g++-14
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20 all
sudo apt install -y cmake ninja-build
- name: Setup dependencies (MacOS)
if: runner.os == 'macOS'
env:
HOMEBREW_NO_AUTO_UPDATE: 1
run: |
brew install llvm@20 lld@20
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup xmake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: branch@master
xmake-version: 3.0.4
actions-cache-folder: ".xmake-cache"
actions-cache-key: ${{ matrix.os }}
package-cache: true
@@ -94,60 +68,33 @@ jobs:
build-cache: true
build-cache-key: ${{ matrix.os }}-${{ matrix.build_type }}
- name: Xmake configure
run: |
xmake config --yes --ci=y --mode=${{ matrix.build_type }} --toolchain=clang-20
- name: Build clice
shell: bash
run: |
if [[ "${{ runner.os }}" == "Windows" ]]; then
xmake config --yes --ci=y --mode=${{ matrix.build_type }} --toolchain=clang -p windows
elif [[ "${{ runner.os }}" == "Linux" ]]; then
xmake config --yes --ci=y --mode=${{ matrix.build_type }} --toolchain=clang-20
elif [[ "${{ runner.os }}" == "macOS" ]]; then
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
xmake config --yes --ci=y --mode=${{ matrix.build_type }} --toolchain=clang --sdk=/opt/homebrew/opt/llvm@20
fi
xmake build --verbose --diagnosis --all
- name: Install uv for integration tests
uses: astral-sh/setup-uv@v6
- name: Run tests
run: xmake test --verbose
macos:
strategy:
matrix:
os: [macos-15]
build_type: [release, debug]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup llvm
shell: bash
run: |
brew install llvm@20
# Workaround for macOS
if [[ "${{ runner.os }}" == "macOS" ]]; then
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
fi
- name: Setup xmake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: branch@master
actions-cache-folder: ".xmake-cache"
actions-cache-key: ${{ matrix.os }}
package-cache: true
package-cache-key: ${{ matrix.os }}
build-cache: true
build-cache-key: ${{ matrix.os }}-${{ matrix.build_type }}
- name: Xmake configure
run: |
xmake config --yes --ci=y --mode=${{ matrix.build_type }} --toolchain=clang --sdk=/opt/homebrew/opt/llvm@20
- name: Build clice
run: |
xmake build --verbose --diagnosis --all
- name: Install uv for integration tests
uses: astral-sh/setup-uv@v6
- name: Run tests
run: |
# Workaround for MacOS
export PATH="/opt/homebrew/opt/llvm@20/bin:/opt/homebrew/opt/lld@20/bin:$PATH"
xmake test --verbose
- name: Remove llvm package (Linux)
if: runner.os == 'Linux'
run: rm -rf /home/runner/.xmake/packages/c/clice-llvm

3
.gitignore vendored
View File

@@ -49,6 +49,9 @@ temp/
__pycache__/
.pytest_cache/
tests/unit/Local/
perf.data
flamegraph.svg
**/node_modules
docs/.vitepress/dist

View File

@@ -1,7 +1,6 @@
cmake_minimum_required(VERSION 3.20)
project(CLICE_PROJECT LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -34,9 +33,20 @@ endif()
if(WIN32)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
target_link_options(clice_options INTERFACE -fuse-ld=lld-link)
else()
target_link_options(clice_options INTERFACE -fuse-ld=lld)
target_link_options(clice_options INTERFACE
-fuse-ld=lld-link
-Wl,/OPT:REF
)
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
-Wl,--gc-sections
)
endif()
if (MSVC)
@@ -51,6 +61,8 @@ else()
-fno-exceptions
-Wno-deprecated-declarations
-Wno-undefined-inline
-ffunction-sections
-fdata-sections
)
endif()
@@ -69,6 +81,21 @@ add_custom_target(
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"
@@ -80,7 +107,7 @@ file(GLOB_RECURSE CLICE_SOURCES CONFIGURE_DEPENDS
"${PROJECT_SOURCE_DIR}/src/Support/*.cpp"
)
add_library(clice-core STATIC "${CLICE_SOURCES}")
add_dependencies(clice-core generate_flatbuffers_schema)
add_dependencies(clice-core generate_flatbuffers_schema generate_config)
target_include_directories(clice-core PUBLIC
"${PROJECT_SOURCE_DIR}/include"
@@ -101,12 +128,13 @@ target_link_libraries(clice PRIVATE clice-core)
install(TARGETS clice RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
message(STATUS "Copying resource directory for development build")
file(COPY
"${LLVM_INSTALL_PATH}/lib/clang/20/"
DESTINATION "${PROJECT_BINARY_DIR}/lib/clang/20"
file(
COPY "${LLVM_INSTALL_PATH}/lib/clang"
DESTINATION "${PROJECT_BINARY_DIR}/lib"
)
install(DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang/20/"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/clang/20"
install(
DIRECTORY "${LLVM_INSTALL_PATH}/lib/clang"
DESTINATION "${CMAKE_INSTALL_LIBDIR}"
)
if(CLICE_ENABLE_TEST)

View File

@@ -48,13 +48,6 @@ cl::opt<unsigned int> port{
cl::desc("The port to connect to"),
};
cl::opt<std::string> resource_dir{
"resource-dir",
cl::cat(category),
cl::value_desc("path"),
cl::desc(R"(The path of the clang resource directory, default is "../../lib/clang/version")"),
};
cl::opt<logging::ColorMode> log_color{
"log-color",
cl::cat(category),
@@ -69,75 +62,43 @@ cl::opt<logging::ColorMode> log_color{
cl::opt<logging::Level> log_level{
"log-level",
cl::cat(category),
cl::value_desc("trace|debug|info|warn|fatal"),
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, "fatal", "")),
clEnumValN(logging::Level::err, "error", ""),
clEnumValN(logging::Level::off, "off", "")),
cl::desc("The log level, default is info"),
};
void init_log() {
using namespace logging;
options.color = log_color;
options.level = log_level;
logging::create_stderr_logger("clice", logging::options);
}
/// Check the command line arguments and initialize the clice.
bool check_arguments(int argc, const char** argv) {
/// Hide unrelated options.
cl::HideUnrelatedOptions(category);
// Set version printer and parse command line options
cl::SetVersionPrinter([](llvm::raw_ostream& os) {
os << std::format("clice version: {}\nllvm version: {}\n",
clice::config::version,
clice::config::llvm_version);
});
cl::ParseCommandLineOptions(argc,
argv,
"clice is a new generation of language server for C/C++");
init_log();
for(int i = 0; i < argc; ++i) {
logging::info("argv[{}] = {}", i, argv[i]);
}
// Initialize resource directory
if(resource_dir.empty()) {
logging::info("No resource directory specified, using default resource directory");
// Try to initialize default resource directory
if(auto result = fs::init_resource_dir(argv[0]); !result) {
logging::warn("Cannot find default resource directory, because {}", result.error());
return false;
}
} else {
// Set and check the specified resource directory
fs::resource_dir = resource_dir.getValue();
if(fs::exists(fs::resource_dir)) {
logging::info("Resource directory found: {}", fs::resource_dir);
} else {
logging::warn("Resource directory not found: {}", fs::resource_dir);
return false;
}
}
return true;
}
} // 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++");
if(!check_arguments(argc, argv)) {
return 1;
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) {
LOGGING_FATAL("Cannot find default resource directory, because {}", result.error());
}
for(int i = 0; i < argc; ++i) {
LOGGING_INFO("argv[{}] = {}", i, argv[i]);
}
async::init();
@@ -151,13 +112,13 @@ int main(int argc, const char** argv) {
switch(mode) {
case Mode::Pipe: {
async::net::listen(loop);
logging::info("Server starts listening on stdin/stdout");
LOGGING_INFO("Server starts listening on stdin/stdout");
break;
}
case Mode::Socket: {
async::net::listen(host.c_str(), port, loop);
logging::info("Server starts listening on {}:{}", host.getValue(), port.getValue());
LOGGING_INFO("Server starts listening on {}:{}", host.getValue(), port.getValue());
break;
}
@@ -169,7 +130,7 @@ int main(int argc, const char** argv) {
async::run();
logging::info("clice exit normally!");
LOGGING_INFO("clice exit normally!");
return 0;
}

View File

@@ -18,7 +18,7 @@ namespace {
namespace cl = llvm::cl;
cl::OptionCategory unittest_category("Clice Unittest Options");
cl::OptionCategory unittest_category("clice Unittest Options");
cl::opt<std::string> test_dir{
"test-dir",
@@ -28,12 +28,6 @@ cl::opt<std::string> test_dir{
cl::cat(unittest_category),
};
cl::opt<std::string> resource_dir{
"resource-dir",
cl::desc("Resource dir path"),
cl::cat(unittest_category),
};
cl::opt<std::string> test_filter{
"test-filter",
cl::desc("A glob pattern to run subset of tests"),
@@ -190,7 +184,7 @@ int main(int argc, const char* argv[]) {
llvm::cl::HideUnrelatedOptions(unittest_category);
llvm::cl::ParseCommandLineOptions(argc, argv, "clice test\n");
logging::create_stderr_logger("clice", logging::options);
logging::stderr_logger("clice", logging::options);
if(!test_filter.empty()) {
if(auto result = GlobPattern::create(test_filter)) {
@@ -198,13 +192,9 @@ int main(int argc, const char* argv[]) {
}
}
if(!resource_dir.empty()) {
fs::resource_dir = resource_dir;
} else {
if(auto result = fs::init_resource_dir(argv[0]); !result) {
std::println("Failed to get resource directory, because {}", result.error());
return 1;
}
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;

View File

@@ -3,12 +3,12 @@ include_guard()
include(${CMAKE_CURRENT_LIST_DIR}/github.cmake)
# Check if LLVM version is supported
function(check_llvm_version llvm_ver OUTPUT_VAR)
if((NOT DEFINED llvm_ver) OR (llvm_ver STREQUAL ""))
function(check_llvm_version LOCAL_LLVM_VERSION LLVM_VERSION OUTPUT_VAR)
if((NOT DEFINED LOCAL_LLVM_VERSION) OR (LOCAL_LLVM_VERSION STREQUAL ""))
message(WARNING "LLVM version is not set.")
set(${OUTPUT_VAR} FALSE PARENT_SCOPE)
elseif(NOT (llvm_ver VERSION_GREATER_EQUAL "20.1" AND llvm_ver VERSION_LESS "20.2"))
message(WARNING "Unsupported LLVM version: ${llvm_ver}. Only LLVM 20.1.x is supported.")
elseif(NOT (LOCAL_LLVM_VERSION VERSION_GREATER_EQUAL LLVM_VERSION))
message(WARNING "Unsupported LLVM version: ${LOCAL_LLVM_VERSION}. Only LLVM ${LLVM_VERSION} is supported.")
set(${OUTPUT_VAR} FALSE PARENT_SCOPE)
else()
set(${OUTPUT_VAR} TRUE PARENT_SCOPE)
@@ -16,14 +16,14 @@ function(check_llvm_version llvm_ver OUTPUT_VAR)
endfunction()
# Look up LLVM version's corresponding commit SHA
function(lookup_commit llvm_ver OUTPUT_VAR)
set(LLVM_TAG "llvmorg-${llvm_ver}")
function(lookup_commit LLVM_VERSION OUTPUT_VAR)
set(LLVM_TAG "llvmorg-${LLVM_VERSION}")
github_lookup_tag_commit("llvm" "llvm-project" ${LLVM_TAG} COMMIT_SHA)
set(${OUTPUT_VAR} ${COMMIT_SHA} PARENT_SCOPE)
endfunction()
# Fetch private Clang header files from LLVM source
function(fetch_private_clang_files llvm_ver)
function(fetch_private_clang_files LLVM_VERSION)
set(PRIVATE_CLANG_FILE_LIST
"Sema/CoroutineStmtBuilder.h"
"Sema/TypeLocBuilder.h"
@@ -58,13 +58,13 @@ function(fetch_private_clang_files llvm_ver)
message(WARNING "Required private clang files incomplete, fetching from llvm-project source...")
# Get the commit SHA for this LLVM version
lookup_commit(${llvm_ver} LLVM_COMMIT)
lookup_commit(${LLVM_VERSION} LLVM_COMMIT)
if(LLVM_COMMIT STREQUAL "NOTFOUND")
message(WARNING "Failed to lookup commit for LLVM ${llvm_ver}, skipping private clang files download")
message(WARNING "Failed to lookup commit for LLVM ${LLVM_VERSION}, skipping private clang files download")
return()
endif()
message(STATUS "LLVM ${llvm_ver} corresponds to commit ${LLVM_COMMIT}")
message(STATUS "LLVM ${LLVM_VERSION} corresponds to commit ${LLVM_COMMIT}")
# Download missing files
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include/clang")
@@ -117,14 +117,14 @@ function(detect_llvm OUTPUT_VAR)
endfunction()
# Download and install prebuilt LLVM binaries with error checking
function(install_prebuilt_llvm llvm_ver)
function(install_prebuilt_llvm LLVM_VERSION)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.llvm")
# Determine platform-specific package name
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(LLVM_BUILD_TYPE "debug")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
set(LLVM_BUILD_TYPE "release")
set(LLVM_BUILD_TYPE "releasedbg")
else()
set(LLVM_BUILD_TYPE "release-lto")
endif()
@@ -146,7 +146,7 @@ function(install_prebuilt_llvm llvm_ver)
# Download if file does not exist
if(NOT EXISTS "${DOWNLOAD_PATH}")
message(STATUS "Downloading prebuilt LLVM package: ${LLVM_PACKAGE}")
set(DOWNLOAD_URL "https://github.com/clice-io/llvm-binary/releases/download/${llvm_ver}/${LLVM_PACKAGE}")
set(DOWNLOAD_URL "https://github.com/clice-io/clice-llvm/releases/download/${LLVM_VERSION}/${LLVM_PACKAGE}")
file(DOWNLOAD "${DOWNLOAD_URL}"
"${DOWNLOAD_PATH}"
STATUS DOWNLOAD_STATUS
@@ -187,7 +187,11 @@ function(install_prebuilt_llvm llvm_ver)
endfunction()
# Main function to set up LLVM for the project
function(setup_llvm)
function(setup_llvm LLVM_VERSION)
if(NOT DEFINED LLVM_VERSION OR LLVM_VERSION STREQUAL "")
message(FATAL_ERROR "setup_llvm() requires a LLVM_VERSION argument (e.g., '21.1.4').")
endif()
# Use existing LLVM installation if path is already set
if(DEFINED LLVM_INSTALL_PATH AND NOT LLVM_INSTALL_PATH STREQUAL "")
message(STATUS "LLVM_INSTALL_PATH is set to ${LLVM_INSTALL_PATH}, using it directly.")
@@ -197,14 +201,14 @@ function(setup_llvm)
set(LLVM_VERSION_OK false)
if (CMAKE_BUILD_TYPE STREQUAL "Release")
# Try to detect system LLVM
detect_llvm(LLVM_VERSION)
check_llvm_version("${LLVM_VERSION}" LLVM_VERSION_OK)
detect_llvm(LOCAL_LLVM_VERSION)
check_llvm_version("${LOCAL_LLVM_VERSION}" "${LLVM_VERSION}" LLVM_VERSION_OK)
endif()
# Download prebuilt LLVM if system version is not suitable
if(NOT LLVM_VERSION_OK)
set(LLVM_VERSION "20.1.5")
message(WARNING "System LLVM not found, version mismatch or incompatible build type, downloading prebuilt LLVM...")
message(WARNING "System LLVM not found, version mismatch or incompatible build type.")
message(WARNING "Downloading prebuilt LLVM ${LLVM_VERSION} ...")
install_prebuilt_llvm("${LLVM_VERSION}")
endif()

View File

@@ -2,7 +2,7 @@ include_guard()
include(${CMAKE_CURRENT_LIST_DIR}/llvm_setup.cmake)
setup_llvm()
setup_llvm("21.1.4")
get_filename_component(LLVM_INSTALL_PATH "${LLVM_INSTALL_PATH}" ABSOLUTE)
@@ -13,7 +13,6 @@ endif()
# set llvm include and lib path
add_library(llvm-libs INTERFACE IMPORTED)
message(STATUS "LLVM include path: ${LLVM_INSTALL_PATH}/include")
# add to include directories
target_include_directories(llvm-libs INTERFACE "${LLVM_INSTALL_PATH}/include")
@@ -23,14 +22,13 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
LLVMSupport
LLVMFrontendOpenMP
LLVMOption
LLVMTargetParser
clangAST
clangASTMatchers
clangBasic
clangDependencyScanning
clangDriver
clangFormat
clangFrontend
clangIndex
clangLex
clangSema
clangSerialization
@@ -69,11 +67,10 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
else()
file(GLOB LLVM_LIBRARIES CONFIGURE_DEPENDS "${LLVM_INSTALL_PATH}/lib/*${CMAKE_STATIC_LIBRARY_SUFFIX}")
target_link_libraries(llvm-libs INTERFACE ${LLVM_LIBRARIES})
target_compile_definitions(llvm-libs INTERFACE CLANG_BUILD_STATIC=1)
endif()
if(WIN32)
target_compile_definitions(llvm-libs INTERFACE "CLANG_BUILD_STATIC")
target_link_libraries(llvm-libs INTERFACE version ntdll)
endif()

View File

@@ -0,0 +1,11 @@
/* This generated file is for internal use. Do not include it from headers. */
#ifdef CLANG_TIDY_CONFIG_H
#error clang-tidy-config.h can only be included once
#else
#define CLANG_TIDY_CONFIG_H
// Clice currently doesn't support this configuration, and we use the same default value as clangd.
#define CLANG_TIDY_ENABLE_STATIC_ANALYZER 0
#endif

View File

@@ -3,64 +3,64 @@
"platform": "Windows",
"build_type": "Release",
"is_lto": false,
"filename": "x64-windows-msvc-release.7z",
"version": "20.1.5",
"sha256": "499b2e1e37c6dcccbc9d538cea5a222b552d599f54bb523adea8594d7837d02b"
"filename": "x64-windows-msvc-releasedbg.7z",
"version": "21.1.4",
"sha256": "02634ff12194994d93e9c76902866c03516c836ab7b55952933fd9ebcf039664"
},
{
"platform": "Windows",
"build_type": "Release",
"is_lto": true,
"filename": "x64-windows-msvc-release-lto.7z",
"version": "20.1.5",
"sha256": "0548c0e3f613d1ec853d493870e68c7d424d70442d144fb35b99dc65fc682918"
"filename": "x64-windows-msvc-releasedbg-lto.7z",
"version": "21.1.4",
"sha256": "7792cfd1e2d9240b49e3db81a6a04f33cbc44afa91e9a637c0490c28d4eee95c"
},
{
"platform": "Linux",
"build_type": "Debug",
"is_lto": false,
"filename": "x86_64-linux-gnu-debug.tar.xz",
"version": "20.1.5",
"sha256": "c04dddbe1d43d006f1ac52db01ab1776b8686fb8d4a1d13f2e07df37ae1ed47e"
"version": "21.1.4",
"sha256": "9c7b98e198ce1c5611e153c3602fb1dc03912f230bded99baaaa56ac1ea21cb4"
},
{
"platform": "Linux",
"build_type": "Release",
"is_lto": false,
"filename": "x86_64-linux-gnu-release.tar.xz",
"version": "20.1.5",
"sha256": "5ff442434e9c1fbe67c9c2bd13284ef73590aa984bb74bcdfcec4404e5074b70"
"filename": "x86_64-linux-gnu-releasedbg.tar.xz",
"version": "21.1.4",
"sha256": "99269acd2d9c5debf30062bf57d7bfd41154f2d0baee94abdb56b421c8e5b92c"
},
{
"platform": "Linux",
"build_type": "Release",
"is_lto": true,
"filename": "x86_64-linux-gnu-release-lto.tar.xz",
"version": "20.1.5",
"sha256": "37bc9680df5b766de6367c3c690fe8be993e94955341e63fb5ee6a3132080059"
"filename": "x86_64-linux-gnu-releasedbg-lto.tar.xz",
"version": "21.1.4",
"sha256": "e5a6c567e30cbe51e4b98151f52071d0013839e7b5eabe7a2a2767c8234d06b2"
},
{
"platform": "macosx",
"build_type": "Debug",
"is_lto": false,
"filename": "arm64-macosx-apple-debug.tar.xz",
"version": "20.1.5",
"sha256": "899d15d0678c1099bccb41098355b938d3bb6dd20870763758b70db01b31a709"
"version": "21.1.4",
"sha256": "ae5509d8d1cfc7441c34f6ee4fcac6eb31aae30d8b54a8f9ba3e9948a22fbc8e"
},
{
"platform": "macosx",
"build_type": "Release",
"is_lto": false,
"filename": "arm64-macosx-apple-release.tar.xz",
"version": "20.1.5",
"sha256": "47d89ed747b9946b4677ff902b5889b47d07b5cd92b0daf12db9abc6d284f955"
"filename": "arm64-macosx-apple-releasedbg.tar.xz",
"version": "21.1.4",
"sha256": "dc308b0057f472e82c2eb14b300db6ee58fb4160f3428948b5a9dcae931a3378"
},
{
"platform": "macosx",
"build_type": "Release",
"is_lto": true,
"filename": "arm64-macosx-apple-release-lto.tar.xz",
"version": "20.1.5",
"sha256": "57a58adcc0a033acd66dbf8ed1f6bcf4f334074149e37bf803fc6bf022d419d2"
"filename": "arm64-macosx-apple-releasedbg-lto.tar.xz",
"version": "21.1.4",
"sha256": "c92d0323ff83e678fec7496c8b024a64b1731c3840752be9a4ade3b99dfd52ff"
}
]

View File

@@ -6,165 +6,77 @@
- Linux
- MacOS
## Prerequisites
## Prerequisite
This section introduces the prerequisites for compiling clice.
### Toolchain
- clang >= 19
- c++23 compatible standard library
- cmake/xmake
- clang, lld >= 20
- c++23 **compatible** standard library
- MSVC STL >= 19.44(VS 2022 17.4)
- GCC libstdc++ >= 14
- Clang libc++ >= 20
clice uses C++23 as the language standard. Please ensure you have an available clang 19 or above compiler, as well as a standard library compatible with C++23.
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).
> clice can currently only be compiled with clang. In the future, we will improve this to allow compilation with gcc and msvc.
> 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.
### LLVM Libs
## CMake
- 20.1.5 <= llvm libs < 21
Use the following commands to build clice
Due to the complexity of C++ syntax, writing a new parser from scratch is unrealistic. clice calls clang's API to parse C++ source files and obtain AST, which means it needs to link llvm/clang libs. Additionally, since clice uses clang's private headers, these private headers are not available in llvm's binary release, so you cannot directly use the system's llvm package.
```shell
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build
```
If you can find the llvm commit corresponding to your system's llvm package, copy the following three files from that commit:
Optional build options:
- `clang/lib/Sema/CoroutineStmtBuilder.h`
- `clang/lib/Sema/TypeLocBuilder.h`
- `clang/lib/Sema/TreeTransform.h`
| 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. |
Copy them to `LLVM_INSTALL_PATH/include/clang/Sema/`.
## XMake
Besides this method, there are two other ways to obtain the llvm libs required by clice:
1. Use our precompiled version
Use the following commands to build clice
```bash
# .github/workflows/cmake.yml
# Linux precompiled binary require glibc 2.35 (build on ubuntu 22.04)
$ mkdir -p ./.llvm
$ curl -L "https://github.com/clice-io/llvm-binary/releases/download/20.1.5/x86_64-linux-gnu-release.tar.xz" | tar -xJ -C ./.llvm
# MacOS precompiled binary require macos15+
$ mkdir -p ./.llvm
$ curl -L "https://github.com/clice-io/llvm-binary/releases/download/20.1.5/arm64-macosx-apple-release.tar.xz" | tar -xJ -C ./.llvm
# Windows precompiled binary only MD runtime support
$ curl -O -L "https://github.com/clice-io/llvm-binary/releases/download/20.1.5/x64-windows-msvc-release.7z"
$ 7z x x64-windows-msvc-release.7z "-o.llvm"
xmake f -c --mode=releasedbg --toolchain=clang
xmake build --all
```
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.
> [!IMPORTANT]
>
> For debug versions of llvm libs, we enabled address sanitizer during build, and address sanitizer depends on compiler rt, which is very sensitive to compiler versions. So if using debug versions, please ensure your clang's compiler rt version is **strictly consistent** with what we used during build.
> For debug builds of llvm libs, we enable address sanitizer. Address sanitizer depends on compiler-rt, which is highly sensitive to the compiler version.
>
> - Windows currently has no debug build of llvm libs because it doesn't support building clang as a dynamic library. Related progress can be found [here](https://discourse.llvm.org/t/llvm-is-buildable-as-a-windows-dll/87748)
> 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, definitely don't use apple clang
> - 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.
2. Compile llvm/clang from scratch
This is the most recommended approach, ensuring environment consistency and avoiding crash issues caused by ABI inconsistencies. We provide a script for building the llvm libs required by clice: [build-llvm-libs.py](https://github.com/clice-io/clice/blob/main/scripts/build-llvm-libs.py).
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
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).
### GCC Toolchain
clice requires GCC libstdc++ >= 14. You could use a different GCC toolchain and also link statically against its libstdc++:
```bash
cmake .. -DCMAKE_C_FLAGS="--gcc-toolchain=/usr/local/gcc-14.3.0/" \
-DCMAKE_CXX_FLAGS="--gcc-toolchain=/usr/local/gcc-14.3.0/" \
-DCMAKE_EXE_LINKER_FLAGS="-static-libgcc -static-libstdc++"
```
## Building
After handling the prerequisites, you can start building clice. We provide two build methods: cmake/xmake.
### CMake
Below are the cmake parameters supported by clice:
- `LLVM_INSTALL_PATH` specifies the installation path of llvm libs
- `CLICE_ENABLE_TEST` whether to build clice's unit tests
For example:
```bash
$ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DLLVM_INSTALL_PATH="./.llvm" -DCLICE_ENABLE_TEST=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
$ cmake --build build
```
### Xmake
Use the following command to build clice:
```bash
$ xmake f -c --dev=true --mode=debug --toolchain=clang --llvm="./.llvm" --enable_test=true
$ xmake build --all
```
> --llvm is optional. If not specified, xmake will automatically download our precompiled binary
## Building Docker Image
Use the following command to build docker image:
```bash
$ docker build -t clice .
```
Run docker image by running the following command:
```bash
$ docker run --rm -it clice --help
OVERVIEW: clice is a new generation of language server for C/C++
...
```
The directory structure of the docker image is as follows:
```
/opt/clice
├── bin
│ ├── clice -> /usr/local/bin/clice
├── include
├── lib
├── LICENSE
├── README.md
```
Hint: launch clice in the docker container by running the following command:
```bash
$ docker run --rm -it --entrypoint bash clice
```
## Development Container
We provide Docker images as a pre-configured environment to streamline the setup process. You can use the following scripts to manage the development container. These scripts can be run from the project root directory.
```bash
# Build the development image
./docker/linux/build.sh
# Run the container with the clang toolchain
./docker/linux/run.sh --compiler clang
# Run the container with the gcc toolchain
./docker/linux/run.sh --compiler gcc
# Reset the container (stops and removes the existing one)
./docker/linux/run.sh --reset
```
> [!NOTE]
> This feature is currently in a preview stage and only supports Linux. Windows support will be provided in the future, and the functionality may be subject to change.
You can also refer to LLVM's official build tutorial: [Building LLVM with CMake](https://llvm.org/docs/CMake.html).

View File

@@ -8,159 +8,74 @@
## Prerequisite
本小节介绍编译 clice 的前置依赖。
### Toolchain
- clang >= 19
- c++23 compitable standard library
- cmake/xmake
- clang, lld >= 20
- c++23 **compatible** standard library
- MSVC STL >= 19.44(VS 2022 17.4)
- GCC libstdc++ >= 14
- Clang libc++ >= 20
clice 使用 C++23 作为语言标准 ,请确保有可用的 clang 19 以及以上的编译器,以及兼容 C++23 的标准库。
clice 使用 C++23 作为语言标准,请确保有可用的 clang 20 以及以上的编译器,以及兼容 C++23 的标准库。clice 依赖 lld 作为链接器。请确保你的 clang 工具链可以找到它(通常 clang 发行版会自带 lld或者你需要单独安装 lld-20 包)。
> clice 暂时只能使用 clang 编译,在未来我们会改进这一点,使其能使用 gcc 和 msvc 编译
> clice 目前只保证能使用 clang 编译CI 测试保证)。对于 gcc 和 msvc 的兼容,我们尽力而为,但不会在 CI 中添加对应的测试。如果遇到任何问题,欢迎贡献
### LLVM Libs
## CMake
- 20.1.5 <= llvm libs < 21
使用如下的命令构建 clice
由于 C++ 的语法太过复杂,自己编写一个新的 parser 是不现实的。clice 调用 clang 的 API 来 parse C++ 源文件获取 AST这意味它需要链接 llvm/clang libs。另外由于 clice 使用了 clang 的私有头文件,这些私有头文件在 llvm 发布的 binary release 中是没有的,所以不能直接使用系统的 llvm package。
```shell
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
cmake --build build
```
如果你能找到系统的 llvm package 对应的 llvm commit将该 commit 下的如下三个文件
可选的构建选项:
- `clang/lib/Sema/CoroutineStmtBuilder.h`
- `clang/lib/Sema/TypeLocBuilder.h`
- `clang/lib/Sema/TreeTransform.h`
| 选项 | 默认值 | 效果 |
| -------------------- | ------ | ----------------------------------------------------------------------------------------------------- |
| 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/include/clang/Sema/` 中即可。
## XMake
除了这种方法以外,还有两种办法获取 clice 所需的 llvm libs
1. 使用我们提供的预编译版本
使用如下的命令即可构建 clice
```bash
# .github/workflows/cmake.yml
# Linux precompiled binary require glibc 2.35 (build on ubuntu 22.04)
$ mkdir -p ./.llvm
$ curl -L "https://github.com/clice-io/llvm-binary/releases/download/20.1.5/x86_64-linux-gnu-release.tar.xz" | tar -xJ -C ./.llvm
# MacOS precompiled binary require macos15+
$ mkdir -p ./.llvm
$ curl -L "https://github.com/clice-io/llvm-binary/releases/download/20.1.5/arm64-macosx-apple-release.tar.xz" | tar -xJ -C ./.llvm
# Windows precompiled binary only MD runtime support
$ curl -O -L "https://github.com/clice-io/llvm-binary/releases/download/20.1.5/x64-windows-msvc-release.7z"
$ 7z x x64-windows-msvc-release.7z "-o.llvm"
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 然后使用,
> [!IMPORTANT]
>
> 对于 debug 版本的 llvm libs构建的时候我们开启了 address sanitizer而 address sanitizer 依赖于 compiler rt它对编译器版本十分敏感。所以如果使用 debug 版本,请确保你的 clang 的 compiler rt 版本和我们构建的时候**严格一致**。
>
> - Windows 暂时 debug 构建的 llvm libs因为它不支持将 clang 构建为动态库,相关的进展可以在 [这里](https://discourse.llvm.org/t/llvm-is-buildable-as-a-windows-dll/87748) 找到
> - Windows 暂时没有 debug 构建的 llvm libs因为它不支持将 clang 构建为动态库,相关的进展在 [这里](https://github.com/clice-io/clice/issues/42) 跟踪
> - Linux 使用 clang20
> - MacOS 使用 homebrew llvm@20一定不要使用 apple clang
> - 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 的环境保持严格一致。
2. 自己从头编译 llvm/clang
这是最推荐的方式,可以保证环境一致性,避免因为 ABI 不一致而导致的崩溃问题。我们提供了一个脚本,用于构建 clice 所需要的 llvm libs[build-llvm-libs.py](https://github.com/clice-io/clice/blob/main/scripts/build-llvm-libs.py)。
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
cd llvm-project
python3 <clice>/scripts/build-llvm-libs.py debug
```
也可以参考 llvm 的官方构建教程 [Building LLVM with CMake](https://llvm.org/docs/CMake.html)。
### GCC Toolchain
clice 要求 GCC libstdc++ >= 14。以下命令使用不同的 GCC 工具链并静态链接其 libstdc++
```bash
cmake .. -DCMAKE_C_FLAGS="--gcc-toolchain=/usr/local/gcc-14.3.0/" \
-DCMAKE_CXX_FLAGS="--gcc-toolchain=/usr/local/gcc-14.3.0/" \
-DCMAKE_EXE_LINKER_FLAGS="-static-libgcc -static-libstdc++"
```
## Building
在处理好前置依赖之后,可以开始构建 clice 了,我们提供 cmake/xmake 两种构建方式。
### CMake
下面是 clice 支持的 cmake 参数
- `LLVM_INSTALL_PATH` 用于指定 llvm libs 的安装路径
- `CLICE_ENABLE_TEST` 是否构建 clice 的单元测试
例如
```bach
$ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DLLVM_INSTALL_PATH="./.llvm" -DCLICE_ENABLE_TEST=ON -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
$ cmake --build build
```
### Xmake
使用如下的命令即可构建 clice
```bash
$ xmake f -c --dev=true --mode=debug --toolchain=clang --llvm="./.llvm" --enable_test=true
$ xmake build --all
```
> --llvm 是可选的如果不指定的话xmake 会自动下载我们编译好的预编译二进制
## Dev Container
我们提供了 docker 镜像作为预装环境解决方案,可以有效地解决环境配置问题,可通过下列命令使用(不限脚本调用路径,可以直接运行 ./build.sh
```bash
# construct container
docker/linux/build.sh
# run clang container
docker/linux/run.sh --compiler clang
# run gcc container
docker/linux/run.sh --compiler gcc
# reset container(delete exist container and reset)
docker/linux/run.sh --reset
```
> [!NOTE]
> 当前该功能仍处于 Preview 阶段,仅支持 Linux后续会提供 Windows 平台版本,并可能存在功能改动
## Building Docker Image
使用以下命令构建 docker 镜像:
```bash
$ docker build -t clice .
```
运行 docker 镜像:
```bash
$ docker run --rm -it clice --help
OVERVIEW: clice is a new generation of language server for C/C++
...
```
docker 镜像的目录结构如下:
```
/opt/clice
├── bin
│ ├── clice -> /usr/local/bin/clice
├── include
├── lib
├── LICENSE
├── README.md
```
提示:可以使用以下命令进入 clice 容器:
```bash
$ docker run --rm -it --entrypoint bash clice

View File

@@ -45,7 +45,14 @@ public:
}
lookup_result lookup(const clang::DependentTemplateSpecializationType* type) {
return lookup(type->getQualifier(), type->getIdentifier());
auto& template_name = type->getDependentTemplateName();
auto identifier = template_name.getName().getIdentifier();
if(identifier) {
return lookup(template_name.getQualifier(), identifier);
} else {
/// FIXME: Operators does't have a name.
return {};
}
}
lookup_result lookup(const clang::DependentScopeDeclRefExpr* expr) {

View File

@@ -627,7 +627,6 @@ public:
}
case clang::NestedNameSpecifier::TypeSpec:
case clang::NestedNameSpecifier::TypeSpecWithTemplate:
case clang::NestedNameSpecifier::Global:
case clang::NestedNameSpecifier::Super: {
break;

View File

@@ -33,6 +33,8 @@ clang::SourceLocation get(clang::SourceRange range) {
}
}
class Lexer;
} // namespace clang
namespace clice {
@@ -63,15 +65,17 @@ struct LocalSourceRange {
}
};
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_preprocessor_directive = false;
bool is_pp_keyword = false;
/// The kind of this token.
clang::tok::TokenKind kind;
TokenKind kind;
/// The source range of this token.
LocalSourceRange range;
@@ -80,31 +84,35 @@ struct Token {
return range.valid();
}
llvm::StringRef name() {
llvm::StringRef name() const {
return clang::tok::getTokenName(kind);
}
llvm::StringRef text(llvm::StringRef content) {
llvm::StringRef text(llvm::StringRef content) const {
assert(range.valid() && "Invalid source range");
return content.substr(range.begin, range.end - range.begin);
}
bool is_eof() {
bool is_eod() const {
return kind == clang::tok::eod;
}
bool is_eof() const {
return kind == clang::tok::eof;
}
bool is_identifier() {
bool is_identifier() const {
return kind == clang::tok::raw_identifier;
}
bool is_directive_hash() {
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() {
bool is_header_name() const {
return kind == clang::tok::header_name;
}
};
@@ -120,6 +128,10 @@ public:
Lexer(Lexer&&) = delete;
Lexer& operator= (const Lexer&) = delete;
Lexer& operator= (Lexer&&) = delete;
~Lexer();
void lex(Token& token);
@@ -133,8 +145,21 @@ public:
/// 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(clang::tok::TokenKind kind);
Token advance_until(TokenKind kind);
private:
/// If this is set to false, the lexer will emit tok::eod at the end
@@ -142,11 +167,13 @@ private:
bool ignore_end_of_directive = true;
/// Whether we are lexing the preprocessor directive.
bool parse_preprocessor_directive = false;
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;
@@ -159,7 +186,7 @@ private:
/// The lexed content.
llvm::StringRef content;
void* impl;
std::unique_ptr<clang::Lexer> lexer;
};
} // namespace clice

View File

@@ -15,6 +15,12 @@ bool is_templated(const clang::Decl* decl);
/// Check whether the decl is anonymous.
bool is_anonymous(const clang::NamedDecl* decl);
/// Checks whether the location is inside the main file.
bool is_inside_main_file(clang::SourceLocation loc, const clang::SourceManager& sm);
/// Checks whether the decl is an implicit template instantiation.
bool is_implicit_template_instantiation(const clang::NamedDecl* decl);
/// Return the decl where it is instantiated from. If could be a template decl
/// or a member of a class template. If the decl is a full specialization, return
/// itself.

View File

@@ -198,8 +198,10 @@ public:
}
};
template <>
struct promise_result<void> {
// 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 {}
};

View File

@@ -2,6 +2,7 @@
#include <expected>
#include "Toolchain.h"
#include "Support/Enum.h"
#include "Support/Format.h"
@@ -13,16 +14,10 @@
namespace clice {
struct CommandOptions {
/// Ignore unknown commands.
/// Ignore unknown commands arguments.
bool ignore_unknown = true;
/// 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;
/// Attach resource directory to the command.
/// Inject resource directory to the command.
bool resource_dir = false;
/// Query the compiler driver for additional information, such as system includes and target.
@@ -31,196 +26,99 @@ struct CommandOptions {
/// 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 {
Unchange,
Create,
Update,
Delete,
};
struct DriverInfo {
/// The target of this driver.
llvm::StringRef target;
/// The default system includes of this driver.
llvm::ArrayRef<const char*> system_includes;
};
struct UpdateInfo {
/// The kind of update.
UpdateKind kind;
llvm::StringRef file;
};
struct LookupInfo {
llvm::StringRef directory;
std::vector<const char*> arguments;
/// The include arguments indices in the arguments list.
std::vector<std::uint32_t> include_indices;
};
class CompilationDatabase {
public:
using Self = CompilationDatabase;
enum class UpdateKind : std::uint8_t {
Unchange,
Create,
Update,
Delete,
};
struct CommandInfo {
/// TODO: add sysroot or no stdinc command info.
llvm::StringRef directory;
/// The canonical command list.
llvm::ArrayRef<const char*> arguments;
/// The extra command @...
llvm::StringRef response_file;
/// The original index of the response file argument in the command list.
std::uint32_t response_file_index = 0;
};
struct DriverInfo {
/// The target of this driver.
llvm::StringRef target;
/// The default system includes of this driver.
llvm::ArrayRef<const char*> system_includes;
};
struct UpdateInfo {
/// The kind of update.
UpdateKind kind;
llvm::StringRef file;
};
struct LookupInfo {
llvm::StringRef directory;
std::vector<const char*> arguments;
};
struct QueryDriverError {
struct ErrorKind : refl::Enum<ErrorKind> {
enum Kind : std::uint8_t {
NotFoundInPATH,
FailToCreateTempFile,
InvokeDriverFail,
OutputFileNotReadable,
InvalidOutputFormat,
};
using Enum::Enum;
};
ErrorKind kind;
std::string detail;
};
CompilationDatabase();
CompilationDatabase(const CompilationDatabase&) = delete;
CompilationDatabase(CompilationDatabase&& other);
CompilationDatabase& operator= (const CompilationDatabase&) = delete;
CompilationDatabase& operator= (CompilationDatabase&& other);
~CompilationDatabase();
auto save_string(this Self& self, llvm::StringRef string) -> llvm::StringRef;
private:
struct Impl;
auto save_cstring_list(this Self& self, llvm::ArrayRef<const char*> arguments)
-> llvm::ArrayRef<const char*>;
using Self = CompilationDatabase;
public:
/// Get an the option for specific argument.
static std::optional<std::uint32_t> get_option_id(llvm::StringRef argument);
auto save_string(llvm::StringRef string) -> llvm::StringRef;
/// Query the compiler driver and return its driver info.
auto query_driver(this Self& self, llvm::StringRef driver)
-> std::expected<DriverInfo, QueryDriverError>;
auto query_driver(llvm::StringRef driver)
-> std::expected<DriverInfo, toolchain::QueryDriverError>;
/// Update with arguments.
auto update_command(this Self& self,
llvm::StringRef directory,
auto update_command(llvm::StringRef directory,
llvm::StringRef file,
llvm::ArrayRef<const char*> arguments) -> UpdateInfo;
/// Update with full command.
auto update_command(this Self& self,
llvm::StringRef directory,
llvm::StringRef file,
llvm::StringRef command) -> UpdateInfo;
auto update_command(llvm::StringRef directory, llvm::StringRef file, llvm::StringRef command)
-> UpdateInfo;
/// Update commands from json file and return all updated file.
auto load_commands(this Self& self, llvm::StringRef json_content, llvm::StringRef workspace)
auto load_commands(llvm::StringRef json_content, llvm::StringRef workspace)
-> std::expected<std::vector<UpdateInfo>, std::string>;
auto process_command(this Self& self,
llvm::StringRef file,
const CommandInfo& info,
const CommandOptions& options) -> std::vector<const char*>;
/// Get compile command from database. `file` should has relative path of workspace.
auto get_command(this Self& self, llvm::StringRef file, CommandOptions options = {})
-> LookupInfo;
/// Load compile commands from given directories. If no valid commands are found,
/// search recursively from the workspace directory.
auto load_compile_database(this Self& self,
llvm::ArrayRef<std::string> compile_commands_dirs,
auto load_compile_database(llvm::ArrayRef<std::string> compile_commands_dirs,
llvm::StringRef workspace) -> void;
auto commands_size() {
return command_infos.size();
}
/// Get compile command from database. `file` should has relative path of workspace.
auto lookup(llvm::StringRef file, CommandOptions options = {}) -> LookupInfo;
auto begin() {
return command_infos.begin();
}
auto end() {
return command_infos.end();
}
std::vector<const char*> files();
private:
/// If file not found in CDB file, try to guess commands or use the default case.
auto guess_or_fallback(this Self& self, llvm::StringRef file) -> LookupInfo;
private:
/// The opaque handle of `ArgumentParser`.
void* parser;
/// The memory pool to hold all cstring and command list.
llvm::BumpPtrAllocator allocator;
/// A cache between input string and its cache cstring
/// in the allocator, make sure end with `\0`.
llvm::DenseSet<llvm::StringRef> string_cache;
/// A cache between input command and its cache array
/// in the allocator.
llvm::DenseSet<llvm::ArrayRef<const char*>> arguments_cache;
/// The clang options we want to filter in all cases, like -c and -o.
llvm::DenseSet<std::uint32_t> filtered_options;
/// A map between file path and its canonical command list.
llvm::DenseMap<const char*, CommandInfo> command_infos;
/// A map between driver path and its query driver info.
llvm::DenseMap<const char*, DriverInfo> driver_infos;
std::unique_ptr<Impl> self;
};
} // namespace clice
namespace llvm {
template <>
struct DenseMapInfo<llvm::ArrayRef<const char*>> {
using T = llvm::ArrayRef<const char*>;
inline static T getEmptyKey() {
return T(reinterpret_cast<T::const_pointer>(~0), T::size_type(0));
}
inline static T getTombstoneKey() {
return T(reinterpret_cast<T::const_pointer>(~1), T::size_type(0));
}
static unsigned getHashValue(const T& value) {
return llvm::hash_combine_range(value.begin(), value.end());
}
static bool isEqual(const T& lhs, const T& rhs) {
return lhs == rhs;
}
};
} // namespace llvm
template <>
struct std::formatter<clice::CompilationDatabase::QueryDriverError> :
std::formatter<std::string_view> {
template <typename FormatContext>
auto format(const clice::CompilationDatabase::QueryDriverError& e, FormatContext& ctx) const {
return std::format_to(ctx.out(), "{} {}", e.kind.name(), e.detail);
}
};

View File

@@ -15,6 +15,9 @@ struct CompilationParams {
/// The kind of this compilation.
CompilationUnit::Kind kind;
/// Whether to run clang-tidy.
bool clang_tidy = false;
/// Output file path.
llvm::SmallString<128> output_file;

View File

@@ -2,7 +2,11 @@
#include <cstdint>
#include <string>
#include "AST/SourceCode.h"
#include "Compiler/Tidy.h"
#include "clang/Basic/Diagnostic.h"
namespace clang {
class DiagnosticConsumer;
@@ -52,6 +56,11 @@ struct DiagnosticID {
bool is_unused() const;
};
class DiagnosticCollector : public clang::DiagnosticConsumer {
public:
tidy::ClangTidyChecker* checker = nullptr;
};
struct Diagnostic {
/// The diagnostic id.
DiagnosticID id;
@@ -66,7 +75,8 @@ struct Diagnostic {
/// The error message of this diagnostic.
std::string message;
static clang::DiagnosticConsumer* create(std::shared_ptr<std::vector<Diagnostic>> diagnostics);
static std::unique_ptr<DiagnosticCollector>
create(std::shared_ptr<std::vector<Diagnostic>> diagnostics);
};
} // namespace clice

32
include/Compiler/Scan.h Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include <string>
#include <vector>
#include "llvm/ADT/StringRef.h"
#include "AST/SourceCode.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

View File

@@ -2,9 +2,23 @@
#include "llvm/ADT/StringRef.h"
#include <memory>
namespace clang {
class CompilerInstance;
}
namespace clice::tidy {
bool is_registered_tidy_check(llvm::StringRef check);
std::optional<bool> is_fast_tidy_check(llvm::StringRef check);
struct TidyParams {};
class ClangTidyChecker;
/// Configure to run clang-tidy on the given file.
std::unique_ptr<ClangTidyChecker> configure(clang::CompilerInstance& instance,
const TidyParams& params);
} // namespace clice::tidy

View File

@@ -0,0 +1,51 @@
#pragma once
#include <expected>
#include "Support/Enum.h"
#include "Support/Format.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
namespace clice::toolchain {
struct QueryDriverError {
struct ErrorKind : refl::Enum<ErrorKind> {
enum Kind : std::uint8_t {
NotFoundInPATH,
NotImplemented,
FailToCreateTempFile,
InvokeDriverFail,
OutputFileNotReadable,
InvalidOutputFormat,
};
using Enum::Enum;
};
ErrorKind kind;
std::string detail;
};
struct QueryResult {
std::string target;
llvm::SmallVector<std::string, 8> includes;
};
enum class Kind {};
struct Toolchain {};
/// Query toolchain info according to the original compilation arguments.
Toolchain query_toolchain(llvm::ArrayRef<const char*> arguments);
auto query_driver(llvm::StringRef driver) -> std::expected<QueryResult, QueryDriverError>;
} // namespace clice::toolchain
template <>
struct std::formatter<clice::toolchain::QueryDriverError> : std::formatter<std::string_view> {
template <typename FormatContext>
auto format(const clice::toolchain::QueryDriverError& e, FormatContext& ctx) const {
return std::format_to(ctx.out(), "{} {}", e.kind.name(), e.detail);
}
};

View File

@@ -5,6 +5,7 @@
#include "Config.h"
#include "Convert.h"
#include "Async/Async.h"
#include "Compiler/Command.h"
#include "Index/MergedIndex.h"
@@ -21,8 +22,10 @@ class CompilationUnit;
class Indexer {
public:
Indexer(CompilationDatabase& database, config::Config& config) :
database(database), config(config) {}
Indexer(CompilationDatabase& database,
config::Config& config,
const PositionEncodingKind& kind) :
database(database), config(config), encoding_kind(kind) {}
async::Task<> index(llvm::StringRef path);
@@ -70,6 +73,8 @@ private:
config::Config& config;
const PositionEncodingKind& encoding_kind;
index::ProjectIndex project_index;
PathMapping mapping;

View File

@@ -230,7 +230,7 @@ private:
/// All registered LSP callbacks.
llvm::StringMap<Callback> callbacks;
PositionEncodingKind kind;
PositionEncodingKind kind = PositionEncodingKind::UTF16;
std::string workspace;

View File

@@ -39,15 +39,15 @@ using namespace llvm::sys::fs;
inline std::string resource_dir = "";
inline std::expected<void, std::error_code> init_resource_dir(llvm::StringRef execute) {
inline std::expected<void, std::string> init_resource_dir(llvm::StringRef execute) {
llvm::SmallString<128> path;
path::append(path, path::parent_path(execute), "..");
path::append(path, "lib", "clang", "20");
path::append(path, "lib", "clang", "21");
if(auto error = real_path(path, path)) {
return std::unexpected(error);
return std::unexpected(std::format("{}:{}", error, path.str()));
}
resource_dir = path.str();
return std::expected<void, std::error_code>();
return std::expected<void, std::string>();
}
inline std::expected<std::string, std::error_code> createTemporaryFile(llvm::StringRef prefix,

View File

@@ -73,16 +73,31 @@ template <>
struct std::formatter<clice::json::Value> : std::formatter<llvm::StringRef> {
using Base = std::formatter<llvm::StringRef>;
int indent = 0;
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) {
return Base::parse(ctx);
auto it = ctx.begin();
auto end = ctx.end();
if(it == end) {
return it;
}
int parsed_indent = 0;
while(it != end && *it >= '0' && *it <= '9') {
parsed_indent = parsed_indent * 10 + (*it - '0');
++it;
}
indent = parsed_indent;
return it;
}
template <typename FormatContext>
auto format(const clice::json::Value& value, FormatContext& ctx) const {
llvm::SmallString<128> buffer;
llvm::raw_svector_ostream os{buffer};
os << value;
llvm::json::OStream(os, indent).value(value);
return Base::format(buffer, ctx);
}
};

View File

@@ -175,7 +175,7 @@ struct Serde<llvm::StringRef> {
}
};
template <std::size_t N>
template <unsigned N>
struct Serde<llvm::SmallString<N>> {
using V = llvm::SmallString<N>;

View File

@@ -9,15 +9,22 @@ using Level = spdlog::level::level_enum;
using ColorMode = spdlog::color_mode;
struct Options {
/// The logging level.
Level level = Level::info;
/// The logging color.
ColorMode color = ColorMode::automatic;
/// If enable, we will record the logs of console sink and replay it
/// when create a new sink,
bool replay_console = true;
};
extern Options options;
void create_stderr_logger(std::string_view name, const Options& options);
void stderr_logger(std::string_view name, const Options& options);
void create_file_loggger(std::string_view name, std::string_view dir, const Options& options);
void file_loggger(std::string_view name, std::string_view dir, const Options& options);
template <typename... Args>
struct logging_rformat {
@@ -72,10 +79,27 @@ void warn(logging_format<Args...> fmt, Args&&... args) {
}
template <typename... Args>
void fatal [[noreturn]] (logging_format<Args...> fmt, Args&&... args) {
void err(logging_format<Args...> fmt, Args&&... args) {
logging::log(spdlog::level::err, fmt.location, fmt.str, std::forward<Args>(args)...);
}
template <typename... Args>
void critical [[noreturn]] (logging_format<Args...> fmt, Args&&... args) {
logging::log(spdlog::level::critical, fmt.location, fmt.str, std::forward<Args>(args)...);
spdlog::shutdown();
std::exit(1);
std::abort();
}
} // namespace clice::logging
#define LOGGING_MESSAGE(name, fmt, ...) \
if(clice::logging::options.level <= clice::logging::Level::name) { \
clice::logging::name(fmt __VA_OPT__(, ) __VA_ARGS__); \
}
#define LOGGING_TRACE(fmt, ...) LOGGING_MESSAGE(trace, fmt, __VA_ARGS__)
#define LOGGING_DEBUG(fmt, ...) LOGGING_MESSAGE(debug, fmt, __VA_ARGS__)
#define LOGGING_INFO(fmt, ...) LOGGING_MESSAGE(info, fmt, __VA_ARGS__)
#define LOGGING_WARN(fmt, ...) LOGGING_MESSAGE(warn, fmt, __VA_ARGS__)
#define LOGGING_ERROR(fmt, ...) LOGGING_MESSAGE(err, fmt, __VA_ARGS__)
#define LOGGING_FATAL(fmt, ...) clice::logging::critical(fmt __VA_OPT__(, ) __VA_ARGS__);

View File

@@ -300,7 +300,7 @@ template <typename... Ts>
struct Struct<Inheritance<Ts...>> {
constexpr inline static bool reflectable_struct = (refl::reflectable_struct<Ts> && ...);
constexpr static std::size_t member_count = (Struct<Ts>::member_count + ...);
constexpr static std::size_t member_count = (impl::member_count<Ts>() + ...);
template <typename Object>
constexpr static auto collect_members(Object&& object) {

View File

@@ -41,7 +41,7 @@ struct Tester {
options.resource_dir = true;
options.query_driver = true;
options.suppress_logging = true;
params.arguments = database.get_command(src_path, options).arguments;
params.arguments = database.lookup(src_path, options).arguments;
for(auto& [file, source]: sources.all_files) {
if(file == src_path) {
@@ -77,7 +77,7 @@ struct Tester {
options.resource_dir = true;
options.query_driver = true;
options.suppress_logging = true;
params.arguments = database.get_command(src_path, options).arguments;
params.arguments = database.lookup(src_path, options).arguments;
auto path = fs::createTemporaryFile("clice", "pch");
if(!path) {

View File

@@ -318,7 +318,14 @@ public:
TD = TST->getTemplateName().getAsTemplateDecl();
args = TST->template_arguments();
} else if(auto DTST = type->getAs<clang::DependentTemplateSpecializationType>()) {
if(auto decl = preferred(lookup(DTST->getQualifier(), DTST->getIdentifier()))) {
auto& template_name = DTST->getDependentTemplateName();
/// FIXME: operators does't have the name.
auto name = template_name.getName().getIdentifier();
if(!name) {
return {};
}
if(auto decl = preferred(lookup(template_name.getQualifier(), name))) {
TD = decl;
args = DTST->template_arguments();
}
@@ -371,9 +378,8 @@ public:
return lookup(type, name);
}
case clang::NestedNameSpecifier::TypeSpec:
case clang::NestedNameSpecifier::TypeSpecWithTemplate: {
/// If the prefix is `TypeSpec` or `TypeSpecWithTemplate`, it must be a type.
case clang::NestedNameSpecifier::TypeSpec: {
/// If the prefix is `TypeSpec`, it must be a type.
return lookup(clang::QualType(NNS->getAsType(), 0), name);
}
@@ -597,18 +603,16 @@ public:
/// Alloc::rebind<T>::other
auto prefix =
clang::NestedNameSpecifier::Create(context, nullptr, false, Alloc.getTypePtr());
clang::NestedNameSpecifier::Create(context, nullptr, Alloc.getTypePtr());
auto rebind = sema.getPreprocessor().getIdentifierInfo("rebind");
auto DTST = context.getDependentTemplateSpecializationType(
clang::ElaboratedTypeKeyword::None,
prefix,
rebind,
clang::DependentTemplateStorage(prefix, rebind, false),
arguments);
prefix =
clang::NestedNameSpecifier::Create(context, prefix, true, DTST.getTypePtr());
prefix = clang::NestedNameSpecifier::Create(context, prefix, DTST.getTypePtr());
auto other = sema.getPreprocessor().getIdentifierInfo("other");
auto DNT = context.getDependentNameType(clang::ElaboratedTypeKeyword::Typename,
@@ -624,6 +628,7 @@ public:
if(auto TST = Alloc->getAs<clang::TemplateSpecializationType>()) {
llvm::SmallVector<clang::TemplateArgument, 4> replaceArguments = {T};
return context.getTemplateSpecializationType(TST->getTemplateName(),
replaceArguments,
replaceArguments);
}
}
@@ -721,8 +726,14 @@ public:
arguments.push_back(arg.getArgument());
}
/// FIXME: operator does't have a name.
auto name = DTST->getDependentTemplateName().getName().getIdentifier();
if(!name) {
return clang::QualType();
}
/// Try resolve the hole.
if(auto result = hole(NNS, DTST->getIdentifier(), arguments); !result.isNull()) {
if(auto result = hole(NNS, name, arguments); !result.isNull()) {
resolved.try_emplace(DTST, result);
TLB.pushTrivial(context, result, {});
return result;
@@ -730,7 +741,7 @@ public:
/// The `lookup` may change the instantiation stack, save the current state.
auto state = stack.state();
if(auto decl = preferred(lookup(NNS, DTST->getIdentifier()))) {
if(auto decl = preferred(lookup(NNS, name))) {
/// FIXME: Current implementation results in duplicated lookup.
/// Cache the result of `lookup` to avoid duplicated lookup.
if(auto TATD = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(decl)) {
@@ -746,10 +757,10 @@ public:
}
/// FIXME: figure out here.
auto result = context.getDependentTemplateSpecializationType(DTST->getKeyword(),
NNS,
DTST->getIdentifier(),
arguments);
auto result = context.getDependentTemplateSpecializationType(
DTST->getKeyword(),
clang::DependentTemplateStorage(NNS, name, false),
arguments);
return TLB.push<clang::DependentTemplateSpecializationTypeLoc>(result).getType();
}

View File

@@ -24,12 +24,6 @@
namespace clice {
#ifdef NDEBUG
#define LOGGING_DEBUG(...)
#else
#define LOGGING_DEBUG(...) logging::debug(__VA_ARGS__)
#endif
namespace {
using Node = SelectionTree::Node;

View File

@@ -5,37 +5,27 @@
namespace clice {
std::uint32_t getTokenLength(const clang::SourceManager& SM, clang::SourceLocation location) {
return clang::Lexer::MeasureTokenLength(location, SM, {});
}
/// A fake location could be used to calculate the token location offset when lexer
/// runs in raw mode.
inline static clang::SourceLocation fake_loc = clang::SourceLocation::getFromRawEncoding(1);
static clang::SourceLocation fake_loc = clang::SourceLocation::getFromRawEncoding(1);
static clang::LangOptions default_opts;
Lexer::Lexer(llvm::StringRef content,
bool ignore_comments,
const clang::LangOptions* lang_opts,
bool ignore_end_of_directive) :
content(content), ignore_end_of_directive(ignore_end_of_directive) {
static clang::LangOptions default_opts;
auto lexer = new clang::Lexer(fake_loc,
lang_opts ? *lang_opts : default_opts,
content.begin(),
content.begin(),
content.end());
content(content), ignore_end_of_directive(ignore_end_of_directive),
lexer(new clang::Lexer(fake_loc,
lang_opts ? *lang_opts : default_opts,
content.begin(),
content.begin(),
content.end())) {
lexer->SetCommentRetentionState(!ignore_comments);
impl = lexer;
}
Lexer::~Lexer() {
delete static_cast<clang::Lexer*>(impl);
}
Lexer::~Lexer() = default;
void Lexer::lex(Token& token) {
auto lexer = static_cast<clang::Lexer*>(impl);
clang::Token raw_token;
if(parse_header_name) {
@@ -46,22 +36,33 @@ void Lexer::lex(Token& token) {
token.kind = raw_token.getKind();
token.is_at_start_of_line = raw_token.isAtStartOfLine();
token.is_preprocessor_directive = parse_preprocessor_directive;
token.is_pp_keyword = parse_pp_keyword;
auto offset = raw_token.getLocation().getRawEncoding() - fake_loc.getRawEncoding();
token.range = LocalSourceRange{offset, offset + raw_token.getLength()};
if(token.is_at_start_of_line) {
if(token.kind == clang::tok::hash) {
parse_preprocessor_directive = true;
lexer->setParsingPreprocessorDirective(true);
}
/// Reset parse_header_name state.
parse_header_name = false;
} else if(parse_preprocessor_directive) {
/// Preprocessor directive token only have one.
parse_preprocessor_directive = false;
if(token.kind == clang::tok::hash ||
(module_declaration_context && token.text(content) == "export")) {
/// Inform the lexer we are paring directive, then it will emit
/// eod(end of directive) token. When there is no end of line
/// at the end of file, it also emits eod(before eof).
parse_pp_keyword = true;
lexer->setParsingPreprocessorDirective(true);
} else if(module_declaration_context && token.text(content) == "module") {
/// If we already in module context, we regard module as directive keyword.
token.is_pp_keyword = true;
lexer->setParsingPreprocessorDirective(true);
} else {
/// When we find the first non directive line, module contexts end.
module_declaration_context = false;
}
} else if(parse_pp_keyword) {
/// Reset parse_pp_keyword state.
parse_pp_keyword = false;
parse_header_name = token.text(content) == "include";
}
}
@@ -95,7 +96,17 @@ Token Lexer::advance() {
return current_token;
}
Token Lexer::advance_until(clang::tok::TokenKind kind) {
std::optional<Token> Lexer::advance_if(llvm::function_ref<bool(const Token&)> callback) {
auto token = next();
if(callback(token)) {
return advance();
}
return std::nullopt;
}
Token Lexer::advance_until(TokenKind kind) {
while(true) {
auto token = advance();
if(token.kind == kind || token.is_eof()) {

View File

@@ -16,6 +16,13 @@
namespace clice::ast {
bool is_inside_main_file(clang::SourceLocation loc, const clang::SourceManager& sm) {
if(!loc.isValid())
return false;
clang::FileID fid = sm.getFileID(sm.getExpansionLoc(loc));
return fid == sm.getMainFileID() || fid == sm.getPreambleFileID();
};
bool is_definition(const clang::Decl* decl) {
if(auto VD = llvm::dyn_cast<clang::VarDecl>(decl)) {
return VD->isThisDeclarationADefinition();
@@ -58,6 +65,25 @@ bool is_anonymous(const clang::NamedDecl* decl) {
return name.isIdentifier() && !name.getAsIdentifierInfo();
}
template <class T>
bool is_template_specialization_kind(const clang::NamedDecl* decl,
clang::TemplateSpecializationKind kind) {
if(const auto* td = dyn_cast<T>(decl))
return td->getTemplateSpecializationKind() == kind;
return false;
}
inline bool is_template_specialization_kind(const clang::NamedDecl* decl,
clang::TemplateSpecializationKind kind) {
return is_template_specialization_kind<clang::FunctionDecl>(decl, kind) ||
is_template_specialization_kind<clang::CXXRecordDecl>(decl, kind) ||
is_template_specialization_kind<clang::VarDecl>(decl, kind);
}
bool is_implicit_template_instantiation(const clang::NamedDecl* decl) {
return is_template_specialization_kind(decl, clang::TSK_ImplicitInstantiation);
}
const static clang::CXXRecordDecl* getDeclContextForTemplateInstationPattern(const clang::Decl* D) {
if(const auto* CTSD = dyn_cast<clang::ClassTemplateSpecializationDecl>(D->getDeclContext())) {
return CTSD->getTemplateInstantiationPattern();

View File

@@ -122,7 +122,7 @@ handle::~handle() {
uv_fs_t request;
int error = uv_fs_close(async::loop, &request, file, nullptr);
if(error < 0) {
logging::warn("Failed to close file: {}", uv_strerror(error));
LOGGING_WARN("Failed to close file: {}", uv_strerror(error));
}
uv_fs_req_cleanup(&request);
}

View File

@@ -30,7 +30,7 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
/// If an error occurred while reading, we can't continue.
if(nread < 0) [[unlikely]] {
logging::fatal("An error occurred while reading: {0}", uv_strerror(nread));
LOGGING_FATAL("An error occurred while reading: {0}", uv_strerror(nread));
}
/// We have at most one connection and use default event loop. So there is no data race
@@ -57,7 +57,7 @@ void on_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) {
task.dispose();
} else {
/// If the message is invalid, we can't continue.
logging::fatal("Unexpected JSON input: {0}", result);
LOGGING_FATAL("Unexpected JSON input: {0}", result);
}
/// Remove the processed message from the buffer.
@@ -130,7 +130,7 @@ struct write {
uv_write(&req, writer, buf, 2, [](uv_write_t* req, int status) {
if(status < 0) {
logging::fatal("An error occurred while writing: {0}", uv_strerror(status));
LOGGING_FATAL("An error occurred while writing: {0}", uv_strerror(status));
}
auto& awaiter = uv_cast<struct write>(req);

View File

@@ -29,11 +29,11 @@ const std::error_category& category() {
/// Use source_location to log the file, line, and function name where the error occurred.
void uv_check_result(const int result, const std::source_location location) {
if(result < 0) {
logging::warn("libuv error: {}", uv_strerror(result));
logging::warn("At {}:{}:{}",
location.file_name(),
location.line(),
location.function_name());
LOGGING_WARN("libuv error: {}", uv_strerror(result));
LOGGING_WARN("At {}:{}:{}",
location.file_name(),
location.line(),
location.function_name());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,12 @@
#include "TidyImpl.h"
#include "AST/Utility.h"
#include "CompilationUnitImpl.h"
#include "Compiler/Command.h"
#include "Compiler/Compilation.h"
#include "Compiler/Diagnostic.h"
#include "Compiler/Tidy.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/MultiplexConsumer.h"
@@ -21,16 +26,17 @@ public:
src_mgr(instance.getSourceManager()), top_level_decls(top_level_decls), stop(stop) {}
void collect_decl(clang::Decl* decl) {
auto location = decl->getLocation();
if(location.isInvalid()) {
if(!(ast::is_inside_main_file(decl->getLocation(), src_mgr))) {
return;
}
location = src_mgr.getExpansionLoc(location);
auto fid = src_mgr.getFileID(location);
if(fid == src_mgr.getPreambleFileID() || fid == src_mgr.getMainFileID()) {
top_level_decls->push_back(decl);
if(const clang::NamedDecl* named_decl = dyn_cast<clang::NamedDecl>(decl)) {
if(ast::is_implicit_template_instantiation(named_decl)) {
return;
}
}
top_level_decls->push_back(decl);
}
auto HandleTopLevelDecl(clang::DeclGroupRef group) -> bool final {
@@ -135,9 +141,30 @@ auto create_invocation(CompilationParams& params,
auto& front_opts = invocation->getFrontendOpts();
front_opts.DisableFree = false;
clang::LangOptions& langOpts = invocation->getLangOpts();
langOpts.CommentOpts.ParseAllComments = true;
langOpts.RetainCommentsFromSystemHeaders = true;
/// Compiler flags (like gcc/clang's -M, -MD, -MMD, -H, or msvc's /showIncludes)
/// can generate dependency files or print included headers to stdout/stderr.
///
/// This output can interfere with or corrupt the Language Server Protocol (LSP)
/// communication if the server is configured to use stdio for its JSON-RPC transport.
/// We explicitly disables all related options to ensure no side-effect output is
/// generated during parsing.
auto& deps_opts = invocation->getDependencyOutputOpts();
deps_opts.IncludeSystemHeaders = false;
deps_opts.ShowSkippedHeaderIncludes = false;
deps_opts.UsePhonyTargets = false;
deps_opts.AddMissingHeaderDeps = false;
deps_opts.IncludeModuleFiles = false;
deps_opts.ShowIncludesDest = clang::ShowIncludesDestination::None;
deps_opts.OutputFile.clear();
deps_opts.HeaderIncludeOutputFile.clear();
deps_opts.Targets.clear();
deps_opts.ExtraDeps.clear();
deps_opts.DOTOutputFile.clear();
deps_opts.ModuleDependencyOutputDir.clear();
auto& lang_opts = invocation->getLangOpts();
lang_opts.CommentOpts.ParseAllComments = true;
lang_opts.RetainCommentsFromSystemHeaders = true;
return invocation;
}
@@ -158,19 +185,24 @@ CompilationResult run_clang(CompilationParams& params,
auto diagnostics =
params.diagnostics ? params.diagnostics : std::make_shared<std::vector<Diagnostic>>();
auto diagnostic_engine =
clang::CompilerInstance::createDiagnostics(*params.vfs,
new clang::DiagnosticOptions(),
Diagnostic::create(diagnostics));
auto diagnostic_consumer = Diagnostic::create(diagnostics);
/// Temporary diagnostic engine, only used for command line parsing.
/// For compilation, we need to create a new diagnostic engine. See also
/// https://github.com/llvm/llvm-project/pull/139584#issuecomment-2920704282.
clang::DiagnosticOptions options;
auto diagnostic_engine = clang::CompilerInstance::createDiagnostics(*params.vfs,
options,
diagnostic_consumer.get(),
false);
auto invocation = create_invocation(params, diagnostic_engine);
if(!invocation) {
return std::unexpected("Fail to create compilation invocation!");
}
auto instance = std::make_unique<clang::CompilerInstance>();
instance->setInvocation(std::move(invocation));
instance->setDiagnostics(diagnostic_engine.get());
auto instance = std::make_unique<clang::CompilerInstance>(std::move(invocation));
instance->createDiagnostics(*params.vfs, diagnostic_consumer.release(), true);
if(auto remapping = clang::createVFSFromCompilerInvocation(instance->getInvocation(),
instance->getDiagnostics(),
@@ -193,7 +225,7 @@ CompilationResult run_clang(CompilationParams& params,
auto action = std::make_unique<ProxyAction>(
std::make_unique<Action>(),
/// We only collect top level declarations for parse main file.
params.kind == CompilationUnit::Content ? &top_level_decls : nullptr,
(params.clang_tidy || params.kind == CompilationUnit::Content) ? &top_level_decls : nullptr,
params.stop);
if(!action->BeginSourceFile(*instance, instance->getFrontendOpts().Inputs[0])) {
@@ -201,7 +233,16 @@ CompilationResult run_clang(CompilationParams& params,
}
auto& pp = instance->getPreprocessor();
/// FIXME: clang-tidy, include-fixer, etc?
/// FIXME: include-fixer, etc?
/// Setup clang-tidy
std::unique_ptr<tidy::ClangTidyChecker> checker;
if(params.clang_tidy) {
tidy::TidyParams tidy_params;
checker = tidy::configure(*instance, tidy_params);
/// TODO: We should make the lifetime of diagnostic consumer more explicit.
static_cast<DiagnosticCollector&>(instance->getDiagnosticClient()).checker = checker.get();
}
/// `BeginSourceFile` may create new preprocessor, so all operations related to preprocessor
/// should be done after `BeginSourceFile`.
@@ -239,6 +280,19 @@ CompilationResult run_clang(CompilationParams& params,
token_buffer = std::move(*token_collector).consume();
}
// Must be called before EndSourceFile because the ast context can be destroyed later.
if(checker) {
// AST traversals should exclude the preamble, to avoid performance cliffs.
// TODO: is it okay to affect the unit-level traversal scope here?
instance->getASTContext().setTraversalScope(top_level_decls);
checker->finder.matchAST(instance->getASTContext());
}
/// XXX: This is messy: clang-tidy checks flush some diagnostics at EOF.
/// However Action->EndSourceFile() would destroy the ASTContext!
/// So just inform the preprocessor of EOF, while keeping everything alive.
pp.EndSourceFile();
/// FIXME: getDependencies currently return ArrayRef<std::string>, which actually results in
/// extra copy. It would be great to avoid this copy.
@@ -247,10 +301,15 @@ CompilationResult run_clang(CompilationParams& params,
resolver.emplace(instance->getSema());
}
if(checker) {
/// Avoid dangling pointer.
static_cast<DiagnosticCollector&>(instance->getDiagnosticClient()).checker = nullptr;
}
auto build_end = chrono::steady_clock::now().time_since_epoch();
auto impl = new CompilationUnit::Impl{
.interested = pp.getSourceManager().getMainFileID(),
.interested = instance->getSourceManager().getMainFileID(),
.src_mgr = instance->getSourceManager(),
.action = std::move(action),
.instance = std::move(instance),
@@ -259,7 +318,7 @@ CompilationResult run_clang(CompilationParams& params,
.directives = std::move(directives),
.path_cache = llvm::DenseMap<clang::FileID, llvm::StringRef>(),
.symbol_hash_cache = llvm::DenseMap<const void*, std::uint64_t>(),
.diagnostics = diagnostics,
.diagnostics = std::move(diagnostics),
.top_level_decls = std::move(top_level_decls),
.build_at = chrono::duration_cast<chrono::milliseconds>(build_at),
.build_duration = chrono::duration_cast<chrono::milliseconds>(build_end - build_start),

View File

@@ -6,6 +6,11 @@ namespace clice {
CompilationUnit::~CompilationUnit() {
if(impl && impl->action) {
auto instance = impl->instance.get();
// We already notified the pp of end-of-file earlier, so detach it first.
// We must keep it alive until after EndSourceFile(), Sema relies on this.
std::shared_ptr<clang::Preprocessor> pp = instance->getPreprocessorPtr();
instance->setPreprocessor(nullptr); // Detach so we don't send EOF again
impl->action->EndSourceFile();
}
@@ -241,9 +246,9 @@ std::vector<std::string> CompilationUnit::deps() {
}
}
for(auto& hasInclude: diretive.has_includes) {
if(hasInclude.fid.isValid()) {
deps.try_emplace(file_path(hasInclude.fid));
for(auto& has_include: diretive.has_includes) {
if(has_include.fid.isValid()) {
deps.try_emplace(file_path(has_include.fid));
}
}
}
@@ -263,9 +268,9 @@ index::SymbolID CompilationUnit::getSymbolID(const clang::NamedDecl* decl) {
if(iter != impl->symbol_hash_cache.end()) {
hash = iter->second;
} else {
llvm::SmallString<128> USR;
index::generateUSRForDecl(decl, USR);
hash = llvm::xxh3_64bits(USR);
llvm::SmallString<128> usr;
index::generateUSRForDecl(decl, usr);
hash = llvm::xxh3_64bits(usr);
impl->symbol_hash_cache.try_emplace(decl, hash);
}
return index::SymbolID{hash, ast::name_of(decl)};
@@ -278,9 +283,9 @@ index::SymbolID CompilationUnit::getSymbolID(const clang::MacroInfo* macro) {
if(iter != impl->symbol_hash_cache.end()) {
hash = iter->second;
} else {
llvm::SmallString<128> USR;
index::generateUSRForMacro(name, macro->getDefinitionLoc(), impl->src_mgr, USR);
hash = llvm::xxh3_64bits(USR);
llvm::SmallString<128> usr;
index::generateUSRForMacro(name, macro->getDefinitionLoc(), impl->src_mgr, usr);
hash = llvm::xxh3_64bits(usr);
impl->symbol_hash_cache.try_emplace(macro, hash);
}
return index::SymbolID{hash, name.str()};

View File

@@ -1,4 +1,7 @@
#include "Compiler/Diagnostic.h"
#include "Support/Format.h"
#include "TidyImpl.h"
#include "clang/AST/Type.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
@@ -7,7 +10,6 @@
#include "clang/Basic/AllDiagnostics.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Preprocessor.h"
#include "Support/Format.h"
namespace clice {
@@ -131,6 +133,10 @@ bool DiagnosticID::is_unused() const {
return source == DiagnosticSource::Clang && unused_diags.contains(value);
}
bool is_note(clang::DiagnosticsEngine::Level level) {
return level == clang::DiagnosticsEngine::Note || level == clang::DiagnosticsEngine::Remark;
}
static DiagnosticLevel diagnostic_level(clang::DiagnosticsEngine::Level level) {
switch(level) {
case clang::DiagnosticsEngine::Ignored: return DiagnosticLevel::Ignored;
@@ -194,14 +200,14 @@ auto diagnostic_range(const clang::Diagnostic& diagnostic, const clang::LangOpti
};
}
class DiagnosticCollector : public clang::DiagnosticConsumer {
class DiagnosticCollectorImpl : public DiagnosticCollector {
public:
DiagnosticCollector(std::shared_ptr<std::vector<Diagnostic>> diagnostics) :
DiagnosticCollectorImpl(std::shared_ptr<std::vector<Diagnostic>> diagnostics) :
diagnostics(diagnostics) {}
void BeginSourceFile(const clang::LangOptions& Opts, const clang::Preprocessor* PP) override {
options = &Opts;
src_mgr = &PP->getSourceManager();
void BeginSourceFile(const clang::LangOptions& opts, const clang::Preprocessor* pp) override {
options = &opts;
src_mgr = &pp->getSourceManager();
}
void HandleDiagnostic(clang::DiagnosticsEngine::Level level,
@@ -209,6 +215,12 @@ public:
auto& diagnostic = diagnostics->emplace_back();
diagnostic.id.value = raw_diagnostic.getID();
if(!is_note(level)) {
if(checker) {
level = checker->adjust_level(level, raw_diagnostic);
}
}
diagnostic.id.level = diagnostic_level(level);
/// TODO:
@@ -229,6 +241,10 @@ public:
diagnostic.range = range;
}
if(checker) {
checker->adjust_diag(diagnostic);
}
/// TODO: handle FixIts
/// raw_diagnostic.getFixItHints();
}
@@ -241,9 +257,9 @@ private:
clang::SourceManager* src_mgr;
};
clang::DiagnosticConsumer*
std::unique_ptr<DiagnosticCollector>
Diagnostic::create(std::shared_ptr<std::vector<Diagnostic>> diagnostics) {
return new DiagnosticCollector(diagnostics);
return std::make_unique<DiagnosticCollectorImpl>(std::move(diagnostics));
}
} // namespace clice

View File

@@ -9,29 +9,29 @@ namespace {
class DirectiveCollector : public clang::PPCallbacks {
public:
DirectiveCollector(clang::Preprocessor& PP,
DirectiveCollector(clang::Preprocessor& pp,
llvm::DenseMap<clang::FileID, Directive>& directives) :
PP(PP), SM(PP.getSourceManager()), directives(directives) {}
pp(pp), sm(pp.getSourceManager()), directives(directives) {}
private:
void add_condition(clang::SourceLocation location,
Condition::BranchKind kind,
Condition::ConditionValue value,
clang::SourceRange cond_range) {
auto& directive = directives[SM.getFileID(location)];
auto& directive = directives[sm.getFileID(location)];
directive.conditions.emplace_back(kind, value, location, cond_range);
}
void add_condition(clang::SourceLocation loc,
Condition::BranchKind kind,
clang::PPCallbacks::ConditionValueKind value,
clang::SourceRange conditionRange) {
clang::SourceRange condition_range) {
Condition::ConditionValue cond_value =
value == clang::PPCallbacks::CVK_False ? Condition::None
: value == clang::PPCallbacks::CVK_True ? Condition::True
: value == clang::PPCallbacks::CVK_NotEvaluated ? Condition::Skipped
: Condition::None;
add_condition(loc, kind, cond_value, conditionRange);
add_condition(loc, kind, cond_value, condition_range);
}
void add_condition(clang::SourceLocation loc,
@@ -51,12 +51,12 @@ private:
return;
}
if(SM.isWrittenInBuiltinFile(loc) || SM.isWrittenInCommandLineFile(loc) ||
SM.isWrittenInScratchSpace(loc)) {
if(sm.isWrittenInBuiltinFile(loc) || sm.isWrittenInCommandLineFile(loc) ||
sm.isWrittenInScratchSpace(loc)) {
return;
}
auto& directive = directives[SM.getFileID(loc)];
auto& directive = directives[sm.getFileID(loc)];
directive.macros.emplace_back(MacroRef{def, kind, loc});
}
@@ -65,67 +65,67 @@ public:
/// Rewritten Preprocessor Callbacks
/// ============================================================================
void InclusionDirective(clang::SourceLocation hashLoc,
const clang::Token& includeTok,
void InclusionDirective(clang::SourceLocation hash_loc,
const clang::Token& include_tok,
llvm::StringRef,
bool,
clang::CharSourceRange filenameRange,
clang::CharSourceRange filename_range,
clang::OptionalFileEntryRef,
llvm::StringRef,
llvm::StringRef,
const clang::Module*,
bool,
clang::SrcMgr::CharacteristicKind) override {
prevFID = SM.getFileID(hashLoc);
prev_fid = sm.getFileID(hash_loc);
/// An `IncludeDirective` call is always followed by either a `LexedFileChanged`
/// or a `FileSkipped`. so we cannot get the file id of included file here.
directives[prevFID].includes.emplace_back(Include{
directives[prev_fid].includes.emplace_back(Include{
.fid = {},
.location = includeTok.getLocation(),
.filename_range = filenameRange.getAsRange(),
.location = include_tok.getLocation(),
.filename_range = filename_range.getAsRange(),
});
}
void LexedFileChanged(clang::FileID currFID,
void LexedFileChanged(clang::FileID curr_fid,
LexedFileChangeReason reason,
clang::SrcMgr::CharacteristicKind,
clang::FileID prevFID,
clang::FileID prev_fid,
clang::SourceLocation) override {
if(reason == LexedFileChangeReason::EnterFile && currFID.isValid() && prevFID.isValid() &&
this->prevFID.isValid() && prevFID == this->prevFID) {
if(reason == LexedFileChangeReason::EnterFile && curr_fid.isValid() && prev_fid.isValid() &&
this->prev_fid.isValid() && prev_fid == this->prev_fid) {
/// Once the file has changed, it means that the last include is not skipped.
/// Therefore, we initialize its file id with the current file id.
auto& include = directives[prevFID].includes.back();
auto& include = directives[prev_fid].includes.back();
include.skipped = false;
include.fid = currFID;
include.fid = curr_fid;
}
}
void FileSkipped(const clang::FileEntryRef& file,
const clang::Token&,
clang::SrcMgr::CharacteristicKind) override {
if(prevFID.isValid()) {
if(prev_fid.isValid()) {
/// File with guard will have only one file id in `SourceManager`, use
/// `translateFile` to find it.
auto& include = directives[prevFID].includes.back();
auto& include = directives[prev_fid].includes.back();
include.skipped = true;
/// Get the FileID for the given file. If the source file is included multiple
/// times, the FileID will be the first inclusion.
include.fid = SM.translateFile(file);
include.fid = sm.translateFile(file);
}
}
void moduleImport(clang::SourceLocation import_location,
clang::ModuleIdPath names,
const clang::Module*) override {
auto fid = SM.getFileID(SM.getExpansionLoc(import_location));
auto fid = sm.getFileID(sm.getExpansionLoc(import_location));
auto& import = directives[fid].imports.emplace_back();
import.location = import_location;
for(auto& [name, location]: names) {
import.name += name->getName();
import.name_locations.emplace_back(location);
for(auto name: names) {
import.name += name.getIdentifierInfo()->getName();
import.name_locations.emplace_back(name.getLoc());
}
}
@@ -136,32 +136,32 @@ public:
clang::SrcMgr::CharacteristicKind) override {
clang::FileID fid;
if(file) {
fid = SM.translateFile(*file);
fid = sm.translateFile(*file);
}
directives[SM.getFileID(location)].has_includes.emplace_back(fid, location);
directives[sm.getFileID(location)].has_includes.emplace_back(fid, location);
}
void PragmaDirective(clang::SourceLocation Loc,
clang::PragmaIntroducerKind Introducer) override {
void PragmaDirective(clang::SourceLocation loc,
clang::PragmaIntroducerKind introducer) override {
// Ignore other cases except starts with `#pragma`.
if(Introducer != clang::PragmaIntroducerKind::PIK_HashPragma)
if(introducer != clang::PragmaIntroducerKind::PIK_HashPragma)
return;
clang::FileID fid = SM.getFileID(Loc);
clang::FileID fid = sm.getFileID(loc);
llvm::StringRef textToEnd = SM.getBufferData(fid).substr(SM.getFileOffset(Loc));
llvm::StringRef thatLine = textToEnd.take_until([](char ch) { return ch == '\n'; });
llvm::StringRef text_to_end = sm.getBufferData(fid).substr(sm.getFileOffset(loc));
llvm::StringRef that_line = text_to_end.take_until([](char ch) { return ch == '\n'; });
Pragma::Kind kind = thatLine.contains("endregion") ? Pragma::EndRegion
: thatLine.contains("region") ? Pragma::Region
: Pragma::Other;
Pragma::Kind kind = that_line.contains("endregion") ? Pragma::EndRegion
: that_line.contains("region") ? Pragma::Region
: Pragma::Other;
auto& directive = directives[fid];
directive.pragmas.emplace_back(Pragma{
thatLine,
that_line,
kind,
Loc,
loc,
});
}
@@ -220,16 +220,16 @@ public:
add_condition(loc, Condition::Elifndef, Condition::Skipped, cond_range);
}
void Else(clang::SourceLocation loc, clang::SourceLocation ifLoc) override {
void Else(clang::SourceLocation loc, clang::SourceLocation if_loc) override {
add_condition(loc, Condition::Else, Condition::None, clang::SourceRange());
}
void Endif(clang::SourceLocation loc, clang::SourceLocation ifLoc) override {
void Endif(clang::SourceLocation loc, clang::SourceLocation if_loc) override {
add_condition(loc, Condition::EndIf, Condition::None, clang::SourceRange());
}
void MacroDefined(const clang::Token& name, const clang::MacroDirective* MD) override {
if(auto def = MD->getMacroInfo()) {
void MacroDefined(const clang::Token& name, const clang::MacroDirective* md) override {
if(auto def = md->getMacroInfo()) {
add_macro(def, MacroRef::Def, name.getLocation());
}
}
@@ -244,19 +244,19 @@ public:
}
void MacroUndefined(const clang::Token& name,
const clang::MacroDefinition& MD,
const clang::MacroDefinition& md,
const clang::MacroDirective* undef) override {
if(auto def = MD.getMacroInfo()) {
if(auto def = md.getMacroInfo()) {
add_macro(def, MacroRef::Undef, name.getLocation());
}
}
private:
clang::FileID prevFID;
clang::Preprocessor& PP;
clang::SourceManager& SM;
clang::FileID prev_fid;
clang::Preprocessor& pp;
clang::SourceManager& sm;
llvm::DenseMap<clang::FileID, Directive>& directives;
llvm::DenseMap<clang::MacroInfo*, std::size_t> macroCache;
llvm::DenseMap<clang::MacroInfo*, std::size_t> macro_cache;
};
} // namespace

144
src/Compiler/Driver.h Normal file
View File

@@ -0,0 +1,144 @@
#pragma once
#include "Compiler/Command.h"
#include "Support/Logging.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/FileSystem.h"
#include "clang/Driver/Driver.h"
namespace clice {
namespace {
namespace opt = llvm::opt;
namespace driver = clang::driver;
/// Checks if dash-dash (`--`) parsing is enabled. If enabled, all arguments
/// after a standalone `--` are treated as positional arguments (e.g., input files).
bool enable_dash_dash_parsing(const opt::OptTable& table);
/// Checks if grouped short options are enabled. If enabled, a short option group
/// like `-ab` is parsed as separate options `-a` and `-b`.
bool enable_grouped_short_options(const opt::OptTable& table);
/// Get the specific toolchain of given target, we mainly use it to get msvc toolchain.
const driver::ToolChain& get_toolchain(driver::Driver& driver,
const opt::ArgList& Args,
const llvm::Triple& Target);
template <auto MP1, auto MP2, auto MP3>
struct Thief {
friend bool enable_dash_dash_parsing(const opt::OptTable& table) {
return table.*MP1;
}
friend bool enable_grouped_short_options(const opt::OptTable& table) {
return table.*MP2;
}
friend const driver::ToolChain& get_toolchain(driver::Driver& driver,
const opt::ArgList& args,
const llvm::Triple& target) {
return (driver.*MP3)(args, target);
}
};
template struct Thief<&opt::OptTable::DashDashParsing,
&opt::OptTable::GroupedShortOptions,
&driver::Driver::getToolChain>;
class ArgumentParser final : public llvm::opt::ArgList {
public:
ArgumentParser(llvm::BumpPtrAllocator* allocator) : allocator(allocator) {}
~ArgumentParser() {
/// We never use the private `Args` field, so make sure it's empty.
if(getArgs().size() != 0) {
std::abort();
}
}
const char* getArgString(unsigned index) const override {
return arguments[index];
}
unsigned getNumInputArgStrings() const override {
return arguments.size();
}
const char* MakeArgStringRef(llvm::StringRef s) const override {
auto p = allocator->Allocate<char>(s.size() + 1);
std::ranges::copy(s, p);
p[s.size()] = '\0';
return p;
}
inline static auto& option_table = clang::driver::getDriverOptTable();
void set_arguments(llvm::ArrayRef<const char*> arguments) {
if(getArgs().size() != 0) {
std::abort();
}
this->arguments = arguments;
}
std::unique_ptr<llvm::opt::Arg> parse_one(unsigned& index) {
/// Make sure we are not using
assert(!enable_dash_dash_parsing(option_table));
assert(!enable_grouped_short_options(option_table));
return option_table.ParseOneArg(*this, index);
}
void parse(llvm::ArrayRef<const char*> arguments, const auto& on_parse, const auto& on_error) {
this->arguments = arguments;
unsigned it = 0;
while(it != arguments.size()) {
llvm::StringRef s = arguments[it];
if(s.empty()) [[unlikely]] {
it += 1;
continue;
}
auto prev = it;
auto arg = parse_one(it);
assert(it > prev && "parser failed to consume argument");
if(!arg) [[unlikely]] {
assert(it >= arguments.size() && "unexpected parser error!");
assert(it - prev - 1 && "no missing arguments!");
/// FIXME: When parsing fails, the parser may have encountered unknown
/// arguments (e.g., options for a different compiler like nvcc).
/// We should allow the user to provide a custom option registry
/// (mainly for these pass-through arguments).
///
/// This would let us ignore them correctly. For example, when
/// parsing `nvcc --option-dir x.txt main.cpp`, our parser fails
/// because it discards `--option-dir` but doesn't know it also
/// consumes the next argument (`x.txt`).
///
/// With a custom registry, we could register that `--option-dir`
/// takes one argument, allowing us to skip both and continue
/// parsing from `main.cpp`.
on_error(prev, it - prev - 1);
break;
}
on_parse(std::move(arg));
}
}
private:
llvm::BumpPtrAllocator* allocator;
llvm::ArrayRef<const char*> arguments;
};
} // namespace
} // namespace clice

View File

@@ -9,9 +9,9 @@ std::string scanModuleName(CompilationParams& params) {
/// accepted, the module name in module declaration cannot be a macro now.
/// It means that if the module declaration doesn't occur in condition preprocess
/// directive, we can determine the module name just by lexing the source file.
clang::LangOptions langOpts;
langOpts.Modules = true;
langOpts.CPlusPlus20 = true;
clang::LangOptions lang_opts;
lang_opts.Modules = true;
lang_opts.CPlusPlus20 = true;
/// FIXME: Figure out main file from command line.
assert(params.buffers.size() == 1);
@@ -19,16 +19,16 @@ std::string scanModuleName(CompilationParams& params) {
/// We use raw mode of lexer to avoid the preprocessor.
clang::Lexer lexer(clang::SourceLocation(),
langOpts,
lang_opts,
content.begin(),
content.begin(),
content.end());
/// Whether we are in a condition directive.
bool isInDirective = false;
bool is_in_directive = false;
/// Whether we need to preprocess the source file.
bool needPreprocess = false;
bool need_preprocess = false;
std::string name;
clang::Token token;
@@ -46,9 +46,9 @@ std::string scanModuleName(CompilationParams& params) {
lexer.LexFromRawLexer(token);
auto diretive = token.getRawIdentifier();
if(diretive == "if" || diretive == "ifdef" || diretive == "ifndef") {
isInDirective = true;
is_in_directive = true;
} else if(diretive == "endif") {
isInDirective = false;
is_in_directive = false;
}
} else if(token.is(clang::tok::raw_identifier)) {
if(token.getRawIdentifier() != "export") [[likely]] {
@@ -61,10 +61,10 @@ std::string scanModuleName(CompilationParams& params) {
}
/// We are after `export module`.
if(isInDirective) {
if(is_in_directive) {
/// If the module name occurs in a condition directive, we have
/// to preprocess the source file to determine the module name.
needPreprocess = true;
need_preprocess = true;
break;
}
@@ -89,7 +89,7 @@ std::string scanModuleName(CompilationParams& params) {
/// If not need to preprocess and the function doesn't return, it means
/// that this file is not a module interface unit.
if(!needPreprocess) {
if(!need_preprocess) {
return "";
}

49
src/Compiler/Scan.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include "Compiler/Scan.h"
namespace clice {
ScanResult scan(llvm::StringRef content) {
ScanResult result;
Lexer lexer{
content,
true,
nullptr,
false,
};
while(true) {
auto token = lexer.advance();
if(token.is_eof()) {
break;
}
if(token.is_header_name()) {
auto spelling = token.text(content);
result.includes.emplace_back(spelling.starts_with('<') || spelling.ends_with('>'),
/// 0,
spelling.trim("<\">"));
} else if(token.is_pp_keyword && token.text(content) == "module") {
auto next = lexer.next();
/// Skip module;
if(next.kind == TokenKind::semi) {
continue;
}
/// Collect module name tokens.
while(true) {
auto name = lexer.advance();
if(name.is_eod()) {
break;
}
result.module_name.emplace_back(name);
}
}
}
return result;
}
} // namespace clice

View File

@@ -10,13 +10,23 @@
/// https://github.com/llvm/llvm-project//blob/0865ecc5150b9a55ba1f9e30b6d463a66ac362a6/clang-tools-extra/clangd/ParsedAST.cpp#L547
/// https://github.com/llvm/llvm-project//blob/0865ecc5150b9a55ba1f9e30b6d463a66ac362a6/clang-tools-extra/clangd/TidyProvider.cpp
#include "clang-tidy/ClangTidyModuleRegistry.h"
#include "clang-tidy/ClangTidyOptions.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Allocator.h"
#include "TidyImpl.h"
#include "AST/Utility.h"
#include "Compiler/Diagnostic.h"
#include "Compiler/Tidy.h"
#include "Support/Logging.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Allocator.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/StringSaver.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang-tidy/ClangTidyOptions.h"
#include "clang-tidy/ClangTidyCheck.h"
#include "clang-tidy/ClangTidyModuleRegistry.h"
#include "clang-tidy/ClangTidyDiagnosticConsumer.h"
#define CLANG_TIDY_DISABLE_STATIC_ANALYZER_CHECKS
#include "clang-tidy/ClangTidyForceLinker.h"
namespace clice::tidy {
@@ -25,7 +35,7 @@ using namespace clang::tidy;
bool is_registered_tidy_check(llvm::StringRef check) {
assert(!check.empty());
assert(!check.contains('*') && !check.contains(',') &&
"isRegisteredCheck doesn't support globs");
"is_registered_tidy_check doesn't support globs");
assert(check.ltrim().front() != '-');
const static llvm::StringSet<llvm::BumpPtrAllocator> all_checks = [] {
@@ -42,15 +52,352 @@ bool is_registered_tidy_check(llvm::StringRef check) {
}
std::optional<bool> is_fast_tidy_check(llvm::StringRef check) {
static auto& fast = *new llvm::StringMap<bool>{
static auto fast = llvm::StringMap<bool>{
#define FAST(CHECK, TIME) {#CHECK, true},
#define SLOW(CHECK, TIME) {#CHECK, false},
// todo: move me to llvm toolchain headers.
#include "TidyFastChecks.inc"
};
if(auto it = fast.find(check); it != fast.end())
if(auto it = fast.find(check); it != fast.end()) {
return it->second;
}
return std::nullopt;
}
tidy::ClangTidyCheckFactories get_fast_checks(const tidy::ClangTidyCheckFactories& all) {
tidy::ClangTidyCheckFactories fast;
for(const auto& factory: all) {
if(is_fast_tidy_check(factory.getKey()).value_or(false)) {
fast.registerCheckFactory(factory.first(), factory.second);
}
}
return fast;
}
tidy::ClangTidyOptions create_options() {
// getDefaults instantiates all check factories, which are registered at link
// time. So cache the results once.
const static auto default_opts = [] {
auto opts = tidy::ClangTidyOptions::getDefaults();
opts.Checks->clear();
return opts;
}();
// These default checks are chosen for:
// - low false-positive rate
// - providing a lot of value
// - being reasonably efficient
const static std::string default_checks = llvm::join_items(",",
"readability-misleading-indentation",
"readability-deleted-default",
"bugprone-integer-division",
"bugprone-sizeof-expression",
"bugprone-suspicious-missing-comma",
"bugprone-unused-raii",
"bugprone-unused-return-value",
"misc-unused-using-decls",
"misc-unused-alias-decls",
"misc-definitions-in-headers");
const static std::string bad_checks =
llvm::join_items(",",
// We want this list to start with a separator to
// simplify appending in the lambda. So including an
// empty string here will force that.
"",
// include-cleaner is directly integrated in IncludeCleaner.cpp
"-misc-include-cleaner",
// ----- False Positives -----
// Check relies on seeing ifndef/define/endif directives,
// clangd doesn't replay those when using a preamble.
"-llvm-header-guard",
"-modernize-macro-to-enum",
// ----- Crashing Checks -----
// Check can choke on invalid (intermediate) c++
// code, which is often the case when clangd
// tries to build an AST.
"-bugprone-use-after-move",
// Alias for bugprone-use-after-move.
"-hicpp-invalid-access-moved",
// Check uses dataflow analysis, which might hang/crash unexpectedly on
// incomplete code.
"-bugprone-unchecked-optional-access");
tidy::ClangTidyOptions opts = default_opts;
// clang::clangd::provideEnvironment
if(std::optional<std::string> user = llvm::sys::Process::GetEnv("USER")) {
opts.User = user;
}
// TODO: Providers.push_back(provideClangTidyFiles(TFS)); Filename
// TODO: if(EnableConfig) Providers.push_back(provideClangdConfig());
// clang::clangd::provideDefaultChecks
if(!opts.Checks || opts.Checks->empty()) {
opts.Checks = default_checks;
}
// clang::clangd::disableUnusableChecks
if(opts.Checks && !opts.Checks->empty()) {
opts.Checks->append(bad_checks);
}
return opts;
}
// Filter for clang diagnostics groups enabled by CTOptions.Checks.
//
// These are check names like clang-diagnostics-unused.
// Note that unlike -Wunused, clang-diagnostics-unused does not imply
// subcategories like clang-diagnostics-unused-function.
//
// This is used to determine which diagnostics can be enabled by ExtraArgs in
// the clang-tidy configuration.
class TidyDiagnosticGroups {
// Whether all diagnostic groups are enabled by default.
// True if we've seen clang-diagnostic-*.
bool default_enable = false;
// Set of diag::Group whose enablement != default_enable.
// If default_enable is false, this is foo where we've seen clang-diagnostic-foo.
llvm::DenseSet<unsigned> exceptions;
public:
TidyDiagnosticGroups(llvm::StringRef checks) {
constexpr llvm::StringLiteral CDPrefix = "clang-diagnostic-";
llvm::StringRef check;
while(!checks.empty()) {
std::tie(check, checks) = checks.split(',');
check = check.trim();
if(check.empty()) {
continue;
}
bool enable = !check.consume_front("-");
bool glob = check.consume_back("*");
if(glob) {
// Is this clang-diagnostic-*, or *, or so?
// (We ignore all other types of globs).
if(CDPrefix.starts_with(check)) {
default_enable = enable;
exceptions.clear();
}
continue;
}
// In "*,clang-diagnostic-foo", the latter is a no-op.
if(default_enable == enable) {
continue;
}
// The only non-glob entries we care about are clang-diagnostic-foo.
if(!check.consume_front(CDPrefix)) {
continue;
}
if(auto group = clang::DiagnosticIDs::getGroupForWarningOption(check)) {
exceptions.insert(static_cast<unsigned>(*group));
}
}
}
bool operator() (clang::diag::Group group_id) const {
return exceptions.contains(static_cast<unsigned>(group_id)) ? !default_enable
: default_enable;
}
};
// Find -W<group> and -Wno-<group> options in extra_args and apply them to diags.
//
// This is used to handle extra_args in clang-tidy configuration.
// We don't use clang's standard handling of this as we want slightly different
// behavior (e.g. we want to exclude these from -Wno-error).
void apply_warning_options(llvm::ArrayRef<std::string> extra_args,
llvm::function_ref<bool(clang::diag::Group)> enable_groups,
clang::DiagnosticsEngine& diags) {
for(llvm::StringRef group: extra_args) {
// Only handle args that are of the form -W[no-]<group>.
// Other flags are possible but rare and deliberately out of scope.
llvm::SmallVector<clang::diag::kind> members;
if(!group.consume_front("-W") || group.empty()) {
continue;
}
bool enable = !group.consume_front("no-");
if(diags.getDiagnosticIDs()->getDiagnosticsInGroup(clang::diag::Flavor::WarningOrError,
group,
members)) {
continue;
}
// Upgrade (or downgrade) the severity of each diagnostic in the group.
// If -Werror is on, newly added warnings will be treated as errors.
// We don't want this, so keep track of them to fix afterwards.
bool needs_werror_exclusion = false;
for(clang::diag::kind id: members) {
if(enable) {
if(diags.getDiagnosticLevel(id, clang::SourceLocation()) <
clang::DiagnosticsEngine::Warning) {
auto group = diags.getDiagnosticIDs()->getGroupForDiag(id);
if(!group || !enable_groups(*group)) {
continue;
}
diags.setSeverity(id, clang::diag::Severity::Warning, clang::SourceLocation());
if(diags.getWarningsAsErrors()) {
needs_werror_exclusion = true;
}
}
} else {
diags.setSeverity(id, clang::diag::Severity::Ignored, clang::SourceLocation());
}
}
if(needs_werror_exclusion) {
// FIXME: there's no API to suppress -Werror for single diagnostics.
// In some cases with sub-groups, we may end up erroneously
// downgrading diagnostics that were -Werror in the compile command.
diags.setDiagnosticGroupWarningAsError(group, false);
}
}
}
ClangTidyChecker::ClangTidyChecker(std::unique_ptr<ClangTidyOptionsProvider> provider) :
context(std::move(provider)) {}
clang::DiagnosticsEngine::Level
ClangTidyChecker::adjust_level(clang::DiagnosticsEngine::Level level,
const clang::Diagnostic& diag) {
if(!checks.empty()) {
std::string tidy_diag = context.getCheckName(diag.getID());
bool is_clang_tidy_diag = !tidy_diag.empty();
if(is_clang_tidy_diag) {
// Check for suppression comment. Skip the check for diagnostics not
// in the main file, because we don't want that function to query the
// source buffer for preamble files. For the same reason, we ask
// shouldSuppressDiagnostic to avoid I/O.
// We let suppression comments take precedence over warning-as-error
// to match clang-tidy's behaviour.
bool in_main_file =
diag.hasSourceManager() &&
ast::is_inside_main_file(diag.getLocation(), diag.getSourceManager());
llvm::SmallVector<clang::tooling::Diagnostic, 1> tidy_suppressed_errors;
if(in_main_file && context.shouldSuppressDiagnostic(level,
diag,
tidy_suppressed_errors,
/*AllowIO=*/false,
/*EnableNolintBlocks=*/true)) {
// FIXME: should we expose the suppression error (invalid use of
// NOLINT comments)?
return clang::DiagnosticsEngine::Ignored;
}
if(!context.getOptions().SystemHeaders.value_or(false) && diag.hasSourceManager() &&
diag.getSourceManager().isInSystemMacro(diag.getLocation())) {
return clang::DiagnosticsEngine::Ignored;
}
// Check for warning-as-error.
if(level == clang::DiagnosticsEngine::Warning && context.treatAsError(tidy_diag)) {
return clang::DiagnosticsEngine::Error;
}
}
}
return level;
}
void ClangTidyChecker::adjust_diag(Diagnostic& diag) {
std::string tidy_diag = context.getCheckName(diag.id.value);
if(!tidy_diag.empty()) {
// TODO: using a global string saver.
static llvm::BumpPtrAllocator allocator;
static llvm::StringSaver saver(allocator);
diag.id.name = saver.save(tidy_diag);
diag.id.source = DiagnosticSource::ClangTidy;
// clang-tidy bakes the name into diagnostic messages. Strip it out.
// It would be much nicer to make clang-tidy not do this.
auto clean_message = [&](std::string& msg) {
llvm::StringRef rest(msg);
if(rest.consume_back("]") && rest.consume_back(diag.id.name) && rest.consume_back(" ["))
msg.resize(rest.size());
};
clean_message(diag.message);
// todo: where is clice notes and fixes?
// for(auto& note: diag.Notes)
// clean_message(note.Message);
// for(auto& fix: diag.Fixes)
// clean_message(fix.Message);
}
}
std::unique_ptr<ClangTidyChecker> configure(clang::CompilerInstance& instance,
const TidyParams& params) {
auto& input = instance.getFrontendOpts().Inputs[0];
if(!input.isFile()) {
return nullptr;
}
auto file_name = input.getFile();
LOGGING_INFO("Tidy configure file: {}", file_name);
tidy::ClangTidyOptions opts = create_options();
if(opts.Checks) {
LOGGING_INFO("Tidy configure checks: {}", *opts.Checks);
}
{
// If clang-tidy is configured to emit clang warnings, we should too.
//
// Such clang-tidy configuration consists of two parts:
// - ExtraArgs: ["-Wfoo"] causes clang to produce the warnings
// - Checks: "clang-diagnostic-foo" prevents clang-tidy filtering them out
//
// In clang-tidy, diagnostics are emitted if they pass both checks.
// When groups contain subgroups, -Wparent includes the child, but
// clang-diagnostic-parent does not.
//
// We *don't* want to change the compile command directly. This can have
// too many unexpected effects: breaking the command, interactions with
// -- and -Werror, etc. Besides, we've already parsed the command.
// Instead we parse the -W<group> flags and handle them directly.
//
// Similarly, we don't want to use Checks to filter clang diagnostics after
// they are generated, as this spreads clang-tidy emulation everywhere.
// Instead, we just use these to filter which extra diagnostics we enable.
auto& diags = instance.getDiagnostics();
TidyDiagnosticGroups groups(opts.Checks ? *opts.Checks : llvm::StringRef());
if(opts.ExtraArgsBefore) {
apply_warning_options(*opts.ExtraArgsBefore, groups, diags);
}
if(opts.ExtraArgs) {
apply_warning_options(*opts.ExtraArgs, groups, diags);
}
}
/// No need to run clang-tidy or IncludeFixerif we are not going to surface
/// diagnostics.
const static auto all_factories = [] {
tidy::ClangTidyCheckFactories factories;
for(const auto& e: tidy::ClangTidyModuleRegistry::entries()) {
e.instantiate()->addCheckFactories(factories);
}
return factories;
}();
tidy::ClangTidyCheckFactories factories = get_fast_checks(all_factories);
std::unique_ptr<ClangTidyChecker> checker = std::make_unique<ClangTidyChecker>(
std::make_unique<tidy::DefaultOptionsProvider>(tidy::ClangTidyGlobalOptions(), opts));
checker->context.setDiagnosticsEngine(
std::make_unique<clang::DiagnosticOptions>(instance.getDiagnosticOpts()),
&instance.getDiagnostics());
checker->context.setASTContext(&instance.getASTContext());
// TODO: is `file_name` always the file to check?
checker->context.setCurrentFile(file_name);
checker->context.setSelfContainedDiags(true);
checker->checks = factories.createChecksForLanguage(&checker->context);
LOGGING_INFO("Tidy configure checks: {}", checker->checks.size());
clang::Preprocessor* pp = &instance.getPreprocessor();
for(const auto& check: checker->checks) {
check->registerPPCallbacks(instance.getSourceManager(), pp, pp);
check->registerMatchers(&checker->finder);
}
return checker;
}
} // namespace clice::tidy

33
src/Compiler/TidyImpl.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <memory>
#include "Compiler/Diagnostic.h"
#include "Compiler/Tidy.h"
#include "clang-tidy/ClangTidyModuleRegistry.h"
#include "clang-tidy/ClangTidyOptions.h"
#include "clang-tidy/ClangTidyCheck.h"
namespace clice::tidy {
using namespace clang::tidy;
class ClangTidyChecker {
public:
/// The context of the clang-tidy checker.
ClangTidyContext context;
/// The instances of checks that are enabled for the current Language.
std::vector<std::unique_ptr<ClangTidyCheck>> checks;
/// The match finder to run clang-tidy on ASTs.
clang::ast_matchers::MatchFinder finder;
ClangTidyChecker(std::unique_ptr<ClangTidyOptionsProvider> provider);
clang::DiagnosticsEngine::Level adjust_level(clang::DiagnosticsEngine::Level level,
const clang::Diagnostic& diag);
void adjust_diag(Diagnostic& diag);
};
} // namespace clice::tidy

282
src/Compiler/Toolchain.cpp Normal file
View File

@@ -0,0 +1,282 @@
#include "Compiler/Toolchain.h"
#include "Support/FileSystem.h"
#include "Support/Logging.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/FileSystem.h"
#include "clang/Driver/Driver.h"
namespace clice::toolchain {
namespace opt = llvm::opt;
namespace driver = clang::driver;
/// Checks if dash-dash (`--`) parsing is enabled. If enabled, all arguments
/// after a standalone `--` are treated as positional arguments (e.g., input files).
bool enable_dash_dash_parsing(const opt::OptTable& table);
/// Checks if grouped short options are enabled. If enabled, a short option group
/// like `-ab` is parsed as separate options `-a` and `-b`.
bool enable_grouped_short_options(const opt::OptTable& table);
/// Get the specific toolchain of given target, we mainly use it to get msvc toolchain.
const driver::ToolChain& get_toolchain(driver::Driver& driver,
const opt::ArgList& Args,
const llvm::Triple& Target);
template <auto MP1, auto MP2, auto MP3>
struct Thief {
friend bool enable_dash_dash_parsing(const opt::OptTable& table) {
return table.*MP1;
}
friend bool enable_grouped_short_options(const opt::OptTable& table) {
return table.*MP2;
}
friend const driver::ToolChain& get_toolchain(driver::Driver& driver,
const opt::ArgList& args,
const llvm::Triple& target) {
return (driver.*MP3)(args, target);
}
};
template struct Thief<&opt::OptTable::DashDashParsing,
&opt::OptTable::GroupedShortOptions,
&driver::Driver::getToolChain>;
Toolchain query_toolchain(llvm::ArrayRef<const char*> arguments) {
llvm::StringRef driver = arguments[0];
/// judge tool chain kind ...
return {};
}
using ErrorKind = toolchain::QueryDriverError::ErrorKind;
auto unexpected(ErrorKind kind, std::string message) {
return std::unexpected<toolchain::QueryDriverError>({kind, std::move(message)});
};
enum class CompilerFamily {
Unknown,
GCC, // Covers gcc, g++, cc, c++, and versioned/arch variants
Clang, // Covers clang, clang++, and versioned variants (excluding clang-cl)
MSVC, // Covers cl
ClangCL, // Covers clang-cl explicitly
NVCC, // Covers nvcc
Intel, // Covers icc, icpc, icx, dpcpp
Zig, // Covers zig cc / zig c++ (assumed GCC/Clang compatible for query)
};
CompilerFamily driver_family(llvm::StringRef driver) {
auto driver_name = llvm::sys::path::filename(driver);
driver_name.consume_back(".exe");
if(driver_name == "cl") {
return CompilerFamily::MSVC;
} else if(driver_name == "nvcc") {
return CompilerFamily::NVCC;
} else if(driver_name.contains("clang-cl")) {
return CompilerFamily::ClangCL;
} else if(driver_name.contains("clang")) {
return CompilerFamily::Clang;
} else if(driver_name == "cc" || driver_name == "c++" || driver_name.contains("gcc") ||
driver_name.contains("g++")) {
return CompilerFamily::GCC;
} else if(driver_name.contains("icpc") || driver_name.contains("icc") ||
driver_name.contains("dpcpp") || driver_name.contains("icx")) {
return CompilerFamily::Intel;
} else if(driver_name.contains("zig")) {
return CompilerFamily::Zig;
}
return CompilerFamily::Unknown;
}
auto parse_query_result(llvm::StringRef content, QueryResult& info)
-> std::expected<void, QueryDriverError> {
const char* TS = "Target: ";
const char* SIS = "#include <...> search starts here:";
const char* SIE = "End of search list.";
llvm::SmallVector<llvm::StringRef> lines;
content.split(lines, '\n', -1, false);
bool in_includes_block = false;
bool found_start_marker = false;
for(const auto& line_ref: lines) {
auto line = line_ref.trim();
if(line.starts_with(TS)) {
line.consume_front(TS);
info.target = line;
continue;
}
if(line == SIS) {
found_start_marker = true;
in_includes_block = true;
continue;
}
if(line == SIE) {
if(in_includes_block) {
in_includes_block = false;
}
continue;
}
if(in_includes_block) {
info.includes.emplace_back(line);
}
}
if(!found_start_marker) {
return unexpected(ErrorKind::InvalidOutputFormat, "Start marker not found...");
}
if(in_includes_block) {
return unexpected(ErrorKind::InvalidOutputFormat, "End marker not found...");
}
return std::expected<void, QueryDriverError>();
}
auto query_driver(llvm::StringRef driver) -> std::expected<QueryResult, QueryDriverError> {
llvm::SmallString<128> path;
/// Note: The name used to invoke the compiler driver affects its behavior.
/// For example, `/usr/bin/clang++` is often a symbolic link to
/// `/usr/lib/llvm-20/bin/clang`. Invoking it as `clang++` enables C++ mode
/// and links C++ libraries by default, while invoking as `clang` defaults to C mode.
/// Therefore, never use `realpath` on the initial `driver` name, as that
/// would lose the context needed for the driver to behave correctly (and break caching).
if(!llvm::sys::path::is_absolute(driver)) {
/// If the path is not absolute path like g++, find it in the env vars.
auto program = llvm::sys::findProgramByName(driver);
if(!program) {
return unexpected(ErrorKind::NotFoundInPATH, program.getError().message());
}
path = *program;
driver = path;
}
/// Check whether we can execute the driver.
if(!llvm::sys::fs::exists(driver) || !llvm::sys::fs::can_execute(driver)) {
/// FIXME: Add whitelisting, blacklisting (do not trust workspace executables),
/// and toolchain integrity checks.
return unexpected(ErrorKind::NotFoundInPATH, "");
}
auto family = driver_family(driver);
/// FIXME: Handle nvcc and intel compiler.
if(family == CompilerFamily::NVCC || family == CompilerFamily::Intel ||
family == CompilerFamily::Zig || family == CompilerFamily::Unknown) [[unlikely]] {
/// FIXME: nvcc and intel compilers need further exploration.
/// zig is easy to handle, just use `zig cc` or `zig c++`, then
/// it will behave like clang.
return unexpected(ErrorKind::NotImplemented, "");
}
/// Query the compiler for includes information.
if(family == CompilerFamily::GCC || family == CompilerFamily::Clang) {
llvm::SmallString<128> output_path;
if(auto error =
llvm::sys::fs::createTemporaryFile("system-includes", "clice", output_path)) {
return unexpected(ErrorKind::FailToCreateTempFile, error.message());
}
// If we fail to get the driver infomation, keep the output file for user to debug.
bool keep_output_file = true;
auto clean_up = llvm::make_scope_exit([&output_path, &keep_output_file]() {
if(keep_output_file) {
LOGGING_WARN("Query driver failed, output file:{}", output_path);
return;
}
if(auto errc = llvm::sys::fs::remove(output_path)) {
LOGGING_WARN("Fail to remove temporary file: {}", errc.message());
}
});
/// FIXME: Is it possible that the output is not in stderr?
std::optional<llvm::StringRef> redirects[3] = {
{""},
{""},
{output_path.str()},
};
#ifdef _WIN32
/// If the env is `std::nullopt`, `ExecuteAndWait` will inherit env from parent process,
/// which is very important for msvc and clang on windows. Thay depend on the environment
/// variables to find correct standard library path.
constexpr auto env = std::nullopt;
llvm::SmallVector<llvm::StringRef, 6> argv = {driver, "-E", "-v", "-xc++", "NUL"};
#else
/// FIXME: We should find a better way to convert "LANG=C", this is important
/// for gcc with locality. Otherwise, it will output non-ASCII char. We also
/// want to inherit the environment variables like windows.
llvm::SmallVector<llvm::StringRef> env = {"LANG=C"};
llvm::SmallVector<llvm::StringRef> argv = {driver, "-E", "-v", "-xc++", "/dev/null"};
#endif
std::string message;
if(int RC = llvm::sys::ExecuteAndWait(driver,
argv,
env,
redirects,
/*SecondsToWait=*/0,
/*MemoryLimit=*/0,
&message)) {
return unexpected(ErrorKind::InvokeDriverFail, std::move(message));
}
auto file = llvm::MemoryBuffer::getFile(output_path);
if(!file) {
return unexpected(ErrorKind::OutputFileNotReadable, file.getError().message());
}
QueryResult info;
if(auto r = parse_query_result(file.get()->getBuffer(), info)) {
keep_output_file = false;
return info;
} else {
return std::unexpected(r.error());
}
}
/// For msvc and clang-cl, we don't need to query driver. Just use clang
/// tool chain to find the built includes.
if(family == CompilerFamily::MSVC || family == CompilerFamily::ClangCL) {
/// FIXME: target information? e.g. arm cross compilation.
llvm::StringRef target = "x86_64-pc-windows-msvc";
/// An workaround to use clang's toolchain to find vsinstall information
/// and related includes.
clang::DiagnosticOptions options;
thread_local clang::DiagnosticsEngine engine(new clang::DiagnosticIDs(), options);
clang::driver::Driver driver("", target, engine);
llvm::SmallVector<const char*> args = {"", "-xc++", "NUL"};
llvm::opt::InputArgList list(args.begin(), args.end());
auto& toolchain = get_toolchain(driver, list, llvm::Triple(target));
/// FIXME: specify specific version of vs?
llvm::opt::ArgStringList includes;
toolchain.AddClangSystemIncludeArgs(list, includes);
QueryResult info;
info.target = target;
for(auto& include: includes) {
info.includes.emplace_back(include);
}
return info;
}
std::unreachable();
}
} // namespace clice::toolchain

View File

@@ -180,7 +180,10 @@ CompletionItemKind completion_kind(const clang::NamedDecl* decl) {
case clang::Decl::ObjCCompatibleAlias:
case clang::Decl::OutlinedFunction:
case clang::Decl::HLSLBuffer: {
case clang::Decl::HLSLBuffer:
case clang::Decl::OpenACCRoutine:
case clang::Decl::OpenACCDeclare:
case clang::Decl::HLSLRootSignature: {
std::unreachable();
}
}

View File

@@ -32,9 +32,9 @@ json::Value diagnostics(PositionEncodingKind kind, PathMapping mapping, Compilat
if(level == DiagnosticLevel::Note || level == DiagnosticLevel::Remark) {
/// FIXME: figure out why it may be invalid.
if(fid.isInvalid()) {
logging::info("code: {}, message: {}",
raw_diagnostic.id.diagnostic_code(),
raw_diagnostic.message);
LOGGING_INFO("code: {}, message: {}",
raw_diagnostic.id.diagnostic_code(),
raw_diagnostic.message);
continue;
}

View File

@@ -41,7 +41,7 @@ std::vector<proto::TextEdit> document_format(llvm::StringRef file,
range ? tooling::Range(range->begin, range->length()) : tooling::Range(0, content.size());
auto replacements = format(file, content, selection);
if(!replacements) {
logging::info("Fail to format for {}\n{}", file, replacements.error());
LOGGING_INFO("Fail to format for {}\n{}", file, replacements.error());
return edits;
}

View File

@@ -133,7 +133,7 @@ public:
SymbolKind kind = SymbolKind::Invalid;
if(token.is_at_start_of_line && token.kind == clang::tok::hash) {
kind = SymbolKind::Directive;
} else if(token.is_preprocessor_directive) {
} else if(token.is_pp_keyword) {
kind = SymbolKind::Directive;
} else {
switch(token.kind) {
@@ -176,7 +176,7 @@ public:
case clang::tok::raw_identifier: {
auto last = lexer.last();
if(last.is_preprocessor_directive && last.text(content) == "define") {
if(last.is_pp_keyword && last.text(content) == "define") {
kind = SymbolKind::Macro;
break;
}

View File

@@ -206,9 +206,9 @@ void USRGenerator::VisitFunctionDecl(const FunctionDecl* D) {
IsTemplate = true;
Out << "@FT@";
VisitTemplateParameterList(FunTmpl->getTemplateParameters());
if(auto RC = D->getTrailingRequiresClause()) {
if(auto& RC = D->getTrailingRequiresClause()) {
Out << ":RC";
AppendExprODRHash(RC, Out);
AppendExprODRHash(RC.ConstraintExpr, Out);
}
} else
Out << "@F@";
@@ -580,7 +580,7 @@ void USRGenerator::VisitType(QualType T) {
case BuiltinType::OCLSampler: Out << "@BT@OCLSampler"; break;
#define SVE_TYPE(Name, Id, SingletonId) \
case BuiltinType::Id: Out << "@BT@" << #Name; break;
#include "clang/Basic/AArch64SVEACLETypes.def"
#include "clang/Basic/AArch64ACLETypes.def"
#define PPC_VECTOR_TYPE(Name, Id, Size) \
case BuiltinType::Id: Out << "@BT@" << #Name; break;
#include "clang/Basic/PPCTypes.def"
@@ -828,7 +828,8 @@ void USRGenerator::VisitTemplateName(TemplateName Name) {
if(auto DT = Name.getAsDependentTemplateName()) {
Out << '^';
printQualifier(Out, LangOpts, DT->getQualifier());
Out << ':' << DT->getIdentifier()->getName();
/// FIXME: This may be operators.
Out << ':' << DT->getName().getIdentifier();
}
}

View File

@@ -12,14 +12,14 @@ void Server::load_cache_info() {
auto path = path::join(config.project.cache_dir, "cache.json");
auto file = llvm::MemoryBuffer::getFile(path);
if(!file) {
logging::warn("Fail to load cache info, because: {}", file.getError());
LOGGING_WARN("Fail to load cache info, because: {}", file.getError());
return;
}
llvm::StringRef content = file.get()->getBuffer();
auto json = json::parse(content);
if(!json) {
logging::warn("Fail to load cache info, invalid json: {}", json.takeError());
LOGGING_WARN("Fail to load cache info, invalid json: {}", json.takeError());
return;
}
@@ -30,7 +30,7 @@ void Server::load_cache_info() {
auto version = object->getString("version");
if(!version) {
logging::info("Fail to load cache info, the cache info is outdated");
LOGGING_INFO("Fail to load cache info, the cache info is outdated");
return;
}
@@ -75,7 +75,7 @@ void Server::load_cache_info() {
}
}
logging::info("Load cache info successfully");
LOGGING_INFO("Load cache info successfully");
}
void Server::save_cache_info() {
@@ -105,20 +105,20 @@ void Server::save_cache_info() {
llvm::SmallString<128> temp_path;
if(auto error = llvm::sys::fs::createTemporaryFile("cache", "json", temp_path)) {
logging::warn("Fail to create temporary file for cache info: {}", error.message());
LOGGING_WARN("Fail to create temporary file for cache info: {}", error.message());
return;
}
auto clean_up = llvm::make_scope_exit([&temp_path]() {
if(auto errc = llvm::sys::fs::remove(temp_path)) {
logging::warn("Fail to remove temporary file: {}", errc.message());
LOGGING_WARN("Fail to remove temporary file: {}", errc.message());
}
});
std::error_code EC;
llvm::raw_fd_ostream os(temp_path, EC, llvm::sys::fs::OF_None);
if(EC) {
logging::warn("Fail to open temporary file for writing: {}", EC.message());
LOGGING_WARN("Fail to open temporary file for writing: {}", EC.message());
return;
}
@@ -127,26 +127,24 @@ void Server::save_cache_info() {
os.close();
if(os.has_error()) {
logging::warn("Fail to write cache info to temporary file");
LOGGING_WARN("Fail to write cache info to temporary file");
return;
}
if(auto error = llvm::sys::fs::rename(temp_path, final_path)) {
logging::warn("Fail to rename temporary file to final cache file: {}", error.message());
LOGGING_WARN("Fail to rename temporary file to final cache file: {}", error.message());
return;
}
clean_up.release();
logging::info("Save cache info successfully");
LOGGING_INFO("Save cache info successfully");
}
namespace {
bool check_pch_update(llvm::StringRef content,
std::uint32_t bound,
CompilationDatabase::LookupInfo& info,
PCHInfo& pch) {
bool
check_pch_update(llvm::StringRef content, std::uint32_t bound, LookupInfo& info, PCHInfo& pch) {
if(content.substr(0, bound) != pch.preamble) {
return true;
}
@@ -170,7 +168,7 @@ bool check_pch_update(llvm::StringRef content,
}
/// The actual PCH build task.
async::Task<bool> build_pch_task(CompilationDatabase::LookupInfo& info,
async::Task<bool> build_pch_task(LookupInfo& info,
std::string cache_dir,
std::shared_ptr<OpenFile> open_file,
std::string path,
@@ -180,7 +178,7 @@ async::Task<bool> build_pch_task(CompilationDatabase::LookupInfo& info,
if(!fs::exists(cache_dir)) {
auto error = fs::create_directories(cache_dir);
if(error) {
logging::warn("Fail to create directory for PCH building: {}", cache_dir);
LOGGING_WARN("Fail to create directory for PCH building: {}", cache_dir);
co_return false;
}
}
@@ -201,7 +199,7 @@ async::Task<bool> build_pch_task(CompilationDatabase::LookupInfo& info,
command += argument;
}
logging::info("Start building PCH for {}, command: [{}]", path, command);
LOGGING_INFO("Start building PCH for {}, command: [{}]", path, command);
command.clear();
PCHInfo pch;
@@ -222,14 +220,14 @@ async::Task<bool> build_pch_task(CompilationDatabase::LookupInfo& info,
});
if(!success) {
logging::warn("Building PCH fails for {}, Because: {}", path, message);
LOGGING_WARN("Building PCH fails for {}, Because: {}", path, message);
for(auto& diagnostic: *diagnostics) {
logging::warn("{}", diagnostic.message);
LOGGING_WARN("{}", diagnostic.message);
}
co_return false;
}
logging::info("Building PCH successfully for {}", path);
LOGGING_INFO("Building PCH successfully for {}", path);
/// Update the built PCH info.
open_file->pch = std::move(pch);
@@ -248,7 +246,7 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
CommandOptions options;
options.resource_dir = true;
options.query_driver = true;
auto info = database.get_command(file, options);
auto info = database.lookup(file, options);
auto bound = compute_preamble_bound(content);
auto& open_file = opening_files.get_or_add(file);
@@ -256,7 +254,7 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
/// Check update ...
if(open_file->pch && !check_pch_update(content, bound, info, *open_file->pch)) {
/// If not need update, return directly.
logging::info("PCH is already up-to-date for {}", file);
LOGGING_INFO("PCH is already up-to-date for {}", file);
co_return true;
}
@@ -265,12 +263,12 @@ async::Task<bool> Server::build_pch(std::string file, std::string content) {
if(!task.empty()) {
if(task.finished()) {
task.release().destroy();
logging::info("Release old pch task!");
LOGGING_INFO("Release old pch task!");
} else {
task.cancel();
task.dispose();
}
logging::info("Cancel old PCH building task!");
LOGGING_INFO("Cancel old PCH building task!");
}
/// Schedule the new building task.
@@ -306,7 +304,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
auto pch = file->pch;
if(!pch) {
logging::fatal("Expected PCH built at this point");
LOGGING_FATAL("Expected PCH built at this point");
}
CommandOptions options;
@@ -315,29 +313,24 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
CompilationParams params;
params.kind = CompilationUnit::Content;
params.arguments = database.get_command(path, options).arguments;
params.arguments = database.lookup(path, options).arguments;
params.add_remapped_file(path, content);
params.pch = {pch->path, pch->preamble.size()};
file->diagnostics->clear();
params.diagnostics = file->diagnostics;
params.clang_tidy = config.project.clang_tidy;
/// Check result
auto ast = co_await async::submit([&] { return compile(params); });
if(!ast) {
/// FIXME: Fails needs cancel waiting tasks.
logging::warn("Building AST fails for {}, Beacuse: {}", path, ast.error());
LOGGING_WARN("Building AST fails for {}, Beacuse: {}", path, ast.error());
for(auto& diagnostic: *file->diagnostics) {
logging::warn("{}", diagnostic.message);
LOGGING_WARN("{}", diagnostic.message);
}
co_return;
}
/// Run Clang-Tidy
if(config.project.clang_tidy) {
logging::warn(
"clang-tidy is not fully supported yet. Tracked in https://github.com/clice-project/clice/issues/90.");
}
/// Send diagnostics
auto diagnostics = co_await async::submit(
[&, kind = this->kind] { return feature::diagnostics(kind, mapping, *ast); });
@@ -356,7 +349,7 @@ async::Task<> Server::build_ast(std::string path, std::string content) {
/// Dispose the task so that it will destroyed when task complete.
file->ast_build_task.dispose();
logging::info("Building AST successfully for {}", path);
LOGGING_INFO("Building AST successfully for {}", path);
}
async::Task<std::shared_ptr<OpenFile>> Server::add_document(std::string path, std::string content) {
@@ -370,12 +363,12 @@ async::Task<std::shared_ptr<OpenFile>> Server::add_document(std::string path, st
if(!task.empty()) {
if(task.finished()) {
task.release().destroy();
logging::info("Release old AST building Task!");
LOGGING_INFO("Release old AST building Task!");
} else {
task.cancel();
task.dispose();
}
logging::info("Cancel old AST building Task!");
LOGGING_INFO("Cancel old AST building Task!");
}
/// Create and schedule a new task.

View File

@@ -31,7 +31,7 @@ auto Server::on_completion(proto::CompletionParams params) -> Result {
/// Set compilation params ... .
CompilationParams params;
params.kind = CompilationUnit::Completion;
params.arguments = database.get_command(path).arguments;
params.arguments = database.lookup(path).arguments;
params.add_remapped_file(path, content);
params.pch = {pch->path, pch->preamble.size()};
params.completion = {path, offset};
@@ -88,7 +88,7 @@ async::Task<json::Value> Server::on_signature_help(proto::SignatureHelpParams pa
/// Set compilation params ... .
CompilationParams params;
params.kind = CompilationUnit::Completion;
params.arguments = database.get_command(path, options).arguments;
params.arguments = database.lookup(path, options).arguments;
params.add_remapped_file(path, content);
params.pch = {pch->path, pch->preamble.size()};
params.completion = {path, offset};

View File

@@ -10,12 +10,12 @@ namespace clice {
async::Task<> Indexer::index(llvm::StringRef path) {
CompilationParams params;
params.kind = CompilationUnit::Indexing;
params.arguments = database.get_command(path).arguments;
params.arguments = database.lookup(path).arguments;
auto path_id = project_index.path_pool.path_id(path);
auto& merged_index = get_index(path_id);
if(!merged_index.need_update(project_index.path_pool.paths)) {
logging::info("Check update for {}, not need to update", path);
LOGGING_INFO("Check update for {}, not need to update", path);
co_return;
}
@@ -25,7 +25,7 @@ async::Task<> Indexer::index(llvm::StringRef path) {
auto tu_index = co_await async::submit([&]() -> std::optional<index::TUIndex> {
auto unit = compile(params);
if(!unit) {
logging::info("Fail to index for {}, because: {}", path, unit.error());
LOGGING_INFO("Fail to index for {}, because: {}", path, unit.error());
return std::nullopt;
}
@@ -55,7 +55,7 @@ async::Task<> Indexer::index(llvm::StringRef path) {
std::move(tu_index->graph.locations),
tu_index->main_file_index);
logging::info("Successfully index {}", path);
LOGGING_INFO("Successfully index {}", path);
}
async::Task<> Indexer::schedule_next() {
@@ -83,7 +83,7 @@ async::Task<> Indexer::schedule_next() {
}
async::Task<> Indexer::index_all() {
for(auto& [file, cmd]: database) {
for(auto& file: database.files()) {
waitings.push_back(project_index.path_pool.path_id(file));
}
@@ -107,9 +107,9 @@ void Indexer::load_from_disk() {
if(auto content = fs::read(output_path); content && !content->empty()) {
/// FIXME: from should return a expected ...
project_index = index::ProjectIndex::from(content->data());
logging::info("Load project index form {} successfully", output_path);
LOGGING_INFO("Load project index form {} successfully", output_path);
} else {
logging::info("Fail to load project index form {}", output_path);
LOGGING_INFO("Fail to load project index form {}", output_path);
}
/// FIXME: check indices update ....
@@ -117,9 +117,9 @@ void Indexer::load_from_disk() {
void Indexer::save_to_disk() {
if(auto err = fs::create_directories(config.project.index_dir)) {
logging::warn("Fail to create index output dir: {}, because: {}",
config.project.index_dir,
err);
LOGGING_WARN("Fail to create index output dir: {}, because: {}",
config.project.index_dir,
err);
return;
}
@@ -139,9 +139,7 @@ void Indexer::save_to_disk() {
std::error_code err;
llvm::raw_fd_ostream os(output_path, err, fs::CreationDisposition::CD_CreateAlways);
if(err) {
logging::info("Fail to create output index file: {}, because: {}",
output_path,
err);
LOGGING_INFO("Fail to create output index file: {}, because: {}", output_path, err);
continue;
}
@@ -149,7 +147,7 @@ void Indexer::save_to_disk() {
auto opath_id = project_index.path_pool.path_id(output_path);
project_index.indices.try_emplace(path_id, opath_id);
logging::info("Successfully save index for {} to {}", path, output_path);
LOGGING_INFO("Successfully save index for {} to {}", path, output_path);
}
}
@@ -158,12 +156,12 @@ void Indexer::save_to_disk() {
std::error_code err;
llvm::raw_fd_ostream os(output_path, err, fs::CreationDisposition::CD_CreateAlways);
if(err) {
logging::info("Fail to create output index file: {}, because: {}", output_path, err);
LOGGING_INFO("Fail to create output index file: {}, because: {}", output_path, err);
return;
}
project_index.serialize(os);
logging::info("Successfully save project index to {}", output_path);
LOGGING_INFO("Successfully save project index to {}", output_path);
}
auto Indexer::lookup(llvm::StringRef path, std::uint32_t offset, RelationKind kind) -> Result {
@@ -201,9 +199,8 @@ auto Indexer::lookup(llvm::StringRef path, std::uint32_t offset, RelationKind ki
continue;
}
/// FIXME: User server's encoding kind.
ranges::sort(results, refl::less);
PositionConverter converter(*content, PositionEncodingKind::UTF16);
PositionConverter converter(*content, this->encoding_kind);
for(auto result: results) {
auto begin = converter.toPosition(result.begin);

View File

@@ -3,9 +3,9 @@
namespace clice {
async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
logging::info("Initialize from client: {}, version: {}",
params.clientInfo.name,
params.clientInfo.version);
LOGGING_INFO("Initialize from client: {}, version: {}",
params.clientInfo.name,
params.clientInfo.version);
/// FIXME: adjust position encoding.
kind = PositionEncodingKind::UTF16;
@@ -17,15 +17,19 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
return *params.rootUri;
}
logging::fatal("The client should provide one workspace folder or rootUri at least!");
LOGGING_FATAL("The client should provide one workspace folder or rootUri at least!");
})());
/// Initialize configuration.
if(auto result = config.parse(workspace)) {
logging::info("Config initialized successfully: {0}", json::serialize(config));
LOGGING_INFO("Config initialized successfully: {0:4}", json::serialize(config));
} else {
logging::warn("Fail to load config, because: {0}", result.error());
logging::info("Use default config: {0}", json::serialize(config));
LOGGING_WARN("Fail to load config, because: {0}", result.error());
LOGGING_INFO("Use default config: {0:4}", json::serialize(config));
}
if(!config.project.logging_dir.empty()) {
logging::file_loggger("clice", config.project.logging_dir, logging::options);
}
/// Set server options.

View File

@@ -95,7 +95,7 @@ async::Task<> Server::registerCapacity(llvm::StringRef id,
});
}
Server::Server() : indexer(database, config) {
Server::Server() : indexer(database, config, kind) {
register_callback<&Server::on_initialize>("initialize");
register_callback<&Server::on_initialized>("initialized");
register_callback<&Server::on_shutdown>("shutdown");
@@ -124,7 +124,7 @@ Server::Server() : indexer(database, config) {
async::Task<> Server::on_receive(json::Value value) {
auto object = value.getAsObject();
if(!object) [[unlikely]] {
logging::fatal("Invalid LSP message, not an object: {}", value);
LOGGING_FATAL("Invalid LSP message, not an object: {}", value);
}
/// If the json object has an `id`, it's a request,
@@ -135,7 +135,7 @@ async::Task<> Server::on_receive(json::Value value) {
if(auto result = object->getString("method")) {
method = *result;
} else [[unlikely]] {
logging::warn("Invalid LSP message, method not found: {}", value);
LOGGING_WARN("Invalid LSP message, method not found: {}", value);
if(id) {
co_await response(std::move(*id),
proto::ErrorCodes::InvalidRequest,
@@ -152,7 +152,7 @@ async::Task<> Server::on_receive(json::Value value) {
/// Handle request and notification separately.
auto it = callbacks.find(method);
if(it == callbacks.end()) {
logging::info("Ignore unhandled method: {}", method);
LOGGING_INFO("Ignore unhandled method: {}", method);
co_return;
}
@@ -160,24 +160,24 @@ async::Task<> Server::on_receive(json::Value value) {
auto current_id = client_request_id++;
auto start_time = std::chrono::steady_clock::now();
logging::info("<-- Handling request: {}({})", method, current_id);
LOGGING_INFO("<-- Handling request: {}({})", method, current_id);
auto result = co_await it->second(*this, std::move(params));
co_await response(std::move(*id), std::move(result));
auto end_time = std::chrono::steady_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
logging::info("--> Handled request: {}({}) {}ms", method, current_id, duration.count());
LOGGING_INFO("--> Handled request: {}({}) {}ms", method, current_id, duration.count());
} else {
auto start_time = std::chrono::steady_clock::now();
logging::info("<-- Handling notification: {}", method);
LOGGING_INFO("<-- Handling notification: {}", method);
auto result = co_await it->second(*this, std::move(params));
auto end_time = std::chrono::steady_clock::now();
auto duration =
std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
logging::info("--> Handled notification: {} {}ms", method, duration.count());
LOGGING_INFO("--> Handled notification: {} {}ms", method, duration.count());
}
co_return;

View File

@@ -4,27 +4,54 @@
#include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/ringbuffer_sink.h"
namespace clice::logging {
Options options;
void create_stderr_logger(std::string_view name, const Options& options) {
static std::shared_ptr<spdlog::sinks::ringbuffer_sink_mt> ringbuffer_sink;
constexpr static auto pattern = "[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [thread %t] [%s:%#] %v";
void stderr_logger(std::string_view name, const Options& options) {
std::shared_ptr<spdlog::logger> logger;
logger = spdlog::stderr_color_mt(std::string(name), options.color);
auto console_sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>(options.color);
if(options.replay_console) {
ringbuffer_sink = std::make_shared<spdlog::sinks::ringbuffer_sink_mt>(128);
std::array<spdlog::sink_ptr, 2> sinks = {console_sink, ringbuffer_sink};
logger = std::make_shared<spdlog::logger>(std::string(name), sinks.begin(), sinks.end());
} else {
logger = std::make_shared<spdlog::logger>(std::string(name), console_sink);
}
logger->set_level(options.level);
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [thread %t] [%s:%#] %v");
logger->set_pattern(pattern);
logger->flush_on(Level::trace);
spdlog::set_default_logger(std::move(logger));
}
void create_file_loggger(std::string_view name, std::string_view dir, const Options& options) {
void file_loggger(std::string_view name, std::string_view dir, const Options& options) {
auto now = std::chrono::system_clock::now();
auto filename = std::format("{:%Y-%m-%d_%H-%M-%S}.log", now);
auto path = path::join(dir, filename);
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path::join(dir, filename));
auto logger = spdlog::basic_logger_mt(std::string(name), path);
if(options.replay_console && ringbuffer_sink) {
sink->set_level(options.level);
sink->set_pattern(pattern);
for(auto& log: ringbuffer_sink->last_raw()) {
sink->log(log);
}
ringbuffer_sink.reset();
}
auto logger = std::make_shared<spdlog::logger>(std::string(name), std::move(sink));
logger->set_level(options.level);
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [thread %t] [%s:%#] %v");
logger->set_pattern(pattern);
logger->flush_on(Level::trace);
spdlog::set_default_logger(std::move(logger));
}

View File

@@ -35,12 +35,6 @@ def pytest_addoption(parser: pytest.Parser):
help="The port to connect to",
)
parser.addoption(
"--resource-dir",
required=False,
help="Path to the of the clang resource directory.",
)
@pytest.fixture(scope="session")
def executable(request) -> Path | None:
@@ -59,14 +53,6 @@ def executable(request) -> Path | None:
return path.resolve()
@pytest.fixture(scope="session")
def resource_dir(request) -> Path | None:
path = request.config.getoption("--resource-dir")
if not path:
return None
return Path(path).resolve()
@pytest.fixture(scope="session")
def test_data_dir(request):
path = os.path.join(os.path.dirname(__file__), "data")
@@ -74,9 +60,7 @@ def test_data_dir(request):
@pytest_asyncio.fixture(scope="function")
async def client(
request, executable: Path | None, resource_dir: Path | None, test_data_dir: Path
):
async def client(request, executable: Path | None, test_data_dir: Path):
config = request.config
mode = config.getoption("--mode")
@@ -85,9 +69,6 @@ async def client(
f"--mode={mode}",
]
if resource_dir:
cmd.append(f"--resource-dir={resource_dir}")
client = LSPClient(
cmd,
mode,

View File

@@ -1,6 +1,25 @@
#include <iostream>
// Tests bugprone-integer-division
int main() {
std::cout << "Hello World!" << std::endl;
float floatFunc(float);
int intFunc(int);
double d;
int i = 42;
// Warn, floating-point values expected.
d = 32 * 8 / (2 + i);
d = 8 * floatFunc(1 + 7 / 2);
d = i / (1 << 4);
// OK, no integer division.
d = 32 * 8.0 / (2 + i);
d = 8 * floatFunc(1 + 7.0 / 2);
d = (double)i / (1 << 4);
// OK, there are signs of deliberateness.
d = 1 << (i / 2);
d = 9 + intFunc(6 * i / 32);
d = (int)(i / 32) - 8;
return 0;
}

View File

@@ -97,7 +97,7 @@ suite<"Command"> command = [] {
CommandOptions options;
options.suppress_logging = true;
expect(eq(result, print_argv(database.get_command(file, options).arguments)));
expect(eq(result, print_argv(database.lookup(file, options).arguments)));
};
/// Filter -c, -o and input file.
@@ -131,8 +131,8 @@ suite<"Command"> command = [] {
CommandOptions options;
options.suppress_logging = true;
auto command1 = database.get_command("test.cpp", options).arguments;
auto command2 = database.get_command("test2.cpp", options).arguments;
auto command1 = database.lookup("test.cpp", options).arguments;
auto command2 = database.lookup("test2.cpp", options).arguments;
expect(eq(command1.size(), 3));
expect(eq(command2.size(), 3));
@@ -166,32 +166,32 @@ suite<"Command"> command = [] {
remove = {"-DA"};
options.remove = remove;
auto result = database.get_command("main.cpp", options).arguments;
auto result = database.lookup("main.cpp", options).arguments;
expect(eq(print_argv(result), "clang++ -D B=0 main.cpp"));
remove = {"-D", "A"};
options.remove = remove;
result = database.get_command("main.cpp", options).arguments;
result = database.lookup("main.cpp", options).arguments;
expect(eq(print_argv(result), "clang++ -D B=0 main.cpp"));
remove = {"-DA", "-D", "B=0"};
options.remove = remove;
result = database.get_command("main.cpp", options).arguments;
result = database.lookup("main.cpp", options).arguments;
expect(eq(print_argv(result), "clang++ main.cpp"));
remove = {"-D*"};
options.remove = remove;
result = database.get_command("main.cpp", options).arguments;
result = database.lookup("main.cpp", options).arguments;
expect(eq(print_argv(result), "clang++ main.cpp"));
remove = {"-D", "*"};
options.remove = remove;
result = database.get_command("main.cpp", options).arguments;
result = database.lookup("main.cpp", options).arguments;
expect(eq(print_argv(result), "clang++ main.cpp"));
append = {"-D", "C"};
options.append = append;
result = database.get_command("main.cpp", options).arguments;
result = database.lookup("main.cpp", options).arguments;
expect(eq(print_argv(result), "clang++ -D C main.cpp"));
};
@@ -201,7 +201,7 @@ suite<"Command"> command = [] {
database.update_command("/fake",
"main.cpp",
llvm::StringRef("clang++ @test.txt -std= main.cpp"));
auto info = database.get_command("main.cpp", {.query_driver = false});
auto info = database.lookup("main.cpp", {.query_driver = false});
};
skip_unless(CIEnvironment) / test("QueryDriver") = [] {
@@ -241,7 +241,7 @@ suite<"Command"> command = [] {
CompilationDatabase database;
using namespace std::literals;
database.update_command("/fake", "main.cpp", "clang++ -std=c++23 test.cpp"sv);
auto arguments = database.get_command("main.cpp", {.resource_dir = true}).arguments;
auto arguments = database.lookup("main.cpp", {.resource_dir = true}).arguments;
fatal / expect(eq(arguments.size(), 4));
expect(eq(arguments[0], "clang++"sv));
@@ -261,7 +261,7 @@ suite<"Command"> command = [] {
CommandOptions options;
options.suppress_logging = true;
auto info = database.get_command(file, options);
auto info = database.lookup(file, options);
expect(info.directory == directory);
expect(info.arguments.size() == arguments.size());

View File

@@ -2,6 +2,7 @@
#include "Compiler/Preamble.h"
#include "Compiler/Compilation.h"
#include "Test/Annotation.h"
#include "Compiler/Scan.h"
namespace clice::testing {
@@ -269,6 +270,23 @@ int y = foo();
auto unit = compile(params);
expect(that % unit.has_value());
};
test("Scan") = [&] {
llvm::StringRef content = R"(
#include <iostream>
#include "test/file"
export module A:
)";
auto result = scan(content);
for(auto& tok: result.module_name) {
std::println("{}", tok.text(content));
}
for(auto& tok: result.includes) {
std::println("include: {}", tok.file);
}
};
};
} // namespace

View File

@@ -1,19 +1,30 @@
#include "Test/Test.h"
#include "Compiler/Tidy.h"
#include "Compiler/Compilation.h"
namespace clice::testing {
namespace {
suite<"ClangTidy"> clang_tidy = [] {
test("is_fast_tidy_check") = [] {
expect(that % tidy::is_fast_tidy_check("readability-misleading-indentation"));
expect(that % tidy::is_fast_tidy_check("bugprone-unused-return-value"));
test("FastCheck") = [] {
expect(tidy::is_fast_tidy_check("readability-misleading-indentation"));
expect(tidy::is_fast_tidy_check("bugprone-unused-return-value"));
// clangd/unittests/TidyProviderTests.cpp
expect(that % tidy::is_fast_tidy_check("misc-const-correctness") == false);
expect(that % tidy::is_fast_tidy_check("bugprone-suspicious-include") == true);
expect(that % tidy::is_fast_tidy_check("replay-preamble-check") == std::nullopt);
expect(tidy::is_fast_tidy_check("misc-const-correctness") == false);
expect(tidy::is_fast_tidy_check("bugprone-suspicious-include") == true);
expect(tidy::is_fast_tidy_check("replay-preamble-check") == std::nullopt);
};
test("Tidy") = [] {
CompilationParams params;
params.clang_tidy = true;
params.arguments = {"clang++", "main.cpp"};
params.add_remapped_file("main.cpp", "int main() { return 0 }");
auto unit = compile(params);
expect(unit.has_value());
expect(!unit->diagnostics().empty());
};
};

View File

@@ -36,7 +36,7 @@ struct GetUSRVisitor : public clang::RecursiveASTVisitor<GetUSRVisitor> {
if(offset.has_value() && USR.has_value()) {
USRs[*offset] = USRInfo{*USR, *offset, decl};
// logging::info("USR: {} at {}:{}", USR->str(), pos->line, pos->character);
// LOGGING_INFO("USR: {} at {}:{}", USR->str(), pos->line, pos->character);
}
return true;
@@ -61,7 +61,7 @@ struct USRTester : public Tester {
llvm::StringRef lookup(llvm::StringRef key) {
auto iter = USRs.find((*this)["main.cpp", key]);
if(iter == USRs.end()) {
logging::fatal("USR not found for key: {}", key);
LOGGING_FATAL("USR not found for key: {}", key);
}
return iter->second.USR;
}

136
xmake.lua
View File

@@ -1,4 +1,4 @@
set_xmakever("3.0.3")
set_xmakever("3.0.0")
set_project("clice")
set_allowedplats("windows", "linux", "macosx")
@@ -16,7 +16,7 @@ if has_config("dev") then
set_runtimes("MD")
if is_mode("debug") then
print("clice does not support build in debug mode with pre-compiled llvm binary on windows.\n"
.."See https://github.com/clice-io/clice/issues/42 for more information.")
.. "See https://github.com/clice-io/clice/issues/42 for more information.")
os.raise()
end
elseif is_mode("debug") and is_plat("linux", "macosx") then
@@ -40,7 +40,8 @@ if has_config("release") then
end
add_defines("TOML_EXCEPTIONS=0")
add_requires(libuv_require, "spdlog[header_only=n,std_format,noexcept]" ,"toml++", "croaring", "flatbuffers")
add_requires("spdlog", {system = false, version = "1.15.3", configs = {header_only = false, std_format = true, noexcept = true}})
add_requires(libuv_require, "toml++", "croaring", "flatbuffers")
add_requires("clice-llvm", {alias = "llvm"})
add_rules("mode.release", "mode.debug", "mode.releasedbg")
@@ -52,7 +53,7 @@ target("clice-core")
add_files("src/**.cpp|Driver/*.cpp", "include/Index/schema.fbs")
add_includedirs("include", {public = true})
add_rules("flatbuffers.schema.gen")
add_rules("flatbuffers.schema.gen", "clice_clang_tidy_config")
add_packages("flatbuffers")
add_packages("libuv", "spdlog", "toml++", "croaring", {public = true})
@@ -63,14 +64,13 @@ target("clice-core")
"LLVMSupport",
"LLVMFrontendOpenMP",
"LLVMOption",
"LLVMTargetParser",
"clangAST",
"clangASTMatchers",
"clangBasic",
"clangDependencyScanning",
"clangDriver",
"clangFormat",
"clangFrontend",
"clangIndex",
"clangLex",
"clangSema",
"clangSerialization",
@@ -105,29 +105,34 @@ target("clice-core")
"clangToolingInclusions",
"clangToolingInclusionsStdlib",
"clangToolingSyntax",
}})
on_config(function (target)
}
})
on_config(function(target)
local llvm_dynlib_dir = path.join(target:pkg("llvm"):installdir(), "lib")
target:add("rpathdirs", llvm_dynlib_dir)
end)
elseif is_mode("release", "releasedbg") then
add_packages("llvm", {public = true})
add_ldflags("-Wl,--gc-sections")
end
target("clice")
set_kind("binary")
add_files("bin/clice.cc")
add_deps("clice-core")
on_config(function (target)
-- workaround
-- @see https://github.com/xmake-io/xmake/issues/7029
if is_plat("macosx") then
set_toolset("dsymutil", "dsymutil")
end
on_config(function(target)
local llvm_dir = target:dep("clice-core"):pkg("llvm"):installdir()
target:add("installfiles", path.join(llvm_dir, "lib/clang/(**)"), {prefixdir = "lib/clang"})
end)
after_build(function (target)
local res_dir = path.join(target:targetdir(), "lib/clang")
after_build(function(target)
local res_dir = path.join(target:targetdir(), "../lib/clang")
if not os.exists(res_dir) then
local llvm_dir = target:dep("clice-core"):pkg("llvm"):installdir()
os.vcp(path.join(llvm_dir, "lib/clang"), res_dir)
@@ -144,10 +149,9 @@ target("unit_tests")
add_tests("default")
after_load(function (target)
after_load(function(target)
target:set("runargs",
"--test-dir=" .. path.absolute("tests/data"),
"--resource-dir=" .. path.join(target:dep("clice-core"):pkg("llvm"):installdir(), "lib/clang/20")
"--test-dir=" .. path.absolute("tests/data")
)
end)
@@ -160,7 +164,7 @@ target("integration_tests")
add_tests("default")
on_test(function (target, opt)
on_test(function(target, opt)
import("lib.detect.find_tool")
local uv = assert(find_tool("uv"), "uv not found!")
@@ -169,7 +173,6 @@ target("integration_tests")
"--log-cli-level=INFO",
"-s", "tests/integration",
"--executable=" .. target:dep("clice"):targetfile(),
"--resource-dir=" .. path.join(target:pkg("llvm"):installdir(), "lib/clang/20"),
}
local opt = {envs = envs, curdir = os.projectdir()}
os.vrunv(uv.program, argv, opt)
@@ -177,16 +180,36 @@ target("integration_tests")
return true
end)
rule("clice_build_config")
on_load(function (target)
target:add("cxflags", "-fno-rtti", {tools = {"clang", "clangxx", "gcc", "gxx"}})
target:add("cxflags", "/GR-", {tools = {"clang_cl", "cl"}})
-- Fix MSVC Non-standard preprocessor caused error C1189
-- While compiling Command.cpp, MSVC won't expand Options macro correctly
-- Output: D:\Desktop\code\clice\build\.packages\l\llvm\20.1.5\cc2aa9f1d09a4b71b6fa3bf0011f6387\include\clang/Driver/Options.inc(3590): error C2365: “clang::driver::options::OPT_”: redefinition; previous definition was 'enumerator'
target:add("cxflags", "cl::/Zc:preprocessor")
rule("clice_clang_tidy_config")
on_load(function(target)
import("core.project.depend")
local autogendir = path.join(target:autogendir(), "rules/clice_clang_tidy_config")
os.mkdir(autogendir)
target:add("includedirs", autogendir, {public = true})
local src = path.join(os.projectdir(), "config/clang-tidy-config.h")
depend.on_changed(function()
os.vcp(src, path.join(autogendir, "clang-tidy-config.h"))
end, {
files = src,
changed = target:is_rebuilt()
})
end)
before_build(function (target)
local dest = path.join(target:autogendir(), "rules/clice_clang_tidy_config/clang-tidy-config.h")
if not os.exists(dest) then
local src = path.join(os.projectdir(), "config/clang-tidy-config.h")
os.vcp(src, dest)
end
end)
rule("clice_build_config")
on_load(function(target)
target:set("exceptions", "no-cxx")
target:add("cxflags", "-fno-rtti", "-Wno-undefined-inline", {tools = {"clang", "clangxx", "gcc", "gxx"}})
target:add("cxflags", "/GR-", "/Zc:preprocessor", {tools = {"clang_cl", "cl"}})
if target:is_plat("windows") and not target:toolchain("msvc") then
target:set("toolset", "ar", "llvm-ar")
@@ -197,9 +220,11 @@ rule("clice_build_config")
target:add("ldflags", "-fuse-ld=lld-link")
end
elseif target:is_plat("linux") then
-- gnu ld need to fix link order
target:add("ldflags", "-fuse-ld=lld")
target:add("ldflags", "-fuse-ld=lld", "-static-libstdc++", "-Wl,--gc-sections")
elseif target:is_plat("macosx") then
target:add("ldflags", "-fuse-ld=lld", "-static-libc++", "-Wl,-dead_strip,-object_path_lto,clice.lto.o")
end
if has_config("ci") then
target:add("cxxflags", "-DCLICE_CI_ENVIRONMENT")
end
@@ -208,7 +233,7 @@ rule("clice_build_config")
rule("flatbuffers.schema.gen")
set_extensions(".fbs")
on_prepare_files(function (target, jobgraph, sourcebatch, opt)
on_prepare_files(function(target, jobgraph, sourcebatch, opt)
import("lib.detect.find_tool")
import("core.project.depend")
import("utils.progress")
@@ -225,7 +250,7 @@ rule("flatbuffers.schema.gen")
local generate_dir = path.normalize(path.join(autogendir, path.directory(sourcefile)))
target:add("includedirs", generate_dir, {public = true})
os.mkdir(generate_dir)
jobgraph:add(job, function (index, total, opt)
jobgraph:add(job, function(index, total, opt)
local argv = {
"--cpp",
"-o", generate_dir,
@@ -249,15 +274,17 @@ package("clice-llvm")
if has_config("llvm") then
set_sourcedir(get_config("llvm"))
else
on_source(function (package)
on_source(function(package)
import("core.base.json")
local info = json.loadfile("./config/prebuilt-llvm.json")
for _, info in ipairs(info) do
if get_config("mode") == info.build_type:lower()
and get_config("plat") == info.platform:lower()
and (info.is_lto == has_config("release")) then
package:add("urls", format("https://github.com/clice-io/llvm-binary/releases/download/%s/%s", info.version, info.filename))
if info.platform:lower() == get_config("plat")
and (info.build_type:lower() == get_config("mode")
or info.build_type:lower() == "release" and get_config("mode") == "releasedbg")
and (info.is_lto == has_config("release"))
then
package:add("urls", format("https://github.com/clice-io/clice-llvm/releases/download/%s/%s", info.version, info.filename))
package:add("versions", info.version, info.sha256)
end
end
@@ -274,7 +301,7 @@ package("clice-llvm")
add_syslinks("version", "ntdll")
end
on_install(function (package)
on_install(function(package)
if not package:config("shared") then
package:add("defines", "CLANG_BUILD_STATIC")
end
@@ -288,19 +315,44 @@ if has_config("release") then
xpack("clice")
if is_plat("windows") then
set_formats("zip")
set_extension(".7z")
set_extension(".zip")
else
set_formats("targz")
set_extension(".tar.xz")
set_extension(".tar.gz")
end
set_prefixdir("clice")
add_targets("clice")
add_installfiles(path.join(os.projectdir(), "docs/clice.toml"))
-- add_installfiles(path.join(os.projectdir(), "docs/clice.toml"))
on_load(function (package)
local llvm_dir = package:target("clice"):dep("clice-core"):pkg("llvm"):installdir()
package:add("installfiles", path.join(llvm_dir, "lib/clang/(**)"), {prefixdir = "lib/clang"})
on_package(function (package)
import("utils.archive")
local build_dir = path.absolute(package:install_rootdir())
os.tryrm(build_dir)
os.mkdir(build_dir)
local function clice_archive(output_file)
local old_dir = os.cd(build_dir)
local archive_files = os.files("**")
os.cd(old_dir)
os.tryrm(output_file)
cprint("packing %s .. ", output_file)
archive.archive(path.absolute(output_file), archive_files, {curdir = build_dir, compress = "best"})
end
local target = package:target("clice")
os.vcp(target:symbolfile(), build_dir)
clice_archive(path.join(package:outputdir(), "clice-symbol" .. package:extension()))
os.tryrm(build_dir)
os.mkdir(path.join(build_dir, "bin"))
os.vcp(target:targetfile(), path.join(build_dir, "bin"))
os.vcp(path.join(os.projectdir(), "docs/clice.toml"), build_dir)
local llvm_dir = target:dep("clice-core"):pkg("llvm"):installdir()
os.vcp(path.join(llvm_dir, "lib/clang"), path.join(build_dir, "lib/clang"))
clice_archive(path.join(package:outputdir(), "clice" .. package:extension()))
end)
end