atilaneves / cmake-ide

Use Emacs as a C/C++ IDE
BSD 3-Clause "New" or "Revised" License
716 stars 92 forks source link

Use cmake's CodeBlocks generator file instead of compile_commands.json #71

Open sezaru opened 8 years ago

sezaru commented 8 years ago

Hello there,

cmake-ide uses the compile_commands.json file to grab the project files compiler flags, but since this file doesn't show any information about the compiler flags of header files, we need to try finding it with some hacky way (like find a source file with the same name and using it's compiler flags), this works in simple projects, but in anything more complex, problems will arise.

I've made some simple changes to the cmake's project source code so it will insert header information in the compile_commands.json file too, (you obviously need to list the headers files in the CMakeLists.txt file, for example add_executable(project main.cpp test.cpp test.hpp)), but it doesn't seems like it will be accepted mainstream (you can see my bug report here bug report link maybe you can help me convince the cmake devs that there is people interested in this feature.

Regardless of that, one already built-in solution in cmake is to use the CodeBlocks generator file instead of the compile_commands.json since that one will show all the information about every file you listed in your CMakeLists.txt file, including headers.

The problem is that this format is not as straight forward as the compile_command.json since it needs a little bit more logic to grab all the compiler flags information. But since this will give all the information we need without relying in hacks and always work despite the project size or complexity, maybe is a great alternative to make emacs works better as a real IDE.

atilaneves commented 8 years ago

The headers issue is a bit more complicated than that: a header can be compiled differently in different compilation units and in my last project at work that happened. I wrote a bit about it in my blog. I'm not even sure what the ideal solution would be given the C and C++ compilation model.

The header support in cmake-ide right now was fine for me in a medium-sized mixed C and C++ codebase. What problems do you have?

sezaru commented 8 years ago

I don't think I got what you mean, the only way I can see this happening is if the developer explicitly listed the same header in more than one output binary (add_executable or add_library), is that what you meant?

I've done a simple example of this, the CMakeLists.txt would have something like this:

