Skip to content

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#

  1. Dependencies: Ensure cmake, ninja, and a compiler are available as dependencies.

  2. CMake Installation Prefix: Use the CMAKE_INSTALL_PREFIX setting to instruct CMake to install the headers in the correct location.

  3. Unix Systems: Follow the standard Unix prefix:

    $PREFIX/include
    $PREFIX/lib
    

  4. Windows Systems: Use a Unix-like prefix but nested in a Library directory:

    $PREFIX/Library/include
    $PREFIX/Library/lib
    
    Utilize the handy variables %LIBRARY_PREFIX% and %LIBRARY_BIN% to guide CMake 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#

recipe.yaml
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
  1. The if: condition allows the user to switch behavior of the build based on some checks like, the operating system.
  2. The compiler function is used to get the C++ compiler for the build system.
  3. 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.
  4. The include section specifies the header file to tested for existence.
  5. 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#

  1. Dependencies:

    • Build Dependencies: These are necessary for the building process, including cmake, ninja, and pkg-config.
    • Host Dependencies: These are the libraries poppler links against, such as cairo, fontconfig, freetype, glib, and others.
  2. Compiler Setup: We use the compiler function to obtain the appropriate C and C++ compilers.

  3. Build Script: The build.script field points to an external script (poppler-build.sh) which contains the build commands.

  4. Testing: Simple tests are included to verify that the installed tools (pdfinfo, pdfunite, pdftocairo) are working correctly by running them, and expecting an exit code 0.

Recipe#

recipe.yaml
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
  1. The compiler jinja function to get the correct compiler for C and C++ on the build system.
  2. These are all the dependencies that the library links against.
  3. 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 or cmd.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.

poppler-build.sh
#! /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:

  1. All shared libraries linked against are present in the run dependencies. Missing libraries trigger an overlinking warning.
  2. You don't require any packages in the host that you are not linking against. This triggers an overdepending warning.