Tired of writing hundreds of lines of CMake boilerplate just to create a properly installable library? Build sleek, professional C++ libraries and executables with a few simple function calls. Installation and package config generation all handled automatically.
Important
PackageBuilder assumes a canonical CMake project structure for its default behavior.
- CMake 3.24 or higher
If your project uses cmake-conan (the tnt-coders fork), this is the recommended way to consume PackageBuilder. Dependencies are resolved automatically with no manual installation.
Add the following to your conanfile.py:
def requirements(self):
self.requires("cmake-package-builder/1.0.0") #recipe: https://github.com/tnt-coders/cmake-package-builder.gitOr in conanfile.txt:
[requires]
cmake-package-builder/1.0.0 #recipe: https://github.com/tnt-coders/cmake-package-builder.git
[generators]
CMakeDeps
CMakeToolchainThe #recipe: tag tells cmake-conan where to find the source for the package. If the package is not already present in your local Conan cache or any configured remote, cmake-conan will automatically clone the tagged version from the provided git URL and run conan create to build it locally. No manual recipe hosting or remote setup needed.
Then in your CMakeLists.txt:
find_package(PackageBuilder REQUIRED)
include(PackageBuilder)For full details on how cmake-conan and the recipe tag work, see the tnt-coders fork of the cmake-conan repository.
The simplest way to get started. No installation or package manager required.
cmake_minimum_required(VERSION 3.24)
project(MyProject LANGUAGES CXX)
include(FetchContent)
FetchContent_Declare(
PackageBuilder
GIT_REPOSITORY https://github.com/tnt-coders/cmake-package-builder
GIT_TAG v1.0.0
)
FetchContent_MakeAvailable(PackageBuilder)
include(PackageBuilder)This allows you to immediately hit the ground running with professional quality install/package creation logic for your own project.
Build and install PackageBuilder to your system, then find it like any other CMake package.
Install:
git clone https://github.com/tnt-coders/cmake-package-builder
cd cmake-package-builder
cmake -B build
cmake --install build
Consume:
cmake_minimum_required(VERSION 3.24)
project(MyProject LANGUAGES CXX)
find_package(PackageBuilder REQUIRED)
include(PackageBuilder)Required. Must be called before any other PackageBuilder functions.
package_create()This function performs essential validation before any targets are defined:
- Version verification: Verifies that
PROJECT_VERSIONis set and fails with a fatal error if it is not.PROJECT_VERSIONis typically set by specifying the version in theproject()command. - Description verification: Verifies that
PROJECT_DESCRIPTIONis set and fails with a fatal error if it is not. Set via theDESCRIPTIONargument in theproject()command. - License verification: Verifies that a
LICENSEfile exists at the project root. This is required for CPack packaging.
# Create a library (automatically aliased as MyProject::mylib)
package_add_library(mylib src/mylib.cpp)
# Create an executable
package_add_executable(myapp src/main.cpp)
# Create an executable with packaging metadata
package_add_executable(myapp src/main.cpp
ICON resources/myapp.ico
LINUX_DESKTOP_FILE resources/myapp.desktop
LINUX_ICON_THEME_DIR resources/icons)
# Link them together using the namespaced alias
target_link_libraries(myapp PRIVATE MyProject::mylib)Libraries created with package_add_library are automatically given a namespaced alias of <PROJECT_NAME>::<target>. This is the same alias that downstream consumers will use after find_package().
Targets created with package_add_library and package_add_executable automatically receive
these private include directories:
${CMAKE_CURRENT_SOURCE_DIR}${CMAKE_CURRENT_SOURCE_DIR}/include${CMAKE_CURRENT_SOURCE_DIR}/src
For libraries, only ${CMAKE_CURRENT_SOURCE_DIR}/include is exposed publicly. The project root is
a build-time convenience only and is not exported to consumers.
package_add_executable accepts optional packaging arguments:
| Argument | Description |
|---|---|
ICON |
Path to an icon file (.ico). On Windows, package_add_executable() embeds this into the executable and also uses it as the NSIS installer/uninstaller icon. Paths may be relative to the calling CMakeLists.txt. |
LINUX_DESKTOP_FILE |
Path to an XDG .desktop file. Installed to share/applications on Linux for desktop launcher integration. Paths may be relative to the calling CMakeLists.txt. |
LINUX_ICON_THEME_DIR |
Path to a directory containing a Linux icon theme subtree such as hicolor/256x256/apps/myapp.png. Installed to share/icons on Linux. Paths may be relative to the calling CMakeLists.txt. |
All other arguments after the target name are forwarded to add_executable() as source files.
If your target is created outside PackageBuilder, such as by add_executable or another CMake
helper, register it for install, export, and CPack handling with package_register_target:
add_executable(my_app main.cpp)
target_include_directories(my_app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
package_register_target(my_app
ICON resources/my_app.ico
LINUX_DESKTOP_FILE resources/my_app.desktop
LINUX_ICON_THEME_DIR resources/icons)For externally created libraries, use PUBLIC_HEADER_DIRS to tell PackageBuilder which public
header roots should be installed:
add_library(my_lib)
target_sources(my_lib PRIVATE src/my_lib.cpp)
target_include_directories(
my_lib
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
package_register_target(my_lib PUBLIC_HEADER_DIRS include)package_register_target accepts the same ICON, LINUX_DESKTOP_FILE, and
LINUX_ICON_THEME_DIR arguments as
package_add_executable. For externally created targets, ICON is packaging metadata only; it is
used for the NSIS installer/uninstaller icon on Windows, but it does not modify the executable's
own resources. package_register_target does not create the target, add sources, add include
directories, or create a namespaced build-tree alias. Those details remain the responsibility of
the project that created the target.
package_install()This single call handles everything:
- Installs all targets registered via
package_add_library,package_add_executable, andpackage_register_target - Installs public headers from
include/or anyPUBLIC_HEADER_DIRSprovided while registering external libraries - Installs any custom CMake modules from
cmake/ - Generates and installs
<ProjectName>Config.cmakeand<ProjectName>ConfigVersion.cmake
Downstream projects can then consume your package:
find_package(MyProject REQUIRED)
target_link_libraries(myapp PRIVATE MyProject::mylib)package_install() configures CPack to generate platform-native installers:
| Platform | Generators |
|---|---|
| Windows | ZIP, NSIS |
| macOS | ZIP, DragNDrop |
| Linux | ZIP, DEB |
Multiple registered applications — including macOS app bundles created by external CMake helpers — are supported in a single package. Each registered runtime target gets its own runtime dependency set, so CPack can correctly resolve and bundle the dependencies for every app.
PackageBuilder assigns every install artifact to one of two CPack components:
| Component | Contents |
|---|---|
Runtime |
Executables, macOS app bundles, shared libraries, and their runtime dependencies |
Development |
Static libraries, public headers, CMake package metadata (exported targets, config/version files, custom modules) |
By default, generated installers include only the Runtime component: executables, app bundles, and shared libraries. Development-facing artifacts such as static libraries, public headers, exported CMake target definitions, and package config files are excluded because Conan is the preferred method for consuming packages as libraries during development.
By default, every registered executable gets a desktop shortcut (Windows) or an XDG .desktop
launcher entry (Linux). To limit this to a specific subset, set PACKAGE_BUILDER_DESKTOP_LINKS to
the list of target names that should receive shortcuts before calling package_install():
# Only my_app gets a Start Menu / desktop shortcut
set(PACKAGE_BUILDER_DESKTOP_LINKS my_app)
package_install()| Platform | Behavior |
|---|---|
| Windows | See Windows one-click installer below for desktop shortcut behavior. The NSIS installer icon is taken from the first registered executable that provides ICON. |
| Linux | Installs the .desktop file provided via LINUX_DESKTOP_FILE to share/applications, and installs the icon theme tree provided via LINUX_ICON_THEME_DIR to share/icons. If LINUX_DESKTOP_FILE is omitted, PackageBuilder falls back to looking for a <target>.desktop file in the same directory as the target's CMakeLists.txt. |
| macOS | No action needed — DragNDrop bundles are self-contained and Launchpad discovers them automatically when dragged to /Applications. |
By default (PACKAGE_BUILDER_WINDOWS_ONE_CLICK_INSTALLER ON), the generated NSIS installer is
zero-interaction: the user double-clicks the installer, it installs the application, creates a
desktop shortcut, and closes automatically. No wizard pages, no "Next" buttons, no path or
directory choices.
When set to OFF, the installer reverts to the classic NSIS wizard with Welcome, License,
Directory, Start Menu, and Install pages, plus an opt-in desktop shortcut checkbox.
# Default: zero-interaction installer (user just double-clicks)
# set(PACKAGE_BUILDER_WINDOWS_ONE_CLICK_INSTALLER ON)
# Classic wizard with pages
set(PACKAGE_BUILDER_WINDOWS_ONE_CLICK_INSTALLER OFF)
package_install()| Mode | Desktop shortcut | PATH modification | Wizard pages |
|---|---|---|---|
ON (default) |
Always created unconditionally | Never added to PATH | None — installs silently |
OFF |
Opt-in checkbox during install | User-controlled via installer page | Welcome, License, Directory, Start Menu, Finish |
If your project needs a fully custom NSIS installer template, set PACKAGE_BUILDER_NSIS_TEMPLATE_DIR
to the directory containing your NSIS.template.in before calling package_install(). This
directory takes the highest priority over PackageBuilder's own template:
set(PACKAGE_BUILDER_NSIS_TEMPLATE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/packaging/nsis")
package_install()PackageBuilder's lookup order for NSIS.template.in (highest to lowest priority):
PACKAGE_BUILDER_NSIS_TEMPLATE_DIR— caller-provided override- PackageBuilder's bundled
cmake/NSIS/directory (one-click template) - CMake's built-in NSIS template (classic wizard)
If your project needs installers that also ship development files, set PACKAGE_BUILDER_CPACK_COMPONENTS before calling package_install():
set(PACKAGE_BUILDER_CPACK_COMPONENTS Runtime Development)
package_install()PackageBuilder is designed to work best with the following project layout:
MyProject/
|- CMakeLists.txt
|- cmake/ # Optional: custom CMake modules (auto-installed alongside config files)
|- include/ # Public headers (auto-installed to the system include directory)
`- src/ # Private source and header files
- Headers in
include/are exposed publicly to consumers of your library. - The project root is also added as a private include directory for targets created by
package_add_libraryandpackage_add_executable. - Files in
src/are private to your project and are not installed. - Any
.cmakefiles incmake/are installed alongside the generated package config, making them available to consumers viaCMAKE_MODULE_PATH.
Projects with non-canonical layouts can still use PackageBuilder by creating targets themselves and
then registering them with package_register_target(...).
cmake_minimum_required(VERSION 3.24)
project(MyAwesomeLib VERSION 1.0.0 DESCRIPTION "An awesome library" LANGUAGES CXX)
# Or use FetchContent if the package is not installed
find_package(PackageBuilder REQUIRED)
include(PackageBuilder)
# Create the package - validates version, description, and license
package_create()
# Create targets
package_add_library(awesome src/awesome.cpp)
package_add_executable(awesome_app src/main.cpp
ICON resources/awesome_app.ico
LINUX_DESKTOP_FILE resources/awesome_app.desktop
LINUX_ICON_THEME_DIR resources/icons)
target_link_libraries(awesome_app PRIVATE MyAwesomeLib::awesome)
# Give awesome_app a Start Menu / desktop shortcut (Windows), install its
# .desktop file to share/applications (Linux), and install its icon theme
# assets to share/icons (Linux)
set(PACKAGE_BUILDER_DESKTOP_LINKS awesome_app)
package_install()JUCE creates GUI app targets via juce_add_gui_app rather than
add_executable. Use package_register_target to hand the externally-created target off to
PackageBuilder:
cmake_minimum_required(VERSION 3.24)
project(MyApp VERSION 1.0.0 DESCRIPTION "A JUCE-based application" LANGUAGES CXX)
find_package(PackageBuilder REQUIRED)
include(PackageBuilder)
package_create()
# JUCE creates and configures the target internally
juce_add_gui_app(my_app
PRODUCT_NAME "My App"
VERSION ${PROJECT_VERSION}
ICON_BIG resources/my_app.ico
ICON_SMALL resources/my_app.ico)
target_sources(my_app PRIVATE main.cpp)
target_compile_definitions(my_app PRIVATE JUCE_WEB_BROWSER=0 JUCE_USE_CURL=0)
target_link_libraries(my_app PRIVATE juce::juce_gui_basics juce::juce_recommended_warning_flags)
# Register the JUCE target so PackageBuilder includes it in install/CPack output
package_register_target(my_app
ICON resources/my_app.ico
LINUX_DESKTOP_FILE resources/my_app.desktop
LINUX_ICON_THEME_DIR resources/icons)
# Give my_app a Start Menu / desktop shortcut (Windows), install its .desktop
# file to share/applications (Linux), and install its icon theme assets to
# share/icons (Linux)
set(PACKAGE_BUILDER_DESKTOP_LINKS my_app)
package_install()For Linux desktop icons, place PNG files under a standard icon theme layout rooted at the
directory passed to LINUX_ICON_THEME_DIR, for example:
resources/icons/
`- hicolor/
|- 32x32/apps/my_app.png
|- 48x48/apps/my_app.png
|- 64x64/apps/my_app.png
|- 128x128/apps/my_app.png
`- 256x256/apps/my_app.png
Then set Icon=my_app in your .desktop file, without a file extension.