I have a project that I'm trying to configure to make use of CMake, and to make my life easier I'm organizing it as a series of libraries that I'm treating as subprojects.
MyProject
|-- common-cmake
| |-- common-functions1.cmake
| |-- common-functions2.cmake
| `-- CMakeLists.txt
|-- lib1
| |-- include
| | `-- someClass.h
| |-- src
| | `-- someClass.cpp
| |-- test
| | |-- include
| | | `-- someClassTest.h
| | |-- src
| | | `-- someClassTest.cpp
| | `-- CMakeLists.txt
| `-- CMakeLists.txt
|-- lib2
| `-- <snip>
|-- lib3
| `-- <snip>
`-- CMakeLists.txt
There's a global CMakeLists.txt in MyProject that handles the cross-library specifics (such as version information and installation directories) and ensures that everything is included via
add_subdirectory(cmake-common)
add_subdirectory(lib1)
add_subdirectory(lib2)
add_subdirectory(lib3)
common-cmake contains common logic to avoid repetition in each library's or test's CMakeLists.txt (such as common shared library definition since all of the libraries have a common structure). Each library contains a CMakeLists.txt with the necessary information so that it itself can be compiled, and includes its test directory. Each test in turn has its own CMakeLists.txt to provide the necessary details for the compilation of the tests for the library it is contained within. Thus, both the library and its test CMakeLists.txt is dependent on details the global CMakeLists.txt provides and functions that are contained within the common-cmake.
After fighting with the specifics of this, I've got it working. I can kick-off a build from the global level and build/test/install all libraries as well as for an individual library via the appropriate make target.
Now that I've got this working at the global level, I'm trying to get this working in Eclipse. I've managed to get the global project imported and working by calling
cmake -G "Eclipse CDT4 - Unix Makefiles"
at the global level, however this means that everything is contained within a single Eclipse project and I find it extremely unwieldy to work with. Ideally I'd like to have each of the libraries as its own project within Eclipse. I can import the library as a project from within Eclipse directly, but when I do that Eclipse in turn starts throwing errors relating to failure to CMakeCache.txt not existing, or the common functions from common-cmake not being available (since it's not longer starting from the global CMakeLists.txt, those common files are never added).
The only way around this that I can think of would be to allow each library to be both part of the global project (as it is right now) as well as to be usable independently. I've been trying to figure out how this could be done, but unfortunately I haven't had any success.
add_subdirectory("..")
add_subdirectory("../common-cmake")
add_subdirectory doesn't allow me to add anything that's outside of the current directory's subdirectories (which makes a lot of sense), and the only way I've found is to include each file individually and have a rather ugly flag in place to prevent duplication, where the global and the project check each other's flag to prevent stepping on each other's toes.
# Global CMakeLists.txt
add_subdirectory(cmake-common)
if(NOT DEFINED ${PROJECT_BUILD})
set(FULL_BUILD true)
add_subdirectory(lib1)
add_subdirectory(lib2)
add_subdirectory(lib3)
endif()
# lib CMakeLists.txt
if(NOT DEFINED ${FULL_BUILD})
set(PROJECT_BUILD true)
include("../common-cmake/common-functions1.cmake")
include("../common-cmake/common-functions2.cmake")
endif()
Is there a way to accomplish this? Is what I'm trying to do antithetical to CMake and thus not possible? Is there a better way to achieve my end goal?
Related
I'd like to use a few classes from the LLVM project in my own Objective-C app. Specifically, I want to use the classes declared in BitstreamReader.h and BitstreamWriter.h. Unfortunately, I don't have much experience linking C++ libraries, so I don't really know where to begin. I started by installing llvm through Homebrew with brew install llvm#14. Then, in my Xcode project, I tried linking the libraries in /opt/homebrew/opt/llvm#14/lib, and adding /opt/homebrew/opt/llvm#14/include/** to my HEADER_SEARCH_PATHS.
Now I'm completely stuck. I have a bunch of build errors in Xcode like:
"Reference to unresolved using declaration"
"No member named 'ldiv' in the global namespace"
"Use of undeclared identifier 'wcspbrk'"
...
Any help at all would be greatly appreciated. Thanks!
In C++ world there are quite a few ways to incorporate a dependency into your project, I'll outline some of them, but will try to describe in details one which I find the most suitable for your scenario:
1. Installing libraries system/user-wide
This is the most classic approach, when you have the libraries pre-compiler and pre-installed for your specific platform so they are available under default library search-path. It's similar to how system frameworks in iOS are used - you only add linker command to the project, while the frameworks (libraries) exist independently from it (or don't exist, which causes linker error). The problem with this approach, is that you cannot really use the libs for restricted systems, where "default" libraries are unchangeable (iOS, tvOS, iPadOS)
2. Embedding static libraries
Another approach is to precompile all libraries you need into archives for all platforms the libraries are supposed to be used and then just embed one of the library version which is required into your final app binary. This approach is somewhat cumbersome and unportable because it will require you to compile the lib and re-embed it manually each time you need some parts of library changed or new platform supported.
3. Embedding an xcframework
This approach is very similar to the previous one with a few benefits that you can wrap binaries for all platforms under one package and even release it in SPM.
4. Using CMake build system
Many projects (and LLVM is not an exception) in C++ are made with use of so-called CMake tool. It's widely adopted multi-platform build system and one of the benefits you can employ is that you can easily include any other project as part of your own. At the same time CMake is not widespread at all in the world of mobile development, so you may have hard time finding relevant information for these platforms.
5. Using Xcode workspaces
This is the solution I'd like to describe in more details. In a nutshell this approach consists of 6 steps:
Download LLVM project repo;
Generate Xcode project for required LLVM libraries with use of CMake;
Make an Xcode workspace and add the newly generated project to it;
Add your own project to the same workspace;
Add required dependencies from LLVM project to your own;
Adjust project settings to make it compatible with the dependencies.
The benefits of this approach is that it gives the most consistent experience (after generating project, all parts are configurable from Xcode), freedom of managing the source code (the LLVM project files can be altered however you want), and luxury of built-in dependency graph (all libraries required to build a target are given under Xcode settings).
Downloading LLVM project
You can clone the project from here. Be advised that this has to be in the same folder your future workspace will be in, so you better prepare it in advance:
% mkdir MyWorkspace && cd MyWorkspace
% git clone git#github.com:llvm/llvm-project.git
Generating LLVM Xcode Project
First, ensure you have CMake installed (it doesn't come out of the box with macOS). You can use brew or just download the app from the official site. After that create a new folder for the future Xcode project next to llvm-project repo folder and run cmake on the LLVM toolkit project:
% mkdir LLVM && cd LLVM
% cmake -GXCode ../llvm-project/llvm
Creating Xcode workspace
Nothing fancy here, just open Xcode and navigate to File/New/Workspace menu (Ctrl+Cmd+N shortcut). Ensure that your workspace is created in the MyWorkspace folder. You can just do it from Xcode:
Then, add the LLVM project created on the previous step. Open File/Add Files to "MyWorkspace"... menu (Option+Cmd+A) and select the Xcode project file:
If Xcode suggests to auto-create schemes I recommend accepting this suggestion so you don't have to deal with it yourself later on.
Adding your own project
This step mimics the previous one with exception that you add your own project to the workspace. I didn't have an existing project for that, so I just created a new one (in my case macOS command-line app) in the workspace directory. If you do the same, ensure that the project is added to "MyWorkspace" and the folder is correct:
Eventually your workspace "Project Navigator" should look something like this:
And here is how the directory tree looks like in a nutshell:
% tree -L 2
.
|-- LLVM
| |-- $(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
| |-- CMakeCache.txt
| |-- CMakeFiles
| |-- CMakeScripts
| |-- CPackConfig.cmake
| |-- CPackSourceConfig.cmake
| |-- Debug
| |-- LLVM.xcodeproj
| |-- MinSizeRel
| |-- RelWithDebInfo
| |-- Release
| |-- benchmarks
| |-- build
| |-- cmake
| |-- cmake_install.cmake
| |-- docs
| |-- examples
| |-- include
| |-- lib
| |-- llvm.spec
| |-- projects
| |-- runtimes
| |-- test
| |-- third-party
| |-- tools
| |-- unittests
| `-- utils
|-- MyProject
| |-- MyProject
| `-- MyProject.xcodeproj
|-- MyWorkspace.xcworkspace
| |-- contents.xcworkspacedata
| |-- xcshareddata
| `-- xcuserdata
`-- llvm-project
|-- CONTRIBUTING.md
|-- LICENSE.TXT
|-- README.md
|-- SECURITY.md
|-- bolt
|-- clang
|-- clang-tools-extra
|-- cmake
|-- compiler-rt
|-- cross-project-tests
|-- flang
|-- libc
|-- libclc
|-- libcxx
|-- libcxxabi
|-- libunwind
|-- lld
|-- lldb
|-- llvm
|-- llvm-libgcc
|-- mlir
|-- openmp
|-- polly
|-- pstl
|-- runtimes
|-- third-party
`-- utils
Adding dependencies to your project
From this point forward you'll only need Xcode to finish the job. First, let's link the libraries you need to the project and start from the Bitstream archive. Open your project's target General tab settings and under Framework and Libraries click the + sign which should take you to the screen with all local dependencies currently available. It's quite a long list of tools from the LLVM project + native Apple libs, so you may want to filter it as needed to find the required position:
Now the tricky part. You might not actually be required to do that, but if the LLVMBitstreamReader target itself relies on symbols of other libraries (and exposes such a dependency with explicit use of the libraries symbols) the project linking will fail, so to be safe go to the LLVMBitstreamReader build settings and check what it depends on under Target Dependencies section:
Now add these dependencies to your project as well. Eventually your Frameworks and Libraries section should look like this:
Adjusting project settings
CMake generated Xcode projects come with peculiarities. In our case the build folders for the LLVM project targets are located under the project directory with the name matching selected configuration (Debug/Release/MinSizeRel/RelWithDebInfo). In order for your own project's targets to find the libraries built with the given configuration you either have to adjust the LLVM project/targets settings, which I don't recommend because it's a lot of manual work, or just add custom library search path to your project. For the latter go the Build Settings of the target where you added the dependencies at the previous step, find Library Search Path item and add $(SRCROOT)/../LLVM/$(CONFIGURATION)/lib as a new item:
Another tricky part is that dependent targets won't make dependencies to build if the dependencies' targets have incompatible build settings. Incompatible is a vague term here, but commonly it means matching the Architectures section. Luckily in our case it merely means making Release configuration to build active architecture only (the Debug shouldn't require any changes at all):
Last, but not least, the headers of the library. After generating the LLVM Xcode project, the headers it offers don't (completely) come with the project and are actually located in the repository folder. You may just copy the headers right into your own project, but I suggest adding the repo directory in the System Headers Search paths to stay consistent with the LLVM Xcode Project settings (you can yourself check where the headers search paths are under the the LLVMBitstreamReader Build Settings). Similarly to lib search path, I suggest use help of settings variables and add this path flexibly:
$(SRCROOT)/../llvm-project/llvm/include
$(SRCROOT)/../LLVM/include
Finale
At this point you should be good to use the LLVMBitstreamReader library and classes defined there. I renamed my main.m to main.mm so clang knows i'm going to use C++ in my code, and here is my sample code:
//
// main.mm
// MyProject
//
// Created by Aleksandr Medvedev on 14.12.2022.
//
#import <Foundation/Foundation.h>
#import <llvm/Bitstream/BitstreamReader.h>
#import <iostream>
int main(int argc, const char * argv[]) {
llvm::BitstreamBlockInfo::BlockInfo info;
info.Name = "Hello, LLVM!";
std::cout << info.Name << std::endl;
#autoreleasepool {
// insert code here...
NSLog(#"Hello, World!");
}
return 0;
}
If everything is done right, it should compile without any errors now.
P.S. I many times focused on specific directory hierarchy for a reason - when an XCode project is generated through CMake it often uses absolute paths, not relative and if you move your project to another directory, you will have to either adjust the paths in the build settings of required targets/projects or repeat the step with generating the LLVM Xcode project again.
Following is my CMakeLists.txt structure
|-- MyProject
|-- CMakeLists.txt
|-- README.md
|-- bin
|-- include
|-- lib
|-- src
| |-- CMakeLists.txt
| |-- main.cc
| |-- parser
| |-- CMakeLists.txt
| |-- parser.cc
| |-- parser.h
|-- third-party
|-- CMakeLists.txt
|-- cassandra-cpp-driver
Now I want to introduce a yugabyteDB-cpp-driver https://github.com/yugabyte/cassandra-cpp-driver/tree/2.9.0-yb to use its API, so I add those commands into my root CMakeLists.txt
set(THIRD_PARTY_DIR ${PROJECT_SOURCE_DIR}/third-party)
add_subdirectory(${THIRD_PARTY_DIR})
Then I add these to third-party/CMakeLists.txt
set(CQL_DRIVER_LIB_NAME "cassandra-cpp-driver")
set(CQL_DRIVER_INC_PATH ${CQL_DRIVER_LIB_NAME}/include)
add_subdirectory(${CQL_DRIVER_LIB_NAME})
After these, when I trying to do build the project, I got
CMake Error at third-party/cassandra-cpp-driver/CMakeLists.txt:10 (include):
include could not find requested file:
CppDriver
CMake Error at third-party/cassandra-cpp-driver/CMakeLists.txt:12 (CassInitProject):
Unknown CMake command "CassInitProject".
Seems the cpp-driver it self has a include() command which calls the .cmake files the git contains, but I can't trigger it in my project.
Note: Below I describe a solution to the specific problem occuring, but this is in no way a complete solution to allow you to include the project the way you intend to. This is unlikely to be possible without modifying the third party cmake logic. The cmake logic just doesn't seem to be designed for your intended use case.
CMake Error at third-party/cassandra-cpp-driver/CMakeLists.txt:10 (include):
include could not find requested file:
CppDriver
The relevant lines in the CMakeLists.txt file in the repository linked are
set(CASS_ROOT_DIR ${CMAKE_SOURCE_DIR})
...
list(APPEND CMAKE_MODULE_PATH ${CASS_ROOT_DIR}/cmake/modules)
include(CppDriver)
The first line is supposed to make cmake able to the CppDriver.cmake module when using the include() command.
CMAKE_SOURCE_DIR refers to the directory containg the toplevel CMakeLists.txt though, which is your own directory. To be able to include the project via add_subdirectory, the relevant line should read
set(CASS_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR})
(Note: In no way this is a complete analysis of the cmake logic. There may be different parts that would require similar adjustments.)
You could fix this specific error by simply adding the correct path before using add_subdirectory():
set(CQL_DRIVER_LIB_NAME "cassandra-cpp-driver")
set(CQL_DRIVER_INC_PATH ${CQL_DRIVER_LIB_NAME}/include)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${CQL_DRIVER_LIB_NAME}/cmake/modules)
add_subdirectory(${CQL_DRIVER_LIB_NAME})
But you'd just run into a similar issue in a different place in the third party cmake logic.
At the early stage of our development, we have created micro-repositories, to be able to deeply modify repositories while minimizing interference with each other's work.
Now that we have a fairly stable version of our codebase, we want to merge some of the micro-repositories into a monorepo. That will ease the users' experience while building from source, as well as the deployment phase.
All our micro-repositories are built with CMake, and each of them obviously defines a project(). They also have 'internal' dependencies: some of the micro-repositories is built first and then used by the others with find_package().
RepoA
|-- CMakeLists.txt
| project(RepoA)
RepoB
|-- CMakeLists.txt
| project(RepoB)
| find_package(RepoA)
RepoC
|-- CMakeLists.txt
| project(RepoC)
| find_package(RepoA)
| find_package(RepoB)
While migrating them into a monorepo, the first option that came to my mind is moving each of them into a subdirectory, then adding add_subdirectory() to the root CMakeLists.txt file to build them. In the first instance, I would define a global project by using project() in the root CMakeLists.txt file, and I would also keep the project() definition in subdirectories.
Monorepo/
|-- CMakeLists.txt
| project(Monorepo)
| add_subdirectory(RepoA)
| add_subdirectory(RepoB)
| add_subdirectory(RepoC)
|-- RepoA/
|-- CMakeLists.txt
| project(RepoA)
|-- RepoB/
|-- CMakeLists.txt
| project(RepoB)
| find_package(RepoA)
|-- RepoC/
|-- CMakeLists.txt
| project(RepoC)
| find_package(RepoA)
| find_package(RepoB)
Now the question is: is that a good choice? Would you advise having a global project and multiple sub-projects, all of them defined through the use of the CMake project() command? Are there better strategies?
And also, would this strategy have drawbacks?
Many thanks for your kind help!
I've started a new c++ project , and I am confused with all the CMake capabilities. I have tried to understand better by looking at examples and CMake tutorials
I should create a new project composed of:
Library: It contains some common classes that will be used by the following module(s) (e.g., vector, matrix, image, etc..)
Module (possibly more than 1 in the future): It contains some module-specific classes (e.g., classifier, estimator, etc.) and a main.
My proposed folder structure is as below:
|-- Root Project
|-- CMakeLists.txt
|
|-- Library
| |-- CMakeLists.txt
| |-- include
| | |-- CMakeLists.txt (?)
| | `-- Lib_Class.h
| `-- src
| |-- CMakeLists.txt (?)
| `-- Lib_Class.h
|
|-- Application 1
| |-- CMakeLists.txt
| |-- include
| | |-- CMakeLists.txt (?)
| | `-- Method.h
| `-- src
| |-- CMakeLists.txt (?)
| |-- Method.cpp
| `-- main.cpp
|
|-- Application 2
| |-- CMakeLists.txt
| |
`
The problem arises when I have to actually add the code to the different CMakeLists.txt files. According to my reasoning, I would have:
Root/CMakeLists.txt: For creating the project and adding the subdirectories of the Library and the Module(s).
Library/CMakeLists.txt: This creates the library with the header (from include folder) and source (from src folder) files.
Module/CMakeLists.txt: This creates an executable from the src/main.cpp file using the Library and the module-specific classes with header files in include folder and source files in src folder.
I have 2 questions:
First, I also found solutions in other replies with CMakeLists.txt files in the Library/src and Module/src folders. But I really don't understand how to use them and what to write inside them, because I would have used only the CMakeLists.txt file in the parent folder.
Second, in case I want to link an external library (e.g., OpenCV or dlib) should I link it in the modules and library, individually, or should I link it in the root CMakeLists.txt file (provided that the library is used everywhere)?
I really need some assistance to try to understand CMAKE. Can someone explain or please direct me to a suitable tutorial on this subject.
Matthieu, thank you very much for your help. According to the explanation you provided me, I came out with the following CMakeLists.txt files:
Root/CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(Project_Name)
add_subdirectory(Library)
add_subdirectory(Application)
Library/CMakeLists.txt
project(Library)
set(LIB_HEADERS
include/Lib_Class.h
)
set(LIB_SOURCES
src/Lib_Class.cpp
)
add_library(Library_Name SHARED ${LIB_SOURCES} ${LIB_HEADERS})
Application/CMakeLists.txt
project(Application)
set(APP_HEADERS
include/Method.h
)
set(APP_SOURCES
src/Method.cpp
src/main.cpp
)
add_executable(Application_Name ${APP_SOURCES} ${APP_HEADERS})
target_link_libraries(Application_Name Library_Name)
Now everything seems to work grat! Thank you again and sorry again for being confusing somethimes!
The root cmakelists should set up all the variables, checking compiler support and library presence.
Then you go to each subfolder and create the libraries and executables based on the source code and the detected libraries. You should also set up all the linked libraries there.
Then cmake will figure out what depends on what.
I started playing around with CMake to create a project with Qt and test it with Google Test. At the moment, I succesfully found a way to compile and link all the required libraries. However, I couldn't find a way to link sources to test files with following project structure:
root
|
+-- CMakeLists.txt
+-- src
| |
| +-- CMakeLists.txt
| +-- MyClass.h
| +-- MyClass.cpp
|
+-- test
| |
| +-- CMakeLists.txt
| +-- MyClassTest.cpp
|
+-- lib
|
+-- gtest-1.6.0
|
+-- CMakeLists.txt
Root CMakeLists.txt contains add_subdirectory for gtest, src and test folders. I have succesfully compiled and run "Hello world" app and simple EXPECT_TRUE(true) test in order to check that each part compiles correctly. Unfortunately, I couldn't find a way to include my source file to tests. Is it possible with the following project structure?
PS I know that it is possible to compile my sources as a library and link it to tests, but I dislike that approach, since it is more appropriate for integration testing, rather then unit testing...
EDIT: Added class names to the tree
You can add a global variable at the level of your root CMakeLists.txt:
set(ALL_SRCS CACHE INTERNAL "mydescription" FORCE)
In the first add_subdirectory(src), you can do:
set(ALL_SRCS ${ALL_SRCS} blabla.cpp CACHE INTERNAL "description")
And in the add_subdirectory(test), you continue with:
set(ALL_SRCS ${ALL_SRCS} bla_test.cpp CACHE INTERNAL "description")
You can then do, add_executable, or library or whatever, with all your sources files.
EDIT: add trick for global variables in CMake.
In the root CMakeLists.txt you can add a include_directories(src) This will then also be used by the tests. Another thing you can do is in the test CMakeLists.txt add a include_directories(${<projectName>_SOURCE_DIR}) where projectName is the name specified using project(myproj) in the src/ CMakeLists.txt (if you specified a project in there of course. Also check the docs about project)