Packaging a C++ package#
This tutorial will guide you though making a C++ package with rattler-build
.
Building a Header-only Library#
To build a package for the header-only library xtensor
, you need to manage dependencies and ensure proper installation paths.
Key Steps#
-
Dependencies: Ensure
cmake
,ninja
, and acompiler
are available as dependencies. -
CMake Installation Prefix: Use the
CMAKE_INSTALL_PREFIX
setting to instructCMake
to install the headers in the correct location. -
Unix Systems: Follow the standard Unix prefix:
-
Windows Systems: Use a Unix-like prefix but nested in a
Utilize the handy variablesLibrary
directory:%LIBRARY_PREFIX%
and%LIBRARY_BIN%
to guideCMake
to install the headers and libraries correctly.
This approach ensures that the headers and libraries are installed in the correct directories on both Unix and Windows systems.
Recipe#
context:
version: "0.24.6"
package:
name: xtensor
version: ${{ version }}
source:
url: https://github.com/xtensor-stack/xtensor/archive/${{ version }}.tar.gz
sha256: f87259b51aabafdd1183947747edfff4cff75d55375334f2e81cee6dc68ef655
build:
number: 0
script:
- if: win # (1)!
then: |
cmake -GNinja ^
-D BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=%LIBRARY_PREFIX% ^
%SRC_DIR%
ninja install
else: |
cmake -GNinja \
-DBUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=$PREFIX \
$SRC_DIR
ninja install
requirements:
build:
- ${{ compiler('cxx') }} # (2)!
- cmake
- ninja
host:
- xtl >=0.7,<0.8
run:
- xtl >=0.7,<0.8
run_constraints: # (3)!
- xsimd >=8.0.3,<10
tests:
- package_contents:
include: # (4)!
- xtensor/xarray.hpp
files: # (5)!
- ${{ "Library" if win }}/share/cmake/xtensor/xtensorConfig.cmake
- ${{ "Library" if win }}/share/cmake/xtensor/xtensorConfigVersion.cmake
about:
homepage: https://github.com/xtensor-stack/xtensor
license: BSD-3-Clause
license_file: LICENSE
summary: The C++ tensor algebra library
description: Multi dimensional arrays with broadcasting and lazy computing
documentation: https://xtensor.readthedocs.io
repository: https://github.com/xtensor-stack/xtensor
extra:
recipe-maintainers:
- some-maintainer
- The
if:
condition allows the user to switch behavior of the build based on some checks like, the operating system. - The
compiler
function is used to get the C++ compiler for the build system. - The
run_constraints
section specifies the version range of a package which the package can run "with". But which the package doesn't depend on itself. - The
include
section specifies the header file to tested for existence. - The
files
section specifies the files to be tested for existence, using a glob pattern.
CMAKE_ARGS
It can be tedious to remember all the different variables one needs to pass to CMake to create the perfect build.
The cmake
package on conda-forge introduces theCMAKE_ARGS
environment variable.
This variable contains the necessary flags to make the package build correctly, also when cross-compiling from one machine to another.
Therefore, it is often not necessary to pass any additional flags to the cmake
command.
However, because this is a tutorial we will show how to pass the necessary flags to cmake
manually.
For more information please refer to the conda-forge documentation.
Building A C++ application#
In this example, we'll build poppler
, a C++ application for manipulating PDF files from the command line.
The final package will install several tools into the bin/
folder.
We'll use external build scripts and run actual scripts in the test.
Key Steps#
-
Dependencies:
- Build Dependencies: These are necessary for the building process, including
cmake
,ninja
, andpkg-config
. - Host Dependencies: These are the libraries
poppler
links against, such ascairo
,fontconfig
,freetype
,glib
, and others.
- Build Dependencies: These are necessary for the building process, including
-
Compiler Setup: We use the
compiler
function to obtain the appropriate C and C++ compilers. -
Build Script: The
build.script
field points to an external script (poppler-build.sh
) which contains the build commands. -
Testing: Simple tests are included to verify that the installed tools (
pdfinfo
,pdfunite
,pdftocairo
) are working correctly by running them, and expecting an exit code0
.
Recipe#
context:
version: "24.01.0"
package:
name: poppler
version: ${{ version }}
source:
url: https://poppler.freedesktop.org/poppler-${{ version }}.tar.xz
sha256: c7def693a7a492830f49d497a80cc6b9c85cb57b15e9be2d2d615153b79cae08
build:
script: poppler-build.sh
requirements:
build:
- ${{ compiler('c') }} # (1)!
- ${{ compiler('cxx') }}
- pkg-config
- cmake
- ninja
host:
- cairo # (2)!
- fontconfig
- freetype
- glib
- libboost-headers
- libjpeg-turbo
- lcms2
- libiconv
- libpng
- libtiff
- openjpeg
- zlib
tests:
- script:
- pdfinfo -listenc # (3)!
- pdfunite --help
- pdftocairo --help
- The
compiler
jinja function to get the correct compiler for C and C++ on the build system. - These are all the dependencies that the library links against.
- The script test just executes some of the installed tools to check if they
are working. These can be as complex as you want. (
bash
orcmd.exe
)
External Build Script#
We've defined an external build script in the recipe. This will be searched next
to the recipe by the file name given, or the default name build.sh
on unix
or
build.bat
on windows are searched for.
#! /bin/bash
extra_cmake_args=(
-GNinja
-DCMAKE_INSTALL_LIBDIR=lib
-DENABLE_UNSTABLE_API_ABI_HEADERS=ON
-DENABLE_GPGME=OFF
-DENABLE_LIBCURL=OFF
-DENABLE_LIBOPENJPEG=openjpeg2
-DENABLE_QT6=OFF
-DENABLE_QT5=OFF
-DENABLE_NSS3=OFF
)
mkdir build && cd build
cmake ${CMAKE_ARGS} "${extra_cmake_args[@]}" \
-DCMAKE_PREFIX_PATH=$PREFIX \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
-DTIFF_INCLUDE_DIR=$PREFIX/include \
$SRC_DIR
ninja
# The `install` command will take care of copying the files to the right place
ninja install
Parsing the rattler-build build
Output#
When running the rattler-build
command, you might notice some interesting information in the output.
Our package will have some run
dependencies, even if we didn't specify any.
These come from the run-exports
of the packages listed in the host
section of the recipe.
This is indicated by "RE of [host: package]"
in the output.
For example, libcurl
specifies that if you depend on it in the host section, you should also depend on it during runtime with specific version ranges.
This ensures proper linking to shared libraries.
Run dependencies:
╭───────────────────────┬──────────────────────────────────────────────╮
│ Name ┆ Spec │
╞═══════════════════════╪══════════════════════════════════════════════╡
│ libcurl ┆ >=8.5.0,<9.0a0 (RE of [host: libcurl]) │
│ fontconfig ┆ >=2.14.2,<3.0a0 (RE of [host: fontconfig]) │
│ fonts-conda-ecosystem ┆ (RE of [host: fontconfig]) │
│ lcms2 ┆ >=2.16,<3.0a0 (RE of [host: lcms2]) │
│ gettext ┆ >=0.21.1,<1.0a0 (RE of [host: gettext]) │
│ freetype ┆ >=2.12.1,<3.0a0 (RE of [host: freetype]) │
│ openjpeg ┆ >=2.5.0,<3.0a0 (RE of [host: openjpeg]) │
│ libiconv ┆ >=1.17,<2.0a0 (RE of [host: libiconv]) │
│ cairo ┆ >=1.18.0,<2.0a0 (RE of [host: cairo]) │
│ libpng ┆ >=1.6.42,<1.7.0a0 (RE of [host: libpng]) │
│ libzlib ┆ >=1.2.13,<1.3.0a0 (RE of [host: zlib]) │
│ libtiff ┆ >=4.6.0,<4.7.0a0 (RE of [host: libtiff]) │
│ libjpeg-turbo ┆ >=3.0.0,<4.0a0 (RE of [host: libjpeg-turbo]) │
│ libglib ┆ >=2.78.3,<3.0a0 (RE of [host: glib]) │
│ libcxx ┆ >=16 (RE of [build: clangxx_osx-arm64]) │
╰───────────────────────┴──────────────────────────────────────────────╯
You can also see "linking" information in the output, for example on macOS:
[lib/libpoppler-glib.8.26.0.dylib] links against:
├─ @rpath/libgio-2.0.0.dylib
├─ @rpath/libgobject-2.0.0.dylib
├─ /usr/lib/libSystem.B.dylib
├─ @rpath/libglib-2.0.0.dylib
├─ @rpath/libpoppler.133.dylib
├─ @rpath/libfreetype.6.dylib
├─ @rpath/libc++.1.dylib
├─ @rpath/libpoppler-glib.8.dylib
└─ @rpath/libcairo.2.dylib
rattler-build
ensures that:
- All shared libraries linked against are present in the run dependencies.
Missing libraries trigger an
overlinking
warning. - You don't require any packages in the host that you are not linking against.
This triggers an
overdepending
warning.