Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support a language server (like ccls or clangd) (IDFGH-4929) #6721

Open
grobx opened this issue Mar 15, 2021 · 49 comments
Open

Support a language server (like ccls or clangd) (IDFGH-4929) #6721

grobx opened this issue Mar 15, 2021 · 49 comments
Labels
Status: Opened Issue is new Type: Feature Request Feature request for IDF

Comments

@grobx
Copy link

grobx commented Mar 15, 2021

Problem
I have tried to index an example project (uart_echo_rs485) without success. Here are the steps to reproduce (${IDF_PATH} is where the release/v4.3 have been cloned and installed):

cd ${IDF_PATH}
source export.sh
cd examples/peripherals/uart/uart_echo_rs485
CMAKE_EXPORT_COMPILE_COMMAND=yes idf.py build
ln -s build/compile_commands.json
ccls -index=.
du -hs .ccls-cache

The last command should not print 0 .ccls-cache as the cache should not be empty.

Solution

I would love to use ccls to index my esp idf project, or any other language server.

Alternatives

I haven't tried clangd.

@grobx grobx added the Type: Feature Request Feature request for IDF label Mar 15, 2021
@espressif-bot espressif-bot added the Status: Opened Issue is new label Mar 15, 2021
@github-actions github-actions bot changed the title Support a language server (like ccls or clangd) Support a language server (like ccls or clangd) (IDFGH-4929) Mar 15, 2021
@igrr
Copy link
Member

igrr commented Mar 15, 2021

@roberti42 I'm afraid you will need to get some suggestions from ccls documentation or issue tracker on debugging this issue. When running idf.py build or idf.py reconfigure, build/compile_commands.json file will be generated by CMake. ESP-IDF doesn't affect this part. It is of course possible that something is wrong with the compile_commands.json file, but given that it is generated by CMake and not by our custom tools, it seems to be unlikely.

My guess is that ccls tries to pass the arguments found in compile_commands.json to clang, which fails because 1) some of the options are architecture-specific (e.g. -mlongcalls) and clang built for x86_64 doesn't recognize such xtensa-specific options, and 2) some of the options are specific to GCC, and not supported by clang. ccls has a configuration option (see https://github.com/MaskRay/ccls/wiki/FAQ#compiling-with-gcc) to filter out such options. But that is only my guess — inspecting ccls logs may point you in some other direction.

@grobx
Copy link
Author

grobx commented Mar 18, 2021

Thank you @igrr, I understand that this is related to ccls (or clangd) more than esp-idf. Is there any alternative to use features such as code completion and the like when developing using esp-idf?

@jsmestad
Copy link

jsmestad commented Jun 24, 2021

Just wanted to say this is the only thing I am missing from esp-idf. I have tried clangd and ccls without getting a configuration that works.


I am stuck with an error I can't get passed (for anyone else like me who stumbles here):

In included file: argument to 'section' attribute is not valid for this target: mach-o section specifier requires a segment and section separated by a comma

So far the .clangd configuration I seem to have gotten the furthest with:

CompileFlags:
  Add: [
    -ferror-limit=0,
    -mlong-calls,
    -I/Users/justinsmestad/esp/esp-idf/components/**,
    -I/Users/justinsmestad/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/xtensa-esp32-elf/include,
    -I/Users/justinsmestad/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/xtensa-esp32-elf/xtensa-esp32-elf/include,
    -I/Users/justinsmestad/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/xtensa-esp32-elf/xtensa-esp32-elf/include/c++/8.4.0/,
  ]
  Remove: [-mlongcalls, -fstrict-volatile-bitfields]

@mikebwilliams
Copy link

I seem to have it working with neovim-0.5, the built in LSP, and clangd 12. I added this lua code to the configuration in init.vim

local lsp = require 'lspconfig'
require'lspconfig'.clangd.setup { 
        cmd = { "clangd", "--background-index --query-driver=/home/mike/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/**/bin/xtensa-esp32-elf-*" },
        root_dir = lsp.util.root_pattern('build/compile_commands.json', '.git'),
        }

I was able to remove all of the -I lines from the .clangd file. It seems clangd still wants to pick up stuff like <stdio.h> from the system include dirs instead of those that the xtensa toolchain uses. Everything else is working though, I can quickly navigate through all the FreeRTOS and ESP-IDF source like I wanted.

@dsalnikov
Copy link

dsalnikov commented Aug 25, 2021

@mikebwilliams, could you clarify how you've configured Nvim?
If I put following lines into init.vim:

 lua << EOF
 local lsp = require 'lspconfig'
 require'lspconfig'.clangd.setup {
         cmd = { "clangd", "--background-index --query-driver=/home/mike/.espressif/tools/xtensa-esp32-elf/esp-2020r3-8.4.0/**/bin/xtensa-esp32-elf-*" },
         root_dir = lsp.util.root_pattern('build/compile_commands.json', '.git'),
         }
 EOF

I get "Client 1 quit with exit code 1 and signal 0" message in a status bar and LSP is not working at all.

@mikebwilliams
Copy link

Did you set the --query-driver to the location of the toolchain on your computer? It's set to /home/mike/... in the command above

@dsalnikov
Copy link

dsalnikov commented Aug 26, 2021

