Setting up a CPF project¶
This section shows how to create a CPF based project. Before doing so, it is recommended that you get acquainted with the CMakeProjectFramework by building the example project as described on the Working with a CPF Project page. Basic knowledge of CMake and how to work with Git is helpful but it should be possible to work through this tutorial without any preexisting knowledge of those tools.
Step by step tutorial for creating a CPF project¶
This tutorial will walk you through the steps that are required to create a CPF project. It starts with a very simple project that will then be extended to demonstrate more advanced features. The goal of the tutorial is to show you what must be done to create a new CPF project and to explain some details in the process. If you already know your way around a CPF project and you just want to create a new one, it is probably quicker if you simply copy the example project and remove the stuff that you do not need and rename the rest.
This tutorial assumes that you work on Windows with Visual Studio 2017. It also assumes that you can run CMake and the python
scripts from the command line without prepending the extra python
command. If you want to run this tutorial
on Linux, you have to use the base Linux configuration in the configuration step instead of the Windows configuration.
Until not stated differently, all commands should be executed in the projects root directory.
Note
Differences on Linux
When working on Linux there are a few differences.
The build output directory for the example executables is a slightly different directory with an additional
bin
subdirectory.When running executables you have to prepend
./
to the executable file.At some places you may have to replace
\\
with/
.You will have to replace commands for filesystem operations with their Linux equivalents.
1. Create the repository¶
First you need a Git repository that will hold your CI project. For simplicity’s sake we create the remote git repository on the same machine that we use for working on the cloned repository. If you are familiar with setting up remote Git repositories you can also create it on a different machine.
To create the remote repository navigate to a directory that is suited for holding the example repositories. If work with an older
Windows version, make sure that it is a short path. Because of the deep nested directory structure, your CPF project may get into trouble with
the 260 character path limit.
Create the directory MyCPFProject.git
, open a command line and create the remote repository by executing the following commands:
> cd MyCPFProject.git
> git init --share --bare
Clone the remote repository with:
> cd ..
> git clone MyCPFProject.git
This will create the MyCPFProject
directory. This is the root directory of your project
that we will use to add and edit source files. In the next section we will add some basic files to that
repository.
2. Basic CI project files¶
We now have the repository that will hold the files of our project. First we add the basic files that are required for a CPF project. You now have to create the files
MyCPFProject
│ .gitignore
│
└───Sources
CMakeLists.txt
packages.cmake
with the following content:
.gitignore¶
The file will make sure that directories and files that are generated by the CPF are ignored by git.
# file MyCPFProject/.gitignore
Generated/**
Generated
Configuration/**
Configuration
1_Configure.py
2_Generate.py
3_Make.py
CMakeLists.txt¶
This is the root CMakeLists.txt
file of your CPF project.
# file MyCPFProject/Sources/CMakeLists.txt
include("CPFCMake/cpfInit.cmake")
cmake_minimum_required (VERSION ${CPF_MINIMUM_CMAKE_VERSION})
project(MyCPFProject)
cpfAddPackages()
include("CPFCMake/cpfInit.cmake")
: Including cpfInit.cmake provides us with theCPF_MINIMUM_CMAKE_VERSION
variable that is used in the next line. Alternatively you can set your own minimum version that must be equal or higher then the version that is required by the CPF. The cpfInit.cmake module also includes further files to make the cpfAddPackages() function available that is used later. cpfInit.cmake also adds the cmake modules of the CPF to the CMAKE_MODULE_PATH which allows you to use the short include syntax from here on.project(MyCPFProject)
: This creates the so called CI project.cpfAddPackages()
: This function adds the packages to the CI project. The function reads the value of theCPF_PACKAGES
variable in thepackages.cmake
file and adds them to the project by calling add_subdirectory(). cpfAddPackages() also initiates some global variables and targets of the CPF.
packages.cmake¶
This file defines which packages are added with the call of cpfAddPackages().
# file MyCPFProject/Sources/packages.cmake
set( CPF_PACKAGES
)
For now we have no packages available so the value of the package list CPF_PACKAGES
stays empty.
We will modify this file later when we add our first packages.
Read more about the file in chapter The packages.cmake file.
Add the basic files to the repository¶
After creating the listed files you should now commit them to the repository and add a first version tag. The tag is required by the CPF to determine the version number of the packages. If it is missing, the CPF will later cause errors.
> git add .
> git commit . -m"Adds basic files"
> git tag -a 0.0.0 -m"The initial version"
3. Add the CPF packages¶
In the last section you added a CMakeLists.txt
file that uses some functions from the CPFCMake package.
However, you do not have those functions yet available in your project. To change that we now add two packages
in form of git submodules. Open a command line and navigate to the Sources
directory. Then run:
Sources> git submodule add https://github.com/Knitschi/CPFCMake.git
Sources> git submodule add https://github.com/Knitschi/CPFBuildscripts.git
The submodules CPFCMake and CPFBuildscripts are part of the CMakeProjectFramework.
Adding them to the source tree is not enough. We also have to add them to the packages.cmake
file as EXTERNAL
packages.
# file MyCPFProject/Sources/packages.cmake
set( CPF_PACKAGES
EXTERNAL CPFCMake
EXTERNAL CPFBuildscripts
)
4. Run the generate step for the first time¶
Now that we have acquired the CPF packages we can run the generate step to see if everything works. Run the following commands in the project root directory.
> Sources\CPFBuildscripts\0_CopyScripts.py
> 1_Configure.py VS --inherits Windows
> 2_Generate.py
This creates the Configuration
and Generated
directories parallel to your Sources
directory.
Your file tree should now look like this.
MyCPFProject
│ .gitignore
│ .gitmodules
│ 1_Configure.py
│ 2_Generate.py
│ 3_Make.py
│
├───Configuration
│ VS.config.cmake
│
├───Generated
│ └───VS
│ ...
│
└───Sources
│ CMakeLists.txt
│ packages.cmake
│
├───CPFBuildscripts
│ ...
│
└───CPFCMake
...
5. Add the MyApp application package¶
Our CI project is now ready. The only thing that is missing is some actual C++ code.
In order to add some payload code, we add our first package to the MyCPFProject
repository. This is a package that creates an executable. Add the following
file tree to the Sources
directory.
Sources
│
└───MyApp
CMakeLists.txt
function.cpp
function.h
main.cpp
Set the content of the new files in the MyApp directory as follows.
CMakeLists.txt¶
# file MyCPFProject/Sources/MyApp/CMakeLists.txt
include(cpfInitPackageProject)
include(cpfAddCppPackage)
cpfInitPackageProject()
set( briefDescription "A C++ command line application that prints a string." )
set( longDescription "The application is so small that it needs no long description" )
############################## list source files ##############################
set( PACKAGE_PRODUCTION_FILES
main.cpp
function.cpp
function.h
)
set( PACKAGE_LINKED_LIBRARIES
)
################################# add Package #################################
cpfAddCppPackage(
PACKAGE_NAMESPACE ma
TYPE CONSOLE_APP
BRIEF_DESCRIPTION ${briefDescription}
LONG_DESCRIPTION ${longDescription}
PRODUCTION_FILES ${PACKAGE_PRODUCTION_FILES}
LINKED_LIBRARIES ${PACKAGE_LINKED_LIBRARIES}
)
include(cpfInitPackageProject)
: Provides the cpfInitPackageProject function.include(cpfAddCppPackage)
: Provides the cpfAddCppPackage function.cpfInitPackageProject()
: This creates the package project and sets thePROJECT_VERSION
variables.set( briefDescription ...
andset( longDescription ...
: Basic descriptions of what your package does. The values may be used in auto-generated documentation or in package archives.set( PACKAGE_PRODUCTION_FILES ...
: A list with the currently available source files of the package. This is aCMakeLists.txt
file after all ;-).set( PACKAGE_LINKED_LIBRARIES ...
: A list with targets on which the created package depends. For now we have no dependencies so the list is empty.:ref:`cpfAddCppPackage`
: This function adds all the binary and custom targets that belong to a CPF C++ package. Most of CPFCMake’s functionality lies beneath this function.
function.cpp¶
This is a simple C++ file that implements a function. It represents your C++ code.
// file MyCPFProject/Sources/MyApp/function.cpp
#include <MyApp/function.h>
#include <iostream>
#include <MyApp/cpfPackageVersion_MyApp.h>
namespace ma
{
bool function()
{
std::cout << "MyApp (version " + getPackageVersion() + ") greets the world!" << std::endl;
return true;
}
}
The file includes the generated file cpfPackageVersion_MyApp.h
which provides function ma::getPackageVersion()
that returns the current version number
of the package. Note that the include directories and the project directory structure in the CPF is laid-out that all includes can uniformly be
written as \#include <package/file.h>
. As recommended in the section for the CMakeLists.txt
file we put our package functions into namespace ma
.
function.h¶
// file MyCPFProject/Sources/MyApp/function.h
#pragma once
#include <MyApp/ma_export.h>
namespace ma
{
MA_EXPORT bool function();
}
Note the use of the MA_EXPORT
export macro. The macro is provided by the ma_export.h
header
which is generated by CMake. The MA_
prefix in the macro is derived from the PACKAGE_NAMESPACE ma
option in the call of cpfAddCppPackage.
Note
Export Macros
Export macros are only needed when building shared libraries. However, it is good practice to let the clients of a library decide whether they want to use the library as a shared or a static library. So better make sure that you always add the export macro to symbols that are intended to be used by clients. If you do not add the macro in a shared library, you will get linker errors.
main.cpp¶
// file MyCPFProject/Sources/MyApp/main.cpp
#include <MyApp/function.h>
int main(int, char**)
{
return ma::function();
}
The CPF expects the main function to be in the file main.cpp
. In the case of a package that creates
an executable, the CPF internally creates an extra library that contains all sources except the main.cpp
file.
This is done to allow linking the complete functionality of the package to more than one executable.
This is required when adding additional test executables to the package.
Add the owned package to the CI project¶
We now have to tell the CI-project that we added an OWNED
package. We do this by adding MyApp
to the
packages.cmake
file.
# file MyCPFProject/Sources/packages.cmake
set( CPF_PACKAGES
EXTERNAL CPFCMake
EXTERNAL CPFBuildscripts
OWNED MyApp
)
Finally commit the new files to the repository by running:
> git add Sources/MyApp
> git commit . -m"Adds the MyApp package."
Build the project¶
With all the files in place we can now generate and build the project. Note that you have to run a fresh generate
whenever you change the packages.cmake
file. A fresh generate is executed when the --clean
option is given to the 2_Generate.py
script. After that you can build the pipeline target and run the application.
> 2_Generate.py --clean
> 3_Make.py --target pipeline
> Generated\VS\BuildStage\Debug\MyApp\MyApp-debug
MyApp (version 0.0.0.2-73cc-dirty) greets the world!
Your actual version number will be different and depends on the current state of your repository. You can read more about the CPF versioning mechanism here.
At this point our pipeline does not contain a lot of functionality. It only builds the MyApp target.
The only thing to note here is that cpfAddCppPackage created the library libMyApp
and the executable
MyApp-debug
.
6. Add a default configuration¶
For our first build we used one of the default configurations that are shipped with the CPF, Linux or Windows. In this section we add our own default configuration to the project. Default configurations can be thought of as configurations that are officially supported by our project. They are typically the configurations that are build by the continuous integration server. You can find more detailed information about configurations in the CPF here. For demonstration purposes we change the configuration to build shared libraries instead of static libraries. To create the new default configuration execute the following steps.
> 1_Configure.py VS2017-shared --inherits Windows -D BUILD_SHARED_LIBS=ON
> mkdir Sources\CIBuildConfigurations
> move Configuration\VS2017-shared.config.cmake Sources\CIBuildConfigurations
> rmdir /s /q Configuration
> 1_Configure.py VS --inherits VS2017-shared
> 2_Generate.py
> git add Sources\CIBuildConfigurations
> git commit . -m"Adds the default configuration VS2017-shared."
First we create a new configuration file that sets a different value for variable
BUILD_SHARED_LIBS
. This variable is used to tell CMake to build shared libraries instead of static ones. This will change our implementation librarylibMyApp
into a shared library. Instead of using the-D
command line options, you can also edit the configuration file with a text-editor, which may be more convenient if multiple values are changed.Then we move the new configuration to the projects default configurations directory
Sources/CIBuildConfigurations
.With the second configure step we use the new default configuration as our new local VS configuration.
We regenerate our make-files with the new configuration.
At the end we commit the new configuration file to the repository.
Developers can now inherit from the new default configuration VS2017-shared
instead of
manually setting all the required CMake variables in the 1_Configure.py
step.
7. Add the library package MyLib¶
As your C++ project grows, it will at some point be reasonable to split it into multiple libraries.
With the CPF we create libraries by adding a library package. In this example we assume that our library
will be used by other projects. To allow this, we create a separate repository for the library package.
We then add this repository as a git submodule to our MyCPFProject
repository.
If you do not know if a library will be shared between projects, you can first add it directly to the
CI repository to avoid the extra overhead of working with a git submodule. You can still put it
in it’s own repository later.
Create a new repository with the name MyLib
using the same steps that you executed when creating the
MyCPFProject
repository. You should end up with two empty repositories, MyLib.git
and MyLib
.
Both lie besides the MyCPFProject.git and :code:`MyCPFProject
directories. We will first add some files to
the MyLib
repository and then add it as a git submodule to MyCPFPRoject
.
Add content to the MyLib repository¶
Add the following text-files to the MyLib
repository and set the content as listed
in the sections below.
MyLib
CMakeLists.txt
function.cpp
function.h
CMakeLists.txt¶
# file MyLib/CMakeLists.txt
include(cpfInitPackageProject)
include(cpfAddCppPackage)
cpfInitPackageProject()
set( briefDescription "A C++ library that prints a string." )
set( longDescription "The library is so small that it needs no long description" )
############################## list source files ##############################
set( PACKAGE_PUBLIC_HEADERS
function.h
)
set( PACKAGE_PRODUCTION_FILES
function.cpp
)
set( PACKAGE_LINKED_LIBRARIES
)
################################# add Package #################################
cpfAddCppPackage(
PACKAGE_NAMESPACE ml
TYPE LIB
BRIEF_DESCRIPTION ${briefDescription}
LONG_DESCRIPTION ${longDescription}
PUBLIC_HEADER ${PACKAGE_PUBLIC_HEADERS}
PRODUCTION_FILES ${PACKAGE_PRODUCTION_FILES}
LINKED_LIBRARIES ${PACKAGE_LINKED_LIBRARIES}
)
This file has some differences compared to the MyApp\CMakeLists.txt
file.
We changed the name of the namespace and the description of the package.
We changed the
TYPE argument
in the cpfAddCppPackage call in order to create a library package.We added the
PUBLIC_HEADER
argument to the cpfAddCppPackage call. Libraries must provide public headers for consumers. With the argument we can define which of our headers are supposed to be public. Each library needs at least one public header or the project will fail to build.
function.cpp¶
// file MyLib/function.cpp
#include <MyLib/function.h>
#include <iostream>
#include <MyLib/cpfPackageVersion_MyLib.h>
namespace ml
{
bool function()
{
std::cout << "MyLib (version " + getPackageVersion() + ") greets the world!" << std::endl;
return true;
}
}
function.h¶
// file MyLib/function.h
#pragma once
#include <MyLib/ml_export.h>
namespace ml
{
ML_EXPORT bool function();
}
Add the new files to the MyLib repository¶
Now add, commit and push all files in the MyLib
repository. We also add an initial
version tag for the MyLib
repository.
MyLib> git add .
MyLib> git commit . -m"Adds package files"
MyLib> git tag -a 0.0.0 -m"The initial version"
MyLib> git push --all
MyLib> git push --tags
Add the MyLib package to the MyCPFProject repository¶
Now add MyLib
as a loose owned package to MyCPFProject
as a git submodule
by running
.../MyCPFProject/Sources> git submodule add <your full path>/MyLib.git
in the MyCPFProject/Sources
directory. This will yield a MyCPFProject/Sources/MyLib
directory
that contains the files that you created in the above section. To finish the process of adding the MyLib package
we have to extend some files in MyCPFProject
.
packages.cmake¶
We add the MyLib
package as owned package to CI project by adding it in the packages.cmake
file.
As the variable description states, it is essential that MyLib
is added to the list before MyApp
or cmake will not be able to find MyLib
when it adds MyApp
.
# file MyCPFProject/Sources/packages.cmake
set( CPF_PACKAGES
EXTERNAL CPFCMake
EXTERNAL CPFBuildscripts
OWNED MyLib
OWNED MyApp
)
MyApp/CMakeLists.txt¶
To make the functionality of MyLib
available in MyApp
,
we have to add it to the linked libraries of MyApp
.
# file MyCPFProject/Sources/MyApp/CMakeLists.txt
...
set( PACKAGE_LINKED_LIBRARIES
MyLib
)
...
MyApp/function.cpp¶
We extend our original ma::function()
to also call ml::function()
from MyLib
.
// file MyCPFProject/Sources/MyApp/function.cpp
#include <MyApp/function.h>
#include <iostream>
#include <MyApp/cpfPackageVersion_MyApp.h>
#include <MyLib/function.h>
namespace ma
{
bool function()
{
ml::function();
std::cout << "MyApp (version " + getPackageVersion() + ") greets the world!" << std::endl;
return true;
}
}
You now have to commit the changes to MyCPFProject
and regenerate the make-files in order to finish adding the library package.
> git commit . -m"Adds MyLib submodule and uses it in MyApp."
> 2_Generate.py
> 3_Make.py
> Generated\VS\BuildStage\Debug\MyApp\MyApp-debug.exe
MyLib (version 0.0.0) greets the world!
MyApp (version 0.0.0.3-3d31) greets the world!
You can see that MyApp
successfully calls the new function from MyLib.
Again, your version numbers will be different. MyLib has a different version than MyApp
because it lives in a different repository.
8. Add a test executable to MyLib¶
The CPF packages are designed to contain an extra executable that runs automated tests for the packages
production code. This section will show you how to enable such a test executable for the MyLib
package.
Add the new file MyLib_tests_main.cpp
to a new Tests
directory with the content
// MyCPFProject/MyLib/Tests/MyLib_tests_main.cpp
#include <iostream>
#include <MyLib/function.h>
int main(int, char**)
{
std::cout << "Run tests for MyLib" << std::endl;
std::cout << std::endl;
auto result = ml::function();
if(result)
{
return 0;
}
else
{
return 1;
}
}
In a real project you would probably use the main function that is provided by your test-framework
instead of writing your own. Note that we placed the file into the arbitrary Tests
subdirectory
which allows us to keep some order in our package. Change the packages CMakeLists.txt
file content
to this:
# file MyCPFProject/Sources/MyLib/CMakeLists.txt
include(cpfInitPackageProject)
include(cpfAddCppPackage)
cpfInitPackageProject()
set( briefDescription "A C++ library that prints a string." )
set( longDescription "The library is so small that it needs no long description" )
############################## list source files ##############################
set( PACKAGE_PUBLIC_HEADERS
function.h
)
set( PACKAGE_PRODUCTION_FILES
function.cpp
)
set( PACKAGE_TEST_FILES
Tests/MyLib_tests_main.cpp
)
set( PACKAGE_LINKED_LIBRARIES
)
set( PACKAGE_LINKED_TEST_LIBRARIES
)
################################# add Package #################################
cpfAddCppPackage(
PACKAGE_NAMESPACE ml
TYPE LIB
BRIEF_DESCRIPTION ${briefDescription}
LONG_DESCRIPTION ${longDescription}
PUBLIC_HEADER ${PACKAGE_PUBLIC_HEADERS}
PRODUCTION_FILES ${PACKAGE_PRODUCTION_FILES}
TEST_FILES ${PACKAGE_TEST_FILES}
LINKED_LIBRARIES ${PACKAGE_LINKED_LIBRARIES}
LINKED_TEST_LIBRARIES ${PACKAGE_LINKED_TEST_LIBRARIES}
)
We added two new lists, PACKAGE_TEST_FILES
and PACKAGE_LINKED_TEST_LIBRARIES
and handed them
to the cpfAddCppPackage()
function. The PACKAGE_TEST_FILES
list should contain all source
files that are used to build the test executable. the PACKAGE_LINKED_TEST_LIBRARIES
list
can be used to add linked libraries that are only used by the test executable. This could be a test-framework
library for example. In this example our test executable does not depend on any other library so we leave this empty.
You can now build and run your test executable by calling:
> 2_Generate.py
> 3_Make.py --target runAllTests
...
Run tests for MyLib
MyLib (version 0.0.0.0-dirty) greets the world!
...
Somewhere in the build-log you should see the text output of the executable. Note that tests will not be re-run if you execute the build command a second time. You have to edit at least one source file of the package in order to outdate the test-run. If you then rebuild the runAllTests target it will automatically create new binaries and run the tests with those.
For more information about the test targets of a CPF package see: Test targets
9. Add a fixture library to MyLib¶
When writing a lot of automated tests, it may become necessary to re-use test utility code from one package
in another. For example this can be fake or mock classes that you provide to replace real implementations in tests.
To make that possible, the CPF can create an extra fixture library per package that can contain reusable
test code. To demonstrate this, add two files function_fixture.h
and function_fixture.cpp
to the
MyLib package with the following content and add them to the CMakeLists.txt
file as shown below.
Sources/MyLib/Tests/function_fixture.h¶
// MyCPFProject/Sources/MyLib/Tests/function_fixture.h
#pragma once
#include <MyLib/ml_tests_export.h>
namespace ml
{
ML_TESTS_EXPORT void prepareFunctionTest();
}
Note that the fixture library uses a different export macro then the production library.
Sources/MyLib/Tests/function_fixture.cpp¶
// MyCPFProject/Sources/MyLib/Tests/function_fixture.cpp
#include <MyLib/Tests/function_fixture.h>
#include <iostream>
namespace ml
{
void prepareFunctionTest()
{
std::cout << "Do reusable test preparations here" << std::endl;
}
}
Sources/MyLib/CMakeLists.txt¶
For the fixture library we have to distinguish between public header files and other source files.
Add the new files to new list variables and as arguments to the cpfAddCppPackage()
call as shown below.
# file MyCPFProject/Sources/MyLib/CMakeLists.txt
...
set( PACKAGE_PUBLIC_FIXTURE_HEADER
Tests/function_fixture.h
)
set( PACKAGE_FIXTURE_FILES
Tests/function_fixture.cpp
)
...
cpfAddCppPackage(
PACKAGE_NAMESPACE ml
TYPE LIB
BRIEF_DESCRIPTION ${briefDescription}
LONG_DESCRIPTION ${longDescription}
PUBLIC_HEADER ${PACKAGE_PUBLIC_HEADERS}
PRODUCTION_FILES ${PACKAGE_PRODUCTION_FILES}
TEST_FILES ${PACKAGE_TEST_FILES}
PUBLIC_FIXTURE_HEADER ${PACKAGE_PUBLIC_FIXTURE_HEADER}
FIXTURE_FILES ${PACKAGE_FIXTURE_FILES}
LINKED_LIBRARIES ${PACKAGE_LINKED_LIBRARIES}
LINKED_TEST_LIBRARIES ${PACKAGE_LINKED_TEST_LIBRARIES}
)
Sources/MyLib/Tests/MyLib_tests_main.cpp¶
Use the new function in the test code. The fixture library is called <package>_fixtures
and is automatically linked to
the test executable. If you need it in the tests of another package you have to add it to that packages PACKAGE_LINKED_TEST_LIBRARIES
variable.
// file MyCPFProject/Sources/MyLib/Tests/MyLib_tests_main.cpp
#include <iostream>
#include <MyLib/function.h>
#include <MyLib/Tests/function_fixture.h>
int main(int, char**)
{
std::cout << "Run tests for MyLib" << std::endl;
std::cout << std::endl;
ml::prepareFunctionTest();
auto result = ml::function();
if(result)
{
return 0;
}
else
{
return 1;
}
}
Compile and run the fixture code¶
You can now compile and run your tests by calling
> 3_Make.py --target runAllTests
...
Run tests for MyLib
Do reusable test preparations here
MyLib (version 0.0.0.2-6f37-dirty) greets the world!
...
10. Package the build results of MyLib¶
One part of a CI pipeline is to create some sort of package that can be downloaded by the users of the software.
For applications this is usually an installer which can be arbitrarily complex. For libraries however,
this often is just a ZIP archive that either holds the complete source code or the compiled artifacts and public headers.
In the CPF nomenclature we call these package files package archives in order to distinguish them from the
CPF code packages in the Sources
directory.
The CPF has enough information about your package to create the simple compressed archive packages for you.
To enable creating package archives that contain source files and binary files you have to add two more
argument to the cpfAddCppPackage()
function.
Sources/MyLib/CMakeLists.txt¶
# file MyCPFProject/Sources/MyLib/CMakeLists.txt
...
set( developerBinaryPackageOptions
PACKAGE_ARCHIVE_CONTENT_TYPE CT_DEVELOPER
PACKAGE_ARCHIVE_FORMATS 7Z ZIP
)
set( sourcePackageOptions
PACKAGE_ARCHIVE_CONTENT_TYPE CT_SOURCES
PACKAGE_ARCHIVE_FORMATS TGZ
)
...
cpfAddCppPackage(
PACKAGE_NAMESPACE ml
TYPE LIB
BRIEF_DESCRIPTION ${briefDescription}
LONG_DESCRIPTION ${longDescription}
PUBLIC_HEADER ${PACKAGE_PUBLIC_HEADERS}
PRODUCTION_FILES ${PACKAGE_PRODUCTION_FILES}
TEST_FILES ${PACKAGE_TEST_FILES}
PUBLIC_FIXTURE_HEADER ${PACKAGE_PUBLIC_FIXTURE_HEADER}
FIXTURE_FILES ${PACKAGE_FIXTURE_FILES}
LINKED_LIBRARIES ${PACKAGE_LINKED_LIBRARIES}
LINKED_TEST_LIBRARIES ${PACKAGE_LINKED_TEST_LIBRARIES}
PACKAGE_ARCHIVES ${developerBinaryPackageOptions}
PACKAGE_ARCHIVES ${sourcePackageOptions}
)
Note that due to the complexity of the option the
PACKAGE_ARCHIVES
argument requires a list of nested key-word arguments.The options in this example will cause the creation of a developer binary package in the
.7z
and.zip
format. Developer binary means, that the package will contain the compiled binaries and public headers. We also set options for creating a.tar.gz
archive that contains the packages sources.For more information about creating other kinds of package archives read: DistributionPackages
Create the package archive¶
In order to create the specified packages, run
> 3_Make.py --target packageArchives
You should now have a directory MyCPFProject/Generated/VS/html/Downloads/MyLib/LastBuild
with the three archives
MyLib.\<version\>.Windows.src.tar.gz
MyLib.\<version\>.Windows.dev.Debug.7z
MyLib.\<version\>.Windows.dev.Debug.zip
The packages are added to the html directory so they can be directly downloaded from the projects web-page.
11. Enable using pre-compiled header by adding the cotire module¶
This is an optional feature and you can skip this step if you do not want to use pre-compiled header.
Cotire is a third party cmake module that implements the automatic use of pre-compiled headers. You can use this in your CPF project to speed up your builds. First you need to add cotire as an external package by executing
Sources> git submodule add https://github.com/Knitschi/cotire.git
Note that we actually use a fork of cotire that was changed to better integrate with the CPF.
We now add cotire to our external packages in the packages.cmake
file.
# file MyCPFProject/Sources/packages.cmake
set( CPF_PACKAGES
EXTERNAL cotire
EXTERNAL CPFCMake
EXTERNAL CPFBuildscripts
OWNED MyLib
OWNED MyApp
)
As a last step we enable the use of cotire in our configuration.
> 1_Configure.py VS --inherits VS2017-shared -D CPF_ENABLE_PRECOMPILED_HEADER=ON
> 2_Generate.py
At this point we will not benefit much from using pre-compiled headers. Cotire will only add headers from external dependencies to the pre-compiled header and we only use iostream here. The speed-up that can be gained with this will show when the project includes many large headers.
Cotire will also add a compiler option for including the generated prefix header which
is not available to clients of our libraries. Therefore you should make sure that your headers
include everything they need and do not rely on the inclusion of the prefix header.
To enforce that you can add a CI configuration that uses CPF_ENABLE_PRECOMPILED_HEADER=OFF
.
This configuration will fail to build when forget to add includes that are provided
by the prefix header.
12. Add a Doxygen package to generate the documentation of your CI-project¶
This is an optional feature and you can skip this step if you do not want to use Doxygen as a documentation generator.
In order to use this feature you have to download and install Doxygen on your developer machine and add it to
the PATH
so you can run it from the command line.
The CPF provides you with the cpfAddDoxygenPackage function that can be used to add a custom target that runs <a href=”http://www.doxygen.nl/index.html”>Doxygen</a> on your CI-project. Doxygen parses your source files for special comments and generates an html documentation from it.
We now add our own package that holds files for our global documentation. This package contains the doxygen configuration files and files that contain global documentation that does not really belong to any of your packages.
Now add the following files.
Sources
│
└───documentation
CMakeLists.txt
MyProject.dox
It is not required to use documentation
as the name of the package.
The name of the package directory is also the name of the target that
is later build to generate the documentation.
CMakeLists.txt¶
In this CMakeLists.txt
file we add a custom-target package instead of
the C++ packages that we added in the previous steps.
# file MyCPFProject/Sources/documentation/CMakeLists.txt
include(cpfInitPackageProject)
include(cpfAddDoxygenPackage)
cpfInitPackageProject()
set( sources
MyProject.dox
)
cpfAddDoxygenPackage(
DOXYGEN_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/DoxygenConfig.txt"
DOXYGEN_LAYOUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml"
DOXYGEN_STYLESHEET_FILE "${CMAKE_CURRENT_SOURCE_DIR}/DoxygenStylesheet.css"
HTML_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/header.html"
HTML_FOOTER "${CMAKE_CURRENT_SOURCE_DIR}/footer.html"
SOURCES ${sources}
)
MyProject.dox¶
This is an example of a file that contains global documentation.
// file MyCPFProject/Sources/documentation/MyProject.dox
/**
\mainpage Overview
# MyProject #
bli bla blub
*/
We use doxygen to create the default versions of the config files DoxygenConfig.txt
, DoxygenLayout.xml
, DoxygenStylesheet.css
header.html
and footer.html
.
Sources/documentation>doxygen -g DoxygenConfig.txt
Sources/documentation>doxygen -l DoxygenLayout.xml
Sources/documentation>doxygen -w html header.html footer.html DoxygenStylesheet.css
You can change these files to customize the content and looks of the generated html pages.
For further information refer to the Doxygen documentation.
Finally we add the package to the repository and the packages.cmake
file.
> git add Sources/documentation
> git commit . -m"Adds the project doxygen documentation"
packages.cmake¶
# file MyCPFProject/Sources/packages.cmake
set( CPF_PACKAGES
EXTERNAL cotire
EXTERNAL CPFCMake
EXTERNAL CPFBuildscripts
OWNED MyLib
OWNED MyApp
OWNED documentation
)
To harvest the fruits of your hard labor run
> 3_Make.py --target documentation
You can now open Generated/VS/html/doxygen/html/index.html
in your browser to take a look at the generated html-page.
There is not much to see here, because we have not added much content yet.
The CPF can generate a standard doxygen documentation for your C++ packages that contains the descriptions from
the CMakeLists.txt
files. To activate this feature we add the GENERATE_PACKAGE_DOX_FILES
options to the
cpfAddCppPackage calls in Sources/MyLib/CMakeLists.txt
and Sources/MyApp/CMakeLists.txt
.
...
cpfAddCppPackage(
...
GENERATE_PACKAGE_DOX_FILES
)
You can now re-build your documentation target and the html-page should contain Modules sub-pages
for the MyLib
and MyApp
packages that contain the description strings and some links to other
generated content.
For more details see: Documentation generation
CPFProjectSetupSummary Summary¶
You now know the basics about setting up a CPF project. If you still have open questions, feel free to add an issue on github with your question.