vector-of-bool / pitchfork

Pitchfork is a Set of C++ Project Conventions
MIT License
1.12k stars 56 forks source link

How to set include directories in CMake #18

Open Flamefire opened 5 years ago

Flamefire commented 5 years ago

Assume a layout like this:

- src 
  - namespace
    - foo.cpp
    - foo2.cpp
- include
  - namespace
    - foo.hpp
- test 
  - namespace
    - foo.cpp

Where shall CMakeLists.txt files go? I would assume 1 in root and 1 in test.

This would mean the library itself is added in the top-level cmake with: add_library(foo src/namespace/foo.cpp src/namespace/foo2.cpp)

The redundancy is kinda annoying especially with deeply nested namespaces. So one could put 1 into the lowest, common namespace which compacts this to add_library(foo foo.cpp foo2.cpp)

Much better, but what now with target_include_directories? Put it again into top-level CMakeLists? Use ${PROJECT_SOURCE_DIR}/include? The former is disallowed by CMake (IIRC) and the latter fails for submodules (src/include in a subfolder for libs).

One could use ../../include but that relative path bugs me as a red flag.

Any suggestions?

PS: Why is no mention of CMake in the spec document?

vector-of-bool commented 5 years ago

Hello! Sorry for the delay. I've been absent for the holidays, but I'm going to be catching up now.

Specific build system provisions are TBD. As for your example, with separate header placement, your main CMakeLists.txt should be a sibling of include/ and src/, with calls like add_library(foo src/namespace/foo.cpp src/namespace/foo2.cpp).

In new CMake versions, you can use file(GLOB CONFIGURE_DEPENDS) to get your source lists accurately, and I recommend it. In new CMake you can also use target_include_directories() on targets in subdirectories as well.

Here is what it may look like:

file(GLOB_RECURSE sources CONFIGURE_DEPENDS src/*.cpp src/*.hpp include/*.hpp)
add_library(foo ${sources})
target_include_directories(foo PUBLIC include PRIVATE src)

I'm also preparing a CMake module to help automate project setup, which should be available in the future. (It's being used within this repository, but needs a bit of cleanup and fixes).

azias commented 5 years ago

In the cmake documentation, there is a note about that:

Note: We do not recommend using GLOB to collect a list of source files from your source tree. If no CMakeLists.txt file changes when a source is added or removed then the generated build system cannot know when to ask CMake to regenerate. The CONFIGURE_DEPENDS flag may not work reliably on all generators, or if a new generator is added in the future that cannot support it, projects using it will be stuck. Even if CONFIGURE_DEPENDS works reliably, there is still a cost to perform the check on every rebuild.

And I confirm that it did not work with Visual Studio, if I add a new source below src/, it is not seen when I rebuild the project (but if I modify the CMakeLists.txt file itself). That is why, with cmake, we usually write explicitly the list of files in cmake files (for example in some files.cmake that will be included but the main CMakeLists.txt)

Mike-Devel commented 5 years ago

My observation on that topic is is this:

1) With or without glob, you have to make sure that cmake gets rerun, if you add or remove a source file.

2) If you don't use globbing, you have to change the cmake file. If you use globbing, you can either change the cmake file or just re-run cmake.

Not that I particularly prefer one way over the other, but I don't see, why the non-glob approach is better. Just as an example: If you use the native cmake support in visual studio (open folder vs first creating the project files), you can trigger the cmake rerun with a hotkey or just run it always (which may or may be acceptable overhead), whereas adding the new source file definitely needs manual work.

azias commented 5 years ago

Actually I am using Visual Studio 2015 and when I launch a build, VS does not re-run cmake by default (and I do not want it does it systematically because it implies a long compilation time for small changes).

Actually my observation was mainly about the note given in the cmake documentation. It seems that you recommend something that is "not recommended" in the cmake documentation.

In my side, on the project I am setting up, I have mimicked the include/src namespace structures in a sub-directory of tools/ specific to cmake, and in it directory I have put a file.cmake that list the files that correspond the each namespace level (and the corresponding unit tests.)

Quincunx271 commented 5 years ago

It looks like Visual Studio 2015 is not supported by CONFIGURE_DEPENDS. When you update to a more modern generator, it should work fine; VS 2017 works great.

In the meantime, note that there's a pf update -bcmake command which can be used to list the source files, but it does require some strict source layout. It looks into the src/CMakeLists.txt and tests/CMakeLists.txt and generates a list of sources and headers at the location of the # sources comment. I use this to great effect when my build system is restricted.

On Linux, I run this in conjunction with inotifywait to always keep the source list up to date. On Windows, I manually rerun it.

Mike-Devel commented 5 years ago

@azias : I think the native cmake support I was talking about was only added in VS2017.

And yes, I don't agree with the statement in the documentation (and I'm not the only one) for the reason I gave and the practical experience I gathered with both approaches. As I said, cmake has to be rerun (by whatever means) with both approaches, but one of them also requires manual editing in addition. As long as the cmake documentation doesn't explain why that mode is preferable I don't give that recommendation a lot of weight. Also regarding the overhead of always re-running cmake: on most projects I've worked on, that overhead was not noticeable compared to build times, but that ration can of course differ significantly depending on the project in question.

Quincunx271 commented 5 years ago

There was a discussion in the cpplang slack's #cmake channel a while back. Globbing can be brittle. As a quick summary, it was mentioned by Ben Boeckel that you can end up building "conflict files" in case of a git conflict, and Damian Jarek's experience was, "The only reliable thing to do after a git submodule update was to delete everything from build and reconfigure."

Personally, I go for the glob if my build tools support CONFIGURE_DEPENDS, because it's not actually that hard to swap if it eventually becomes a problem. You can just have CMake spit out the list of files and copy it in (or use any other glob utility):

file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")
message(FATAL_ERROR "${sources}") # or something to this effect