Yes, my compiler path is /Users/dsalnikov/.espressif/tools/xtensa-esp32-elf/esp-2021r1-8.4.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc, so as a cmd I use "/Users/dsalnikov/.espressif/tools/xtensa-esp32-elf/esp-2021r1-8.4.0/**/bin/xtensa-esp32-elf-*"

Looks like I have to specify commands separately:

local lsp = require 'lspconfig'
require'lspconfig'.clangd.setup {
        cmd = { "clangd", "--background-index", "--query-driver=/Users/dsalnikov/.espressif/tools/xtensa-esp32-elf/esp-2021r1-8.4.0/**/bin/xtensa-esp32-elf-*"},
        root_dir = lsp.util.root_pattern('build/compile_commands.json', '.git'),
        }
EOF

Now LSP starts fine.

PS
To debug set vim.lsp.set_log_level("debug") in config and call :LspInfo to get log file path.

@danngreen
Copy link

Has anyone gotten rid of this error?

In included file: argument to 'section' attribute is not valid for this target: mach-o section specifier requires a segment and section separated by a comma

I get it when including certain esp-idf headers, such as <esp_wifi.h>. Simple example to demonstrate:

//test.c
#include <esp_wifi.h>

The lsp logs show that it's compiling a native target-triple: -triple x86_64-apple-macosx12.0.0, so I'm guessing that architecture doesn't support whatever 'section' attributes that the esp architecture does.

Perhaps there's a more compatible target-triple we can use?

@tolgraven
Copy link

tolgraven commented Dec 18, 2021

So what's happening is clangd doesn't recognize xtensa-esp32-elf as a valid target/triple/arch, and falls back on empty (resulting in native), causing all kinds of trouble and weird errors, some of which can be circumvented by adding and removing args etc, but not all.

Getting everything working required cloning the xtensa llvm fork at https://github.com/espressif/llvm-project and building clangd from there:

cmake -S llvm -B build -G Ninja -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra"
cmake --build build

Then simply use the clangd in build/bin instead of system.

This is my .clangd, removing/adding some args is still necessary. And the automatic driver extraction sysroot makes no sense so I had to put that in as well.

CompileFlags:
  Add: [-mlong-calls, -isysroot=/Users/tol/.espressif/tools/xtensa-esp32-elf/esp-2021r2-8.4.0/xtensa-esp32-elf]
  Remove: [-fno-tree-switch-conversion, -mtext-section-literals, -mlongcalls, -fstrict-volatile-bitfields]

To easily make it work for everyone Espressif simply needs to build and distribute clangd in their existing xtensa-clang (that can be downloaded with idf_tools.py install xtensa-clang).

@igrr
Copy link
Member

igrr commented Dec 18, 2021

Thanks for the recommendation @tolgraven, I'll create a task to add clangd to the next xtensa-clang update.

@danngreen
Copy link

Thanks for this, it works! I'm sure it would be much appreciated to have clangd included in xtensa-clang, as compile time is pretty long.

@tolgraven
Copy link

It really makes such a difference having a proper LSP and not just tags and vim-fu. Really wish I'd gotten around to figuring this out sooner - solution far too obvious in retrospect.

Is it just me or is [presumably this version of] clangd/clang rather buggy? I'm getting inexplicable errors like

Renderer::Renderer(const std::string& id, uint16_t keyFrameHz, uint16_t targetHz, const RenderStage& target):
  RenderStage(id + " renderer", target.fieldSize(),
              target.fieldCount(), target.buffers().size()),
  core::Task(id.c_str(), 4096, taskPrio,
             milliseconds{1000/targetHz},
             0),
  Sub<PatchIn>(this),   
