A może Clangd zamiast IntellliSense?
June 3, 2024

Od dawna słyszałem, że Clangd jest lepszy niż IntelliSense. Ale co to właściwie znaczy lepszy? To przecież tylko opinia. Postanowiłem to sprawdzić. W poniższym tekście opisuję, jak zamieniłem IntelliSense na Clangd i jakie były tego efekty.

Podczas pracy nad projektem na nRF z Zephyrem przy użyciu Visual Studio Code wpadliśmy na pomysł: A może by tak zamienić IntelliSense na Clangd? Czy warto spróbować? Teoretycznie oferują to samo, tylko w inny sposób. Postanowiłem zobaczyć, czy faktycznie warto.

Podmieniamy IntelliSense na Clandg w VSC na Zephyr RTOS.

IntelliSense -> Clang

No to zaczynamy.

1) Dodaj konfigurację wtyczki clangd do settings.json:

/// CLANGD settings:
    "C_Cpp.intelliSenseEngine": "disabled",
	"clangd.path": "/usr/bin/clangd", //if clangd is not in PATH
	"clangd.arguments": [
            "--pretty",
            "--enable-config",
            "--background-index",
            "--compile-commands-dir=${workspaceFolder}/build",
            "--query-driver=${env:ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/bin/arm-zephyr-eabi-*",
			// "--log=verbose",
			"--log=error"
    ],
"clangd.checkUpdates": false
/// CLANGD settings^

2) Jeszcze plik konfiguracyjny projektu w głównym katalogu projektu. Plik musi mieć nazwę “.clangd” a jego zawartość to:

CompileFlags:
  Add: -Wno-unknown-warning-option
  Remove: [-mfp16-format=ieee, -fno-reorder-functions ]
Diagnostics:
  UnusedIncludes: Strict
  Suppress:
    - drv_unknown_argument

3) Zainstaluj wtyczkę clangd w swoim VSC:

Clangd in VSC

Wtyczka powinna zainstalować clangd jeśli nie znajdzie go w PATH, albo w katalogu podanym w punkcie 1 ("clangd.path": "/usr/bin/clangd").

4) Zainstaluj wtyczkę clangd w swoim VSC:

Teraz ciesz się autouzupełnianiem, rozpoznawaniem ścieżek, informacjami o obiektach i sprawdzaniem kodu w czasie edycji. 
I to w całkiem miłej dla developera formie:

Clangd w boju


Troubleshooting

Tutaj przedstawiamy przykład konfiguracji dla procesorów nRF i Zephyra, ale po wprowadzeniu drobnych zmian, to podejście będzie skuteczne również w innych przypadkach.

Podczas pracy z Zephyrem warto dodać dodatkowy plik konfiguracyjny, który będzie miał zasięg globalny. Plik .clangd umieszczony w głównym katalogu projektu będzie działać na wszystkie pliki znajdujące się poniżej w strukturze katalogów. Możesz mieć różne konfiguracje dla różnych katalogów, po prostu umieszczając w nich oddzielne pliki .clangd. Jednak gdy zaczniesz przeglądać pliki źródłowe Zephyra poza swoim projektem, ta konfiguracja przestanie obowiązywać. Przykładowo, po otwarciu pliku kernel.h z katalogu Zephyra mogą pojawić się błędy.

Clangd a problem z zasięgiem

Wszystko dlatego, że clangd nie rozumie argumentów kompilatora naszego projektu, a powyżej pliku “kernel.h” nie ma żadnego pliku “.clangd”, który by mu je kazał zignorować. Globalny plik konfiguracyjny nazywa się z jakiegoś powodu inaczej: “config.yaml”. Jego lokalizacja zależy od systemu operacyjnego:

  • Windows: %LocalAppData%\clangd\config.yaml;
  • Linux: ~/.config/clangd/config.yaml;

Skopiuj zawartość swojego pliku “.clangd” do “config.yaml” i problem będzie rozwiązany.

Możesz szybko uzyskać dostęp do obu plików konfiguracyjnych w Visual Studio Code, naciskając [CTRL+SHIFT+P] i wpisując “clangd: configuration”.

Dodajemy Clangd configuration

Aby zewnętrzne narzędzie dobrze radziło sobie z analizą kodu C/C++, musi "wiedzieć" to samo, co kompilator. Oznacza to znajomość, w jaki sposób każdy plik jest kompilowany (z jakimi flagami, includami itp.) oraz "wewnętrzne" elementy toolchainu. Dostarczamy te informacje, podając:

  • Ścieżkę do compile_commands.json (w parametrze: --compile-commands-dir=${workspaceFolder}/build);
  • Kompilator/toolchain (w parametrze: --query-driver=${env:ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi/bin/arm-zephyr-eabi-* - dzięki “*” clangd sam dobierze, czy ma to być arm-zephyr-eabi-gcc, czy g++);

Oczywiście, ścieżki można podać jako absolutne, ale w przyszłości trzeba pamiętać o ich aktualizacji, jeśli zmienimy toolchain lub katalog wyjściowy.

Konfigurację wtyczki clangd możesz zrobić też przez GUI:

W pliku “.clangd” możemy też suppresować błędy, nawet dla pojedynczych plików lub grup. Przykład:

Przykład supresacji błędów

Oczywiście, pamiętajcie: "Don't shoot the messenger".

Ważna uwaga

W przypadku naszego projektu opartego na NRF SDK v2.6.1, najnowszy clangd (v1.8.3) wyrzucił z listy inkludowanych katalogów “...arm-zephyr-eabi/12.2.0/include/”. To uniemożliwiało znalezienie niektórych plików nagłówkowych Zephyra (błąd: In included file: 'stddef.h' file not found). Zalecam więc instalację wersji np. 17.0.3 - w tej wersji nie mieliśmy tego problemu.

Co zrobić, jeśli coś nie działa?

1. Przestaw poziom logowania z --log=error na --log=verbose (w konfiguracji wtyczki clangd lub w settings.json).

2. Otwórz jakiś plik .c/.cpp, żeby clangd miał co analizować.

3. Otwórz “Output” wtyczki clangd:

4. Zrestartuj clangd wciskając [CTRL+SHIFT+P] i wpisując “clangd: restart”:

5. Zobacz na początek, czy clangd dostaje poprawne parametry:

Sprawdź też ścieżki w logu i porównaj te z compile_commands.json.

I to wszystko.

Podsumowanie


Po zmianie IntelliSense na Clangd zauważyliśmy kilka istotnych korzyści:

  • Skuteczniejsze zrozumienie języka: Clangd lepiej rozumie strukturę i hierarchię kodu, co pozwala na bardziej precyzyjną analizę.
  • Lepsze wsparcie dla hierarchii: Możesz łatwiej zobaczyć strukturę i zależności w kodzie.
  • Szybsze podpowiedzi i automatyzacja: Clangd natychmiast informuje o brakujących includach lub automatycznie je dodaje, co przyspiesza pracę.

Te zmiany zdecydowanie usprawniły naszą pracę i poprawiły efektywność kodowania.

Clangd ma wielu fanów w GoodByte, więc może i Wy poświęcicie dzień lub dwa, aby się z nim zapoznać.
To naprawdę fajne narzędzie. Serio.