niosus / EasyClangComplete

:boom: Robust C/C++ code completion for Sublime Text 3/4
https://niosus.github.io/EasyClangComplete/
MIT License
575 stars 78 forks source link

Include paths for generated headers with CMake custom command #737

Closed Legorock closed 3 years ago

Legorock commented 3 years ago

System info:

What happens:

My project uses an external framework (Apache Thrift) which generates some boilerplate C++ code and code generation is part of the build process. On the CMake side, it is handled with custom_command and custom_target which is added as a dependency to the executable target. Then I added the build folder where the generated code resides as include-directory. This manages to compile and I can observe the correct compiler flags (include directories and etc.). My only problem is that ECC cannot detect the include path of the generated code. A minimal example is below:

CMakeLists.txt

cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(cmake_custom_cmd VERSION 0.1 LANGUAGES CXX)
add_custom_command(
    OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp/func.h"
    COMMAND mkdir -p "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp"
    COMMAND echo "void func(void);" > "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp/func.h"
    DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/func.cpp"
    VERBATIM
)
add_custom_target(gen_header
    COMMAND ${CMAKE_COMMAND} -E echo "Generating 'func.h' header file..."
    DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/gen-cpp/func.h"
    VERBATIM
)
add_executable(test_exe main.cpp func.cpp)
add_dependencies(test_exe gen_header)
target_include_directories(test_exe PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
target_compile_options(test_exe PRIVATE -Wall -Werror)
target_compile_features(test_exe PRIVATE cxx_std_17)

main.cpp

#include "gen-cpp/func.h"
int main(int argc, char* argv[])
{
    func();
    return 0;
}

func.cpp

#include "gen-cpp/func.h"
#include <iostream>
void func()
{
    std::cout << "We did it! Hellow World!" << std::endl;
}

Then after creating a out-of-source build directory I got these ECC errors: main.cpp: Line 1 file not found, Line 4 use of undeclared identified 'func' func.cpp: Line 1 file not found, Line 5 invalid operands to binary expression ('std::ostream' (a.k.a 'int') and 'const char [25]')

For the sake of completeness, I followed these commands: mkdir build cmake -S . -B build cmake --build build ./build/test_exe It compiles, links and runs without problems.

I can force the ECC to include my build directory with sublime settings:

{
    "folders":
    [
        {
            "path": "."
        }
    ],
    "settings": {
        "ecc_common_flags": [
        // some example includes
        "-I/usr/include",
        "-I$project_base_path/src",
        // this is needed to include the correct headers for clang
        "-I/usr/lib/clang/$clang_version/include",
        // For simple projects, you can add a folder where your current file is
        "-I$file_path",

        "-I$project_base_path/build", // Couldn't get it from cmake!
        ],
        "easy_clang_complete_verbose": true
    }
}

Is there anything I missed in CMake or in the ECC setup to allow this to include a path without sublime settings?

niosus commented 3 years ago

@Legorock that is kinda weird. I do run cmake in a temporary folder but it should still export all the build artifacts and those should still be visible in the compilation_database.json that is generated during the cmake build. Can you check if the paths you are talking about are present in the compilation database?

Legorock commented 3 years ago

In the minimal example, I don't even see the compilation_database.json in the build directory. In my own project, this file exists and there are a lot of directories starting with the ${CMAKE_CURRENT_BINARY_DIR} path but not the directory that cmake custom command generates.

niosus commented 3 years ago

Yeah, you would have to look in the temporary folder which is in different places depending on your system. If you're on Linux, it's /tmp

Legorock commented 3 years ago

The contents of the file are below, I just replaced my home directory path with $HOME.

[ { "directory": "/tmp/EasyClangComplete/cmake_builds/080fe5ebf38958224a540f56235f6c18", "command": "/usr/bin/c++ -I/tmp/EasyClangComplete/cmake_builds/080fe5ebf38958224a540f56235f6c18 -Wall -Werror -std=gnu++17 -o CMakeFiles/test_exe.dir/main.cpp.o -c $HOME/Projects/cpp/cmake_custom_cmd/main.cpp", "file": "$HOME/Projects/cpp/cmake_custom_cmd/main.cpp" }, { "directory": "/tmp/EasyClangComplete/cmake_builds/080fe5ebf38958224a540f56235f6c18", "command": "/usr/bin/c++ -I/tmp/EasyClangComplete/cmake_builds/080fe5ebf38958224a540f56235f6c18 -Wall -Werror -std=gnu++17 -o CMakeFiles/test_exe.dir/func.cpp.o -c $HOME/Projects/cpp/cmake_custom_cmd/func.cpp", "file": "$HOME/Projects/cpp/cmake_custom_cmd/func.cpp" } ]

I can see that ${CMAKE_CURRENT_BINARY_DIR} is in the compiler path but it is for the build directory in the /tmp. And I am guessing you are just generating this and not running the build hence my C++ files are not present in this temporary build folder. Does this make any sense or I am guessing wrong?

niosus commented 3 years ago

Yep, I don't think I'm running the build either. What I do is I let libclang execute commands from the compilation database as they are + some additional include paths and flags. But I do run cmake on your project and let it generate all the artifacts in the temp folder. Do I understand correctly that the files will only be generated by the built itself? I guess I could allow users to specify the build folder, but I believe that it is not too much of a hassle to just add another include folder like you do. As this build is a bit away from completely standard, I expect people who have custom commands in their build to also be able to provide this additional include flag just like you already did. Does this make sense to you? Maybe I should mention this in documentation though somewhere...

Legorock commented 3 years ago

Yes, you are right. In my case, the files are generated at built times (due to add_custom_command and add_custom_target) and I agree that in this case probably it is best to specify the directory in *.sublime-project settings. I managed to recreate a similar scenario with execute_process and, in deed, ECC is able to find the headers because the file generation is in configuration time and they are available in /tmp`. I think you are right, but it would be a nice addition to the documentation about build time artifacts that may not be discovered by ECC. Thank you for your help.