// ^ clang expected_either - Expected '(' or '{'  // which is utter nonsense and since it's not
  targetFps(targetHz) {                           // an actual error there's no way to make it go away...
...

Bunch of stuff like that (plus obviously the many quirks in gcc/clang in what will cause a warning or error, or simply silently compile.
It also seems to generally post warnings as errors (despite my config saying otherwise) and misses a whole bunch of actual errors! So se makeprg=Ninja -C build still essential for me to know what's actually happening.

Anyone got a clue what might be going on?

@listout
Copy link

listout commented Jan 16, 2022

@tolgraven and @danngreen can you please share how you build clangd? I followed @tolgraven's suggestion and cloned the llvm-project repo and ran the cmake commands mentioned in that comment. But I'm getting FAILED: lib/libLTO.so.13 esp xtensa (to be more precise the error is collect2: fatal error: ld terminated with signal 9 [Killed]), which google suggests could be memory issue (I could be running out of memory). If you could just if you added any additional options. BTW I've 24 gigs of memory, don't know if that would be enough or not.

Thank you.

@danngreen
Copy link

@listout:
Hmm.. I did exactly as specified in @tolgraven's post:

git clone https://github.com/espressif/llvm-project
cd llvm-project
cmake -S llvm -B build -G Ninja -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra"
cmake --build build

Maybe check which branch you're building? I notice the default branch is not master

It built for me on macOS 12 (intel hardware), with 16GB of memory, I'd be surprised if that's not enough.

@tolgraven
Are you still getting weird errors like that? I'm not, but I have seen stuff like that on other projects. Setting the log level to debug, and then digging into the log will reveal the actual command being run by clangd. If you run that in the terminal, you should see the same errors. Then you can start deconstructing that command, seeing what flags are missing/added and go from there.
I recall once for me some header files weren't compiling as c++20 because compile_commands.json didn't specify an entry for .h files, and clangd "thought" they should get compiled as c99. Might be a clue that the error you showed us occurs right before a token containing a '<', which c99 would not like.... just a guess.

@listout
Copy link

listout commented Jan 16, 2022

@danngreen thank you. I'll try again.

@danngreen
Copy link

Just to be clear, I was able to build commit 7b5afb55f5c7959a2903978a25774c75172e8741
(on the xtensa_release_13.0.0 branch). Also I was able to build it on apple M1 hardware, as well as x86_64. Both of those are using clang 13 as the compiler.

@listout
Copy link

listout commented Jan 16, 2022

I was finally able to build it with the additional argument -DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=Xtensa. Thanks to gentoo linux for the idea.


@danngreen Nope the same issues, don't know whats happening but the linker is failing on me. Maybe I'll need to dig into it deeper.

My exact error message was: collect2: fatal error: ld terminated with signal 9 [Killed].

@gdanov
Copy link

gdanov commented Feb 19, 2022

So what's happening is clangd doesn't recognize xtensa-esp32-elf as a valid target/triple/arch, and falls back on empty (resulting in native), causing all kinds of trouble and weird errors, some of which can be circumvented by adding and removing args etc, but not all.

Getting everything working required cloning the xtensa llvm fork at https://github.com/espressif/llvm-project and building clangd from there:

cmake -S llvm -B build -G Ninja -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra"
cmake --build build

Then simply use the clangd in build/bin instead of system.

This is my .clangd, removing/adding some args is still necessary. And the automatic driver extraction sysroot makes no sense so I had to put that in as well.

CompileFlags:
  Add: [-mlong-calls, -isysroot=/Users/tol/.espressif/tools/xtensa-esp32-elf/esp-2021r2-8.4.0/xtensa-esp32-elf]
  Remove: [-fno-tree-switch-conversion, -mtext-section-literals, -mlongcalls, -fstrict-volatile-bitfields]

To easily make it work for everyone Espressif simply needs to build and distribute clangd in their existing xtensa-clang (that can be downloaded with idf_tools.py install xtensa-clang).

Thanks for this idea, did it on two different macs and now I've got lsp + emacs running great!

The alternative I used before that was ccls but the lsp flychecker disabled. The flycheck config was in .dir-local and used xtensa's g++ with command line copied from the generated .ccls file. Worked OK, but clangd is much better.

@listout
Copy link

listout commented Apr 27, 2022

> Thanks for the recommendation @tolgraven, I'll create a task to add clangd to the next xtensa-clang update.

@igrr Any plan on adding clangd to xtensa-clang? Just saw a new update but it didn't include clangd


Edit: Okay, I manually downloaded the latest release asset and clangd is included, so. How does one download the latest releases with idf_tools.py, it this releated to not using the master branch as I'm on stable branch (v4.4).

@igrr
Copy link
Member

igrr commented Apr 27, 2022

@listout could you please post the URL of the update you are referring to? I'll check.

@listout
Copy link

listout commented Apr 27, 2022

@igrr I was referring to this repo https://github.com/espressif/llvm-project.git. The latest release seems to be 12 days ago. My bad, I meant new release.


Looking at the download logs, it's most probably not pulling latest github release assets as it downloads xtensa-esp32-elf-llvm12_0_1-esp-12.0.1-20210914-linux-amd64.tar.xz instead of the new release, I was using the idf_tools.py install xtensa-clang to download.

Edit: Okay, I manually downloaded the latest release asset and clangd is indeed included. So, how does one download the latest releases with idf_tools.py? Is it releated to not using the master branch as I'm on stable branch (v4.4).

@igrr
Copy link
Member

igrr commented Apr 27, 2022

@listout I think we haven't updated the download link in tools.json, so the old version is still downloaded when you run idf_tools.py. We will update the link soon. In the meantime you can simply avoid running idf_tools.py install xtensa-clang, simply download the clang release you like, extract it somewhere and add the bin subdirectory to PATH. Be sure to do idf.py fullclean when switching to a new toolchain.

@listout
Copy link

listout commented Apr 27, 2022

@igrr Thanks. Did just that.

@andychess
Copy link

Has anyone else tried running the latest esp clangd under Mac OS13 Ventura? I get a security warning when I run binary either directly or via neovim.

Does anyone have a workaround?

Thanks in advance.

@igrr
Copy link
Member

igrr commented Feb 24, 2023

@andychess I've checked on one computer running macOS 13, but couldn't reproduce this issue.

What does the warning say, exactly? "clangd" can't be opened because Apple cannot check it for malicious software, or something else?

If that is the case, please check if you have quarantine attribute set on the clangd:

$ xattr -p com.apple.quarantine `which clangd`

if that prints a line which looks like 0000;abcdef01;Chrome;<GUID> then that means the binary is quarantined and will not run.

At that point you can remove the quarantine attribute using xattr -d com.apple.quarantine, if you trust the binary.

If instead you see No such xattr: com.apple.quarantine printed by xattr -p or if the warning message is different, then the issue might be somewhere else...

@andychess
Copy link

@igrr Thank you Ivan for answering so quickly.

The warning is indeed the one that you refer to (clangd" can't be opened because Apple cannot check it for malicious software).

I ran $ xattr... as you suggested above and received the reply: xattr: /usr/bin/clangd: No such xattr: com.apple.quarantine

I have read elsewhere that MacOS 13 implements a new version of Gatekeeper that is more difficult to circumvent.

Interesting that you did not encounter the problem. In my case, I downloaded the latest file from (llvm-esp-15.0.0-20221201-macos.tar.xz) to my Download directory, expanded the file and moved the esp-clang folder to my home directory. Running clangd from the command line and from the cmd: line in neovim both produce the warning.

It looks like I may have found the answer here: https://support.apple.com/de-de/guide/mac-help/mchleab3a043/mac (in German). The new way to run quarantined apps is to navigate to the required directory in Finder and hold CTRL and left click on the app. The select 'Open' from the menu. This produces a prompt that will allow the app to run. I had to repeat this step for several files in the folder and then clangd ran for me.

I hope this is useful to anyone who experiences the same issue and thanks again Ivan for pointing me in the right direction!

@igrr
Copy link
Member

igrr commented Feb 25, 2023

xattr: /usr/bin/clangd: No such xattr: com.apple.quarantine

This means that you are checking the OS-provided copy of clangd (in /usr/bin), not the ESP-specific one. When I wrote the command which clangd above, I have assumed that you had the esp-specific clangd already added to PATH, so which command would return the path to it. Good to know you have managed to solve the issue though, thanks for leaving the comment!

@andychess
Copy link

Yes, I only noticed that after I had posted my response. Nevertheless, your answer pointed me in the right direction and everything is working fine now. I'm really pleased with the result. Thank you :-).

@xjzi
Copy link

xjzi commented Jun 4, 2023

I was able to get clangd working without errors:

  1. idf_tools.py install esp-clang to download the ESP-architecture clang build including clangd.
  2. Configure your editor to use the new clangd binary at $HOME/.espressif/tools/esp-clang/15.0.0-23786128ae/esp-clang/bin/clangd
  3. Allow-list the gcc binary specified in compile_commands.json in the command: field by configuring your editor to run clangd with --query-driver=$HOME/.espressif/tools/xtensa-esp32s3-elf/esp-12.2.0_20230208/xtensa-esp32s3-elf/bin/xtensa-esp32s3-elf-gcc or whatever the binary is. Make sure it's an absolute path without "~".
  4. Remove compile flags that gcc has but clang doesn't using .clangd:
CompileFlags:
    Remove: [-fno-tree-switch-conversion, -fstrict-volatile-bitfields]

@joaotavora
Copy link

@xjzi See also clangd/clangd#537 (comment)

  1. I didn't need any special esp-clang installation.
  2. Just used normal clangd installed by my OS (Arch)
  3. You may be misinterpreting the meaning of --query-driver, which is understandable as it is a bit confusing. It is a glob for whitelisting what presumably is already a precise/correct compilation database in compile_commands.json. Check the linked issue closely.
  4. This was useful. In my case Iadded -mlongcalls to that list.

@igrr
Copy link
Member

igrr commented Jun 16, 2023

See the -mlong-calls argument there? I had to change it manually from -mlongcalls across the whole compile_commands.json

@joaotavora please note that these arguments aren't the same thing. -mlongcalls is an argument of Xtensa compiler, while -mlong-calls is an argument of an x86 compiler.

I am not sure the recommendation to use clang targeting x86-64 instead of Xtensa is helpful. If you do so, various macro expansions which depend on architecture (such as #if __XTENSA__) won't be done correctly. The other important problem with this approach is that type definitions (uint32_t, ptrdiff_t and others) also differ between compiler targets. I would recommend using the espressif builds of clang toolchain as they do support Xtensa architecture. The latest clang based toolchain releases also contain all the system headers, so you won't have unresolved header issues if you use it.

@joaotavora
Copy link

@joaotavora please note that these arguments aren't the same thing. -mlongcalls is an argument of Xtensa compiler, while -mlong-calls is an argument of an x86 compiler.

Right. Your pasting from the other post and a part which i myself noted was the wrong fix. Later on, if you read the full post, or just read above, you'll notice I just removed it from static analysis. As far as I can understand it only affects code generation and thus is useless for Clangd ally. So even the wrong -mlong-calls or some other completely different option would have no effect for LSP purposes.

The latest clang based toolchain releases also contain all the system headers, so you won't have unresolved header issues if you use it.

As far as I was able to tell from my experience, which so far has been positive --and admittedly limited -- Clangd actually calls the compiler with something like echo | foo-gcc -x c++ -E - to know what the system headers are. I wrote that from memory, so it might be off.

Anyway, that's what's its --query-driver option is for: instead of necessitating a full custom build of the whole toolchain for clang, ask any existing toolchain what makes it different in terms of defines and include paths. I think it's a neat and elegant idea. Time will tell how flawed it is, but so far so good.

I would recommend using the espressif builds of clang toolchain as they do support Xtensa architecture.

Building with xtensa GCC is fine, and so would building with xtensa Clang probably. But in this case i just need clangd, the language server, to understand the code and give me intellisense, etc.

Contrary to most other tools for this architecture (compiler, debugger, linker, disassembler) there shouldn't be a need for a special build of the code analysis toolkit.

@igrr
Copy link
Member

igrr commented Jul 12, 2023

@joaotavora Interesting, thanks for sharing this! Have you noticed any issues with resolution of types, e.g. due to different definitions of built-in types between the host and target architectures? For example, do you get correct resolution of something like static_assert(sizeof(void*) == 4); with the --query-driver approach? (assuming a 64-bit host and a 32-bit target.)

This is something we ran into with clang-tidy; even with all correct the system headers provided, the differences in evaluation of built-in types prevented clang-tidy from correctly analyzing Xtensa code. On the other hand, clang-tidy built from a version of Clang with Xtensa support had no issue with that.

@joaotavora
Copy link

joaotavora commented Jul 12, 2023

@igrr So far no problem -- though I've only been dabling around in relatively small ESP-IDF projects. But so far so good, completion and analysis match exactly (though with different compiler messages), what the gcc based compilation also reports.

To get around the types defined with __XTENSA and to teach clangd about the 32bit little endian nature of the system, this is the full contents my .clangd which lives at the root of my project (besides the usual .git folder):

CompileFlags:
    Add: [ -D__XTENSA__, --target=mipsel ]
    Remove: [-fno-tree-switch-conversion, -fstrict-volatile-bitfields, -mlongcalls]

Yes, I know mipsel isn't really the target, but it doesn't seem to matter (EDIT: and yes, your static_assert passes cleanly).

For reference here's a fragment of my compile_commands.json which I think is generated automatically by ESP-IDF's Cmake toolchain. Other then eliding some local filesystem details, I haven't touched it.

{
  "directory": "<ELIDED>/build/esp-idf/xtensa",
  "command": "<HOME>/.espressif/tools/xtensa-esp32-elf/esp-2022r1-11.2.0/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc -DSOC_MMU_PAGE_SIZE=CONFIG_MMU_PAGE_SIZE -I<ELIDED>/build/config -I/opt/esp-idf/components/xtensa/include -I/opt/esp-idf/components/xtensa/esp32/include -I/opt/esp-idf/components/newlib/platform_include -I/opt/esp-idf/components/freertos/FreeRTOS-Kernel/include -I/opt/esp-idf/components/freertos/esp_additions/include/freertos -I/opt/esp-idf/components/freertos/FreeRTOS-Kernel/portable/xtensa/include -I/opt/esp-idf/components/freertos/esp_additions/include -I/opt/esp-idf/components/esp_hw_support/include -I/opt/esp-idf/components/esp_hw_support/include/soc -I/opt/esp-idf/components/esp_hw_support/include/soc/esp32 -I/opt/esp-idf/components/esp_hw_support/port/esp32/. -I/opt/esp-idf/components/esp_hw_support/port/esp32/private_include -I/opt/esp-idf/components/heap/include -I/opt/esp-idf/components/log/include -I/opt/esp-idf/components/soc/include -I/opt/esp-idf/components/soc/esp32/. -I/opt/esp-idf/components/soc/esp32/include -I/opt/esp-idf/components/hal/esp32/include -I/opt/esp-idf/components/hal/include -I/opt/esp-idf/components/hal/platform_port/include -I/opt/esp-idf/components/esp_rom/include -I/opt/esp-idf/components/esp_rom/include/esp32 -I/opt/esp-idf/components/esp_rom/esp32 -I/opt/esp-idf/components/esp_common/include -I/opt/esp-idf/components/esp_system/include -I/opt/esp-idf/components/esp_system/port/soc -I/opt/esp-idf/components/esp_system/port/include/private -mlongcalls -Wno-frame-address  -ffunction-sections -fdata-sections -Wall -Werror=all -Wno-error=unused-function -Wno-error=unused-variable -Wno-error=deprecated-declarations -Wextra -Wno-unused-parameter -Wno-sign-compare -Wno-enum-conversion -gdwarf-4 -ggdb -Og -fmacro-prefix-map=<ELIDED>=. -fmacro-prefix-map=/opt/esp-idf=/IDF -fstrict-volatile-bitfields -Wno-error=unused-but-set-variable -fno-jump-tables -fno-tree-switch-conversion -DconfigENABLE_FREERTOS_DEBUG_OCDAWARE=1 -std=gnu17 -Wno-old-style-declaration -D_GNU_SOURCE -DIDF_VER=\\\"HEAD-HASH-NOTFOUND\\\" -DESP_PLATFORM -D_POSIX_READER_WRITER_LOCKS -o CMakeFiles/__idf_xtensa.dir/eri.c.obj -c /opt/esp-idf/components/xtensa/eri.c",
  "file": "/opt/esp-idf/components/xtensa/eri.c"
},

EDIT: Sorry I forgot to remind you that --query-driver is still needed when starting the /usr/bin/clangd program. The somewhat lazy invocation on my end is clangd --query-driver=**, which tells clangd that it's OK to invoke any command in compile_commands.json to get information about sysroots and so on. If you're more paranoid about security, you could replace the ** which a glob that more closely matches the path to the xtensa-esp32-elf-gcc and xtensa-esp32-elf-g++ programs.

This is something we ran into with clang-tidy; even with all correct the system headers provided, the differences in evaluation of built-in types prevented clang-tidy from correctly analyzing Xtensa code. On the other hand, clang-tidy built from a version of Clang with Xtensa support had no issue with that.

The clangd language server I have installed is the stock version available for Archlinux. version 16 is already available but I haven't yet felt the need to upgrade.

clangd version 15.0.7
Features: linux
Platform: x86_64-pc-linux-gnu

clangd leverages clang-tidy automatically as long as .clang-tidy is in your project root. I don't see any problems, likely because clangd is feeding it a correct AST and types (because of the .clangd file).

I see the main advantage in this approach that apparently you can use the latest and greatest clangd (with latest LSP features and bugfixes) without needing to compile a full cross-architecture LLVM/clangd yourself.

@andreichalapco
Copy link

I also battled this week with the language server and here are my tips ...

  1. Be sure to use a new esp-idf version (remove .espressif folder and install again)
  2. idf_tools.py install esp-clang (install esp-idf)
  3. When developing with c++ be sure to put --query-driver= $HOME/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20240305/xtensa-esp-elf/bin/xtensa-esp32-elf-g++ as extra flag for clangd. (this is done for esp32 ... for other architectures the setting could vary ... explore the folder for all binaries)

.clangd conf:

CompileFlags:
  Remove: [-fstrict-volatile-bitfields, -fno-tree-switch-conversion, -fno-shrink-wrap]

Still using these settings I get some errors when in the header file esp_log.h : inttypes.h not found ...
Probably some isysroot option in .clangd could help, but I haven't found the solution.

I think it is also beneficial to the community to make a documentation about the language server use. Using a mac myself I found that using the clangd compiled with xtensa makes for a much better developer experience. Clion (using clangd as backend) out of the box also does not manage very well espressif projects so the language server setting here is very important.

@KaeLL
Copy link
Contributor

KaeLL commented Jul 12, 2024

Still using these settings I get some errors when in the header file esp_log.h : inttypes.h not found

Same problem, but I solved that by specifying gcc instead of g++ to the query-driver option. Next person who reads this working on a mixed C and C++ project should test providing both options to see if it works.

Ironically enough, when compiling my project with clang, clangd can't find some system headers, like stdbool.h or time.h, among others.

@j0of
Copy link

j0of commented Jul 14, 2024

Hi, I just got started with the ESP32 today and followed a quick tutorial by Low Level Learning to get started with esp-idf. I'm using Neovim with the clangd LSP as my dev environment, but I'm getting a few errors in my source file even though the project compiles normally. I think my issue is related, so I'm going to comment here rather than opening a new issue.
My CMakeLists.txt is this:

cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp32)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

My source file is just the default, but also has some errors from the LSP:

#include <stdio.h>
Diagnostics:
1. Unknown argument '-mlongcalls'; did you mean '-mlong-calls'? [drv_unknown_argument_with_suggestion]
2. Unknown argument: '-fno-shrink-wrap' [drv_unknown_argument]
3. Unknown argument: '-fstrict-volatile-bitfields' [drv_unknown_argument]
4. Unknown argument: '-fno-tree-switch-conversion' [drv_unknown_argument]
5. Included header stdio.h is not used directly (fix available) [unused-includes]


void app_main(void)
{

}

Can someone help me out? Apologies if there is already a solution here that I just didn't see... this thread is quite long 😅

@andychess
Copy link

I'm running Neovim / Espidf as well. Have you got a .clangd file in your root directory?

Mine looks like this:

CompileFlags:
Remove: [-fno-tree-switch-conversion, -fno-shrink-wrap, -mtext-section-literals, -mlong-calls, -fstrict-volatile-bitfields, -march=rv32imac_zicsr_zifencei]

The last one is necessary for the RISCV chips.

@j0of
Copy link

j0of commented Jul 15, 2024

@andychess This is great, thanks! Have you also encountered errors with including FreeRTOS before? I'm getting In included file: 'machine/endian.h' file not found on the line #include <freertos/FreeRTOS.h>. I did some digging and found this, but it didn't work for me.

@andychess
Copy link

andychess commented Jul 16, 2024

@j0of Glad it helped :-). Problems with the headers seem to be very common with clang. You probably need a section like this in the lsp config section of nvim. Mine looks like this at the moment.

1 require("mason").setup()
1 require("mason-lspconfig").setup()
2
3 -- vim.lsp.set_log_level 'error'
4 -- vim.lsp.set_log_level 'debug'
5 vim.lsp.set_log_level 'off'
6
7 require('vim.lsp.log').set_format_func(vim.inspect)
8
9 -- Setup language servers.
10

31 require('lspconfig').clangd.setup {
32 virtual_text = false,
33 cmd = { "/Users/andy/.espressif/tools/esp-clang/16.0.1-fe4f10a809/esp-clang/bin/clangd",
34 "--background-index",
35 },
36 filetype = { "c", "cpp" },
37 }
38 --
39 require('lspconfig').pyright.setup{}

I've had to use a lot of trial and error with this as nvim seems to be very sensitive to syntax issues. In theory, it should be possible to use wildcards, but for some reason I often have to put in the full path. Perhaps someone can supply a bit more background to this?

The link you supplied was very interesting btw. I didn't know about the line in export.sh.

@j0of
Copy link

j0of commented Jul 16, 2024

After a few more changes to the config, I finally got it to work, and it only took 2 days! 😂 Thanks again for the helpful replies @andychess . I'm gonna leave my solution here, partially to help any others who need help, but mainly to save future me from a massive headache if I somehow find myself in the same situation in the future.

Prerequisites

# Install & export idf stuffs
apt update && apt install -y clang-tidy
pip install -U pyclang # dunno if this is necessary; i just took this from another person's solution :v) )
idf_tools.py install esp-clang
. ${IDF_PATH}/export.sh

.clangd config

CompileFlags:
  Remove: [-fno-tree-switch-conversion, -fno-shrink-wrap, -mtext-section-literals, -mlongcalls, -fstrict-volatile-bitfields, -march=rv32imac_zicsr_zifencei]

Neovim Lspconfig

local lspconfig = require("lspconfig")
local capabilities = require('cmp_nvim_lsp').default_capabilities()
lspconfig.clangd.setup({
  handlers = handlers,
  capabilities = capabilities;
  cmd = { "/home/joof/.espressif/tools/esp-clang/esp-17.0.1_20240419/esp-clang/bin/clangd", "--background-index", "--query-driver=**", },
  root_dir = function()
      -- leave empty to stop nvim from cd'ing into ~/ due to global .clangd file
  end
})

@SotchNam
Copy link

SotchNam commented Aug 6, 2024

@j0of Thanks for the config, i improved upon it to allow using a default config whenever we're not in an esp-idf enviroment:

-- Check if the ESP-IDF environment variable is set
local esp_idf_path = os.getenv("IDF_PATH")
if esp_idf_path then
  -- for esp-idf
  require'lspconfig'.clangd.setup{
    -- handlers = handlers,
    capabilities = capabilities;
    cmd = { "/home/sotch/.espressif/tools/esp-clang/16.0.1-fe4f10a809/esp-clang/bin/clangd", "--background-index", "--query-driver=**", },
    root_dir = function()
        -- leave empty to stop nvim from cd'ing into ~/ due to global .clangd file
    end
  }

else
  -- clangd config
  require'lspconfig'.clangd.setup{
    -- cmd = { 'clangd', "--background-index", "--clang-tidy"},
    handlers = {
    ["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
      disable = { "cpp copyright" }
    })}
  }
end

@kashperanto
Copy link

kashperanto commented Aug 18, 2024

@SotchNam @j0of Thank you for the example configs guys, I finally got my setup working 😃

Has anyone else ran into the problem where clangd is using the wrong stdint types (even though it finds all the right headers)?

I checked the output from each compiler on my system, and sure enough clang doesn't agree about these defines. Only the 32-bit int types seem to disagree:

~/.espressif/tools
✦ ❯ ./xtensa-esp-elf/esp-13.2.0_20240530/xtensa-esp-elf/bin/xtensa-esp-elf-g++ -dM -xc++ /dev/null -c -v -E 2> /dev/null | rg "U?INT\d+_TYPE" | sort
#define __INT16_TYPE__ short int
#define __INT32_TYPE__ long int
#define __INT64_TYPE__ long long int
#define __INT8_TYPE__ signed char
#define __UINT16_TYPE__ short unsigned int
#define __UINT32_TYPE__ long unsigned int
#define __UINT64_TYPE__ long long unsigned int
#define __UINT8_TYPE__ unsigned char

~/.espressif/tools
✦ ❯ ./esp-clang/16.0.1-fe4f10a809/esp-clang/bin/clang++ --target=xtensa-esp32s3-elf -dM -xc++ /dev/null -c -v -E 2> /dev/null | rg "U?INT\d+_TYPE" | sort
#define __INT16_TYPE__ short
#define __INT32_TYPE__ int
#define __INT64_TYPE__ long long int
#define __INT8_TYPE__ signed char
#define __UINT16_TYPE__ unsigned short
#define __UINT32_TYPE__ unsigned int
#define __UINT64_TYPE__ long long unsigned int
#define __UINT8_TYPE__ unsigned char

I was able to override the 32-bit defines in my .clangd and now have no warnings (at least for now, there are probably other inconsistencies in less frequent types).

Oh, and I figured out a way to let you use a wildcard to find clangd (assuming you have only one installed). This way you don't need to modify your config whenever you update clangd:

if esp_idf_path then
    local clangd = vim.fn.expand('/home/me/.espressif/tools/esp-clang/*/esp-clang/bin/clangd')
    lspconfig.clangd.setup{
        -- handlers = handlers,
        capabilities = capabilities;
        cmd = { clangd, '--background-index', '--query-driver=**', },
        ...

And my .clangd for reference:

CompileFlags:
  CompilationDatabase: build
  QueryDriver: /home/me/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20240530/xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc
  Add:
    - --target=xtensa-esp32s3-elf
    - -D__INT32_TYPE__=long int
    - -D__UINT32_TYPE__=long unsigned int
  Remove:
    - -fno-tree-switch-conversion
    - -fno-shrink-wrap
    - -mtext-section-literals
    - -mlongcalls
    - -fstrict-volatile-bitfields
    - -mdisable-hardware-atomics
    - -march=rv32imac_zicsr_zifencei

It appears to handle the quoting properly for the defines (not sure if it will also work for the array-like syntax). I don't think the target argument helps, but it doesn't hurt (as far as I know). I also redundantly specify the query driver in .clangd, since there it makes sense to hard-code it to your project. I guess it's a security issue to use the ** in the vim config? I have both for now, and it is working fine in a mixed C/C++ project.

I'm not sure if the uint32_t thing is a bug, but if so does anyone know the appropriate place to report it?

@igrr
Copy link
Member

igrr commented Aug 18, 2024

I'm not sure if the uint32_t thing is a bug, but if so does anyone know the appropriate place to report it?

I think it's not a bug, just the fact that these two combinations of the compiler and the target use different types for uint32_t.

In GCC, several bare-metal targets define uint32_t as unsigned long: Xtensa, RISC-V, ARM. This is the case not only in Espressif builds of the toolchain but in the upstream as well.

In Clang, the same targets define uint32_t as unsigned.

I was able to override the 32-bit defines in my .clangd and now have no warnings

Could you please give an example of a warning you are getting? Is it from IDF code or from the code in your application?

@kashperanto
Copy link

kashperanto commented Aug 20, 2024

@igrr

I think it's not a bug, just the fact that these two combinations of the compiler and the target use different types for uint32_t.

Yeah, I think "bug" is a too harsh a term, but maybe "undesired discrepancy" is what I was thinking. I did find one discussion somewhere (can't find the link at the moment) about whether it is a bug or missing feature that the query-driver option doesn't also include these compiler intrinsic/builtin definitions. It was my understanding that the compilers can define things this way, but they should act the same way as though they were defined in a header. With that understanding it would make sense to include these definitions when querying the other compiler.

In GCC, several bare-metal targets define uint32_t as unsigned long: Xtensa, RISC-V, ARM. This is the case not only in Espressif builds of the toolchain but in the upstream as well.

Interesting, I guess I somehow managed to unwittingly get myself into a uint32_t == unsigned int bubble at my last job. Their arm compilers were updated to use clang years ago, and much of my development has been on their Tiva micros. I also have not done much printf() debugging using fixed-width types on embedded systems in general until working on some ESP32 projects at my new job, and on PCs gcc uses uint32_t == unsigned int.

And the icing on the cake is that the ESP experience I did have from years ago was before you guys changed the definition to be consistent with other platforms. I happened to stumble upon your informative comments in this github issue. Now it all makes sense why so much code was using the wrong printf format specifiers.

In Clang, the same targets define uint32_t as unsigned.

This is an interesting discrepancy. Do you happen to know why they differ in this area? I'm curious. I could see it making sense to target consistency with larger architectures.

I was able to override the 32-bit defines in my .clangd and now have no warnings

Could you please give an example of a warning you are getting? Is it from IDF code or from the code in your application?

It is from my code in all ESP_LOGX() calls using 32-bit types. The usual Format specifies type 'unsigned int' but the argument has type 'uint32_t' (aka 'unsigned long') (fix available)

The project-specific .clangd CompileFlags solution works perfectly fine for this. I don't intend to migrate to clang for any existing projects. I will start using PRId32 and other standard macros for printf formatting, though, as ugly as they are.

@stevenferrer
Copy link

Hi all, I'm targeting esp32c3 and followed most of the configs above, but somehow I'm still getting an error on freertos include that says In included file: 'sys/reent.h' file not found.

image

I went into sys/reent.h and the error is shown on the screenshot. Anything I might be missing?

image

I'm using the hello_world template and below for my configs.

.vscode/settings

{
  "clangd.arguments": [
    "--query-driver=/home/me/.espressif/tools/riscv32-esp-elf/esp-13.2.0_20240530/riscv32-esp-elf/bin/riscv32-esp-elf-*",
    "--background-index"
  ]
}

.clangd

CompileFlags:
  Remove:
    [
      -fno-tree-switch-conversion,
      -mtext-section-literals,
      -fstrict-volatile-bitfields,
      -mlong-calls,
      -fno-shrink-wrap,
      -march=rv32imc_zicsr_zifencei,
    ]

cmakelists.txt

cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hello_world)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

@andychess
Copy link

Do you have a compile_commands.json in your root directory?

@stevenferrer
Copy link

Do you have a compile_commands.json in your root directory?

Yeah, the compile commands are automatically generated inside the build directory. I believe clangd knows how to find it inside the build directory.

@b1ackviking
Copy link
Contributor

I believe clangd knows how to find it inside the build directory.

No, it is not. You need to copy/symlink compile_commands.json into the root directory of your project or set a path to it in .clangd file (at the root of your project) as follows:

CompileFlags:
  # adjust for your compile_commands.json location
  CompilationDatabase: build/Debug

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Opened Issue is new Type: Feature Request Feature request for IDF
Projects
None yet
Development

No branches or pull requests