0

I am working on a C project as of recently and want to learn how to use CMake properly. The project consists of the following directory structure (as of now):

.
└── system
    ├── collections
    │   ├── bin
    │   ├── build
    │   ├── includes
    │   └── src
    └── private
        └── corelib
            ├── bin
            ├── build
            ├── includes
            └── src

Every directory including 'bin' sub-directories is a separate library. They contain a CMakeLists.txt each.

The plan is to link the libraries in such a way that, during development, no manual recompilation of 'corelib' should be required to receive updated code from it, while also ensuring that dependencies would be resolved once all libraries get compiled as SHARED libraries and put in a place such as 'usr/local/lib' or similar.

I have a dependency of library 'corelib' in library 'collections'. Trying to resolve said dependency, I have come up with the following CMakeLists.txt in 'collections':

cmake_minimum_required(VERSION 3.0.0)
project(collections VERSION 0.1.0 LANGUAGES C)
set(LIBRARY_OUTPUT_PATH ../bin)
add_subdirectory(../private/corelib ${LIBRARY_OUTPUT_PATH})
include_directories(./includes)
aux_source_directory(./src SOURCES)
add_library(collections SHARED ${SOURCES} main.c)

However, this does not produce the result I am looking for, as I get the following output on build:

[main] Building folder: collections 
[build] Starting build
[proc] Executing command: /usr/bin/cmake --build /home/codeuntu/Repositories/netcore-c/src/system/collections/build --config Debug --target all -j 6 --
[build] gmake[1]: *** No rule to make target '../bin/all', needed by 'all'.  Stop.
[build] gmake[1]: *** Waiting for unfinished jobs....
[build] Consolidate compiler generated dependencies of target collections
[build] [ 50%] Built target collections
[build] gmake: *** [Makefile:91: all] Error 2
[build] Build finished with exit code 2

It seems this is the wrong way to go about it. Any help is greatly appreciated.

This is the CMakeLists.txt for 'corelib':

cmake_minimum_required(VERSION 3.0.0)
project(corelib VERSION 0.1.0 LANGUAGES C)
include_directories(./includes)
aux_source_directory(./src SOURCES)
set(LIBRARY_OUTPUT_PATH ../bin)
add_library(corelib SHARED ${SOURCES} main.c)

  • 1
    Unrelated to your problem, but shouldn't you tell CMake that `collections` should be linked with `corelib`? You seem to be missing a `target_link_libraries` command there. – Some programmer dude May 12 '22 at 07:02
  • You're correct, it should be linked. I'm not sure how to link the libraries so that they're linked properly regardless of their location. What I mean is, during development, it should link to the relative path ../private/corelib, but when released, it should link to usr/local/lib (/private/corelib?) – Anderson88 May 12 '22 at 07:10
  • Let CMake handle the locations, just use something like `target_link_libraries(collections corelib)` and CMake should be able to handle everything for you. – Some programmer dude May 12 '22 at 07:14
  • But the line 'add_subdirectory(../private/corelib ${LIBRARY_OUTPUT_PATH})' would cause a conflict if the corelib library existed as a SHARED library in usr/local/lib (or wherever gcc would look else), would it not? – Anderson88 May 12 '22 at 09:31
  • While it's not stated in [the `target_link_libraries` documentation](https://cmake.org/cmake/help/latest/command/target_link_libraries.html) I would assume the order CMake uses would be the one listed in the documentation. Which means a library target created with `add_library` in the same project would be used if it exists. – Some programmer dude May 12 '22 at 09:38
  • I gave it a shot, including `target_link_libraries(collections corelib)` in CMakeLists.txt of collections, but it didn't seem to enjoy it as much as I hoped: `[build] gmake[1]: *** No rule to make target '../bin/CMakeFiles/corelib.dir/all', needed by 'CMakeFiles/collections.dir/all' Stop.` – Anderson88 May 12 '22 at 09:46
  • Remove `../bin`, do `add_subdirectory(../private/corelib corelib)`. – KamilCuk May 12 '22 at 13:27
  • @KamilCuk that actually worked really well. I believe you meant to remove "../bin" from add_subdirectory, and not as the LIBRARY_OUTPUT_PATH alltogether. I am not sure why it worked, though. Could you please explain that to me, or point to where I can read up on this? – Anderson88 May 12 '22 at 14:10
  • https://stackoverflow.com/questions/50408169/cmake-error-add-subdirectory-not-given-a-binary-directory – KamilCuk May 12 '22 at 14:20

1 Answers1

0

Binary directory has to be a subdirectory of current dir, it can't be above ../bin. Use:

add_subdirectory(../private/corelib some_unique_name)

Overall, let's fix some issues. A more advanced CMake might look like this:

# system/CmakeLists.txt
add_subdirectory(private EXCLUDE_FROM_ALL)
add_subdirectory(collections)

# system/collections/CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(collections VERSION 0.1.0 LANGUAGES C)
file(GLOB_RECURSE srcs *.c *.h)
add_library(collections ${srcs})
# Use only target_* intefaces
target_include_directories(collections PUBLIC
   ./includes
)
target_link_libraries(collections PRIVATE 
   corelib
)

# system/private/CMakeLists.txt
add_subdirectory(corelib)

# system/private/corelib/CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(corelib VERSION 0.1.0 LANGUAGES C)
file(GLOB_RECURSE srcs *.c *.h)
add_library(corelib ${srcs})
target_include_directorieS(corelib PUBLIC ./includes)

# system/CMakePresets.json
{
    ... see documentation ...
    "configurePresets": [
      {
        ...
        "cacheVariables": {
          "BUILD_SHARED_LIBS": "1",
          "ARCHIVE_OUTPUT_DIRECTORY": "${binaryDir}/bin",
          "LIBRARY_OUTPUT_DIRECTORY": "${binaryDir}/bin",
          "RUNTIME_OUTPUT_DIRECTORY": "${binaryDir}/bin"
     }
}

I.e. overall, I do not think every project inside system wants to compile his own separate instance of corelib, rather one corelib should be shared. Just add corelib once, from anywhere. Note that it doesn't have to be in order - you can target_link_libraries on targets before they are defined.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks for adding an answer, it worked very well. I am going to implement my CMakeLists (and CMakePresets.json, which I wasn't aware existed until now) the way you showed in your answer. They make the project structure more flexible and less prone to breaking. – Anderson88 May 12 '22 at 14:39
  • Just saw your edit: `I.e. overall, I do not think every project inside system wants to compile his own separate instance of corelib, rather one corelib should be shared. Just add corelib once, from anywhere. Note that it doesn't have to be in order - you can target_link_libraries on targets before they are defined.` I agree with you on that, however: Wouldn't that mean that *all* libraries would be linked to `corelib`, even if they don't use it? If that were NOT the case, then I'd probably add every library on the top-most CMakeLists.txt, would that make sense? – Anderson88 May 12 '22 at 14:52
  • `target_link_libraries(collections PRIVATE corelib` libraires are linked explicitly. Only collections links with corelib. `then I'd probably add every library on the top-most CMakeLists.tx` Yea, you usually do that on big projects, and add `EXCLUDE_FROM_ALL` to all `add_subdirectory` and then only explicitly build what you need, cmake will figure out dependencies and build only what is really needed. – KamilCuk May 12 '22 at 15:23
  • I see. The project, where `system` resides in, is a collection of SHARED libraries (where `system`is one of them). So the top-most CMakeLists.txt should create everything into one bin-directory for distribution in, for example, usr/local/lib. From what I can tell, the above advice is directed towards projects where not everything needs to be compiled on the top-most level. How would I need to change this to get everything? – Anderson88 May 12 '22 at 16:15
  • I do not know the intricacies of your setup and your requirements - consider hiring a developer. `reate everything into one bin-directory for distribution in` you should distribute with `install()` and `cmake --install`. – KamilCuk May 12 '22 at 16:37
  • What I meant was to collect all .so and .h files into the main project ./bin directory. But I assume that'd be easy enough to do with a small bash script, with the main CMakeLists.txt just adding all sub_directories, with no EXCLUDE_FROM_ALL to build everything. – Anderson88 May 12 '22 at 17:09