Compare commits
14 Commits
v0.1.0-alp
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16867902c | ||
|
|
4d07bad2f2 | ||
|
|
4a2a6aa65a | ||
|
|
3c53d3bc72 | ||
|
|
9e1039f861 | ||
|
|
8a2ef62596 | ||
|
|
336ca639f0 | ||
|
|
9c43285d0d | ||
|
|
39ec9bf7c5 | ||
|
|
397eb71dad | ||
|
|
3b1e379408 | ||
|
|
8b998e658c | ||
|
|
9806e45fa3 | ||
|
|
7d71c0f689 |
1
.github/workflows/check-format.yml
vendored
1
.github/workflows/check-format.yml
vendored
@@ -7,7 +7,6 @@ on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
78
.github/workflows/cmake.yml
vendored
78
.github/workflows/cmake.yml
vendored
@@ -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
137
.github/workflows/package.yml
vendored
Normal 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
|
||||
96
.github/workflows/release.yml
vendored
96
.github/workflows/release.yml
vendored
@@ -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
|
||||
127
.github/workflows/xmake.yml
vendored
127
.github/workflows/xmake.yml
vendored
@@ -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
3
.gitignore
vendored
@@ -49,6 +49,9 @@ temp/
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
tests/unit/Local/
|
||||
perf.data
|
||||
flamegraph.svg
|
||||
|
||||
|
||||
**/node_modules
|
||||
docs/.vitepress/dist
|
||||
|
||||
@@ -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)
|
||||
|
||||
89
bin/clice.cc
89
bin/clice.cc
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
11
config/clang-tidy-config.h
Normal file
11
config/clang-tidy-config.h
Normal 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
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -627,7 +627,6 @@ public:
|
||||
}
|
||||
|
||||
case clang::NestedNameSpecifier::TypeSpec:
|
||||
case clang::NestedNameSpecifier::TypeSpecWithTemplate:
|
||||
case clang::NestedNameSpecifier::Global:
|
||||
case clang::NestedNameSpecifier::Super: {
|
||||
break;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {}
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
32
include/Compiler/Scan.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
51
include/Compiler/Toolchain.h
Normal file
51
include/Compiler/Toolchain.h
Normal 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);
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -230,7 +230,7 @@ private:
|
||||
/// All registered LSP callbacks.
|
||||
llvm::StringMap<Callback> callbacks;
|
||||
|
||||
PositionEncodingKind kind;
|
||||
PositionEncodingKind kind = PositionEncodingKind::UTF16;
|
||||
|
||||
std::string workspace;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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__);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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()};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
144
src/Compiler/Driver.h
Normal 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
|
||||
@@ -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
49
src/Compiler/Scan.cpp
Normal 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
|
||||
@@ -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
33
src/Compiler/TidyImpl.h
Normal 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
282
src/Compiler/Toolchain.cpp
Normal 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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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
136
xmake.lua
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user