file (GLOB_RECURSE sources src/*.cpp)
file (GLOB_RECURSE sources2 src2/*.cpp)
file (GLOB_RECURSE headers include/*.hpp)

add_executable (TEST1 ${sources} ${headers})
target_include_directories(TEST1 PUBLIC ${CMAKE_SOURCE_DIR}/include ${OpenCV_INCLUDE_DIRS})

add_executable (TEST2 ${sources2} ${headers})
target_include_directories(TEST2 PUBLIC ${CMAKE_SOURCE_DIR}/include  ${OPENSSL_INCLUDE_DIR})

Running my modified version of cmake will output this compile_commands.json:

[
{
  "directory": "/home/ebarreto/Downloads/cmake-3.6.1/test/build",
  "command": "/usr/bin/c++    -I/home/ebarreto/Downloads/cmake-3.6.1/test/include -I/usr/include/openssl    -o CMakeFiles/TEST2.dir/test.hpp.gch -x c++-header -c /home/ebarreto/Downloads/cmake-3.6.1/test/include/test.hpp",
  "file": "/home/ebarreto/Downloads/cmake-3.6.1/test/include/test.hpp"
},
{
  "directory": "/home/ebarreto/Downloads/cmake-3.6.1/test/build",
  "command": "/usr/bin/c++    -I/home/ebarreto/Downloads/cmake-3.6.1/test/include -I/usr/include/openssl    -o CMakeFiles/TEST2.dir/src2/test2.cpp.o -c /home/ebarreto/Downloads/cmake-3.6.1/test/src2/test2.cpp",
  "file": "/home/ebarreto/Downloads/cmake-3.6.1/test/src2/test2.cpp"
},
{
  "directory": "/home/ebarreto/Downloads/cmake-3.6.1/test/build",
  "command": "/usr/bin/c++    -I/home/ebarreto/Downloads/cmake-3.6.1/test/include -I/usr/include/openssl    -o CMakeFiles/TEST2.dir/src2/main.cpp.o -c /home/ebarreto/Downloads/cmake-3.6.1/test/src2/main.cpp",
  "file": "/home/ebarreto/Downloads/cmake-3.6.1/test/src2/main.cpp"
},
{
  "directory": "/home/ebarreto/Downloads/cmake-3.6.1/test/build",
  "command": "/usr/bin/c++    -I/home/ebarreto/Downloads/cmake-3.6.1/test/include -I/usr/include/opencv    -o CMakeFiles/TEST1.dir/test.hpp.gch -x c++-header -c /home/ebarreto/Downloads/cmake-3.6.1/test/include/test.hpp",
  "file": "/home/ebarreto/Downloads/cmake-3.6.1/test/include/test.hpp"
},
{
  "directory": "/home/ebarreto/Downloads/cmake-3.6.1/test/build",
  "command": "/usr/bin/c++    -I/home/ebarreto/Downloads/cmake-3.6.1/test/include -I/usr/include/opencv    -o CMakeFiles/TEST1.dir/src/test1.cpp.o -c /home/ebarreto/Downloads/cmake-3.6.1/test/src/test1.cpp",
  "file": "/home/ebarreto/Downloads/cmake-3.6.1/test/src/test1.cpp"
},
{
  "directory": "/home/ebarreto/Downloads/cmake-3.6.1/test/build",
  "command": "/usr/bin/c++    -I/home/ebarreto/Downloads/cmake-3.6.1/test/include -I/usr/include/opencv    -o CMakeFiles/TEST1.dir/src/main.cpp.o -c /home/ebarreto/Downloads/cmake-3.6.1/test/src/main.cpp",
  "file": "/home/ebarreto/Downloads/cmake-3.6.1/test/src/main.cpp"
}
]

The important parts are the duplication of test.hpp, in one case it has a include path to openssl, in the other it is one to opencv, since this header should work with both the cases, the logical thing to do here is merge both parts into one and remove any flag that are specific to one of the parts, so -I/home/ebarreto/Downloads/cmake-3.6.1/test/include will remain, but -I/usr/include/opencv and -I/usr/include/openssl will be removed, resulting exactly in the right compiler flags for the header file test.hpp.

Is that the case you mentioned? If not, can you elaborate more about it so I can reproduce it here?

Thanks

atilaneves commented 8 years ago

No, I mean that foo.cpp and bar.cpp could both include hdr.hpp and that each one of those source files can be compiled with different flags, with hdr.hpp not appearing anywhere in the CMakeLists.txt explicitly. Which flags should be used when a user visits hdr.hpp?

sezaru commented 8 years ago

You can only workaround this if you apply a post-processing, wrapping the make command or using the compile_commands.json to parse all cpp files searching for includes.

I've made the second option using libclang, it parses all the cpp files from the project searching for #include directives, it will then apply the logic I talked in the above post to find out the correct flags for the header file. It works like a charm, but I don't like it since it is another step needed instead of simply using cmake.

Note that my proposal is about explicitly adding the headers files to the project CMakeLists.txt's files, if the user does that, the problem you talked about simply disappear.

My proposal is very similar to what Qt-Creator IDE does, when opening a cmake project with Qt-Creator, Qt-Creator will generate a cbp file from cmake and use it to find what are the project files and compiler flags, if you explicitly add your headers to the CMakeLists.txt file, Qt-Creator will list it in the project panel and will find the correct flags for it.

I know that not everyone adds it's header files to the CMakeLists.txt file, but in this case, you can just fallback to the current cmake-ide mode to find the flags or even use the libclang solution I talked about.

atilaneves commented 8 years ago

cmake-ide is already trying to find source files that include the header in question to apply flags. The only problem is it can be slow in large projects. But the functionality is already there.

sezaru commented 8 years ago

As far as I know, what cmake-ide does is simply try to compile the header file with one of the provided compiler flags from the cpp files from compile_commands.json. That doesn't work with the example I gave before..

For example cmake-ide gave to test.hpp these flags: "-I/home/ebarreto/Downloads/cmake-3.6.1/test/include" "-I/usr/include/openssl" "-o" "CMakeFiles/TEST2.dir/src2/test2.cpp.o" But the correct should be only: "-I/home/ebarreto/Downloads/cmake-3.6.1/test/include"

These differences would make flycheck not recognize as an error every time I include or use something for openssl for example.

atilaneves commented 8 years ago

It tries a bunch of things, some of which are configurable and off by default due to speed reasons