Skip to content

C Library

Get the C library one of two ways — a pre-built release tarball (no build) or building from source — then choose an integration method below. Either way you get the headers plus a shared (libdoppler.so) and a self-contained static (libdoppler.a) library.

Get jbx

jbx is the just-buildit command-line tool that installs doppler's pre-built C library (jbx get-doppler) and a source build's system dependencies (jbx install-deps). If command -v jbx prints nothing, install it once:

. <(curl -sSL https://just-buildit.github.io/get-jb.sh)

It installs to $HOME/.local/bin; sourcing the script (the leading .) puts that on PATH for the current shell, and a new login shell picks it up from your profile. From a clone of doppler, make install-deps does the same bootstrap for you and then installs the system packages.

Install from a release tarball

Every GitHub release ships a pre-built C library for Linux (x86_64, aarch64) and macOS (arm64) — no toolchain or build step.

One-liner via jbx

jbx get-doppler # extracts to $HOME/.local/doppler

Resolves the latest release, downloads the platform-appropriate tarball, and extracts it — the manual steps below, in one command.

A previous install at the same prefix is moved aside first, restored automatically if the new one fails a sanity check, and restorable any time with --restore.

Needs jbx: Get jbx. Source: scripts/get-doppler.sh.

Or by hand. The version in the URL is the current release; change it to install another one from the releases page:

# Linux x86_64 — for Linux aarch64 swap the suffix for `linux-aarch64`,
# for macOS arm64 swap it for `macos-arm64`:
curl -L -o doppler.tar.gz \
  "https://github.com/doppler-dsp/doppler/releases/download/v0.81.0/doppler-0.81.0-linux-x86_64.tar.gz"
mkdir -p "$HOME/.local/doppler" && tar -xzf doppler.tar.gz -C "$HOME/.local/doppler"

The library is now in $HOME/.local/doppler. To build against it, tell CMake or pkg-config where it landed, then follow the find_package or pkg-config method below:

cmake -B build -DCMAKE_PREFIX_PATH="$HOME/.local/doppler"     # CMake: find_package(doppler)
export PKG_CONFIG_PATH="$HOME/.local/doppler/lib/pkgconfig"   # pkg-config: doppler

The core library is pure C and links -lm and -lpthread, both part of a POSIX toolchain, so there is no external runtime dependency to install. The optional stream component (libdoppler_stream, for the NATS wire layer) embeds the vendored nats.c statically, so it too needs no external runtime NATS client library — just a running nats-server to connect to. See the static vs. dynamic linking design notes for why static vendoring was chosen over a system client-library dependency.

Windows

Every release also carries doppler-<version>-windows-x86_64.zip, built with clang-cl against the MSVC runtime. Your own code must be compiled with clang-cl as well: doppler's headers use C99 _Complex, which MSVC's cl.exe does not implement, and they stop a cl.exe build with an #error saying so. From a Developer PowerShell for VS (x64), with the C++ Clang tools for Windows component installed:

$v = "0.81.0"   # the release to install
curl.exe -L -o doppler.zip `
  "https://github.com/doppler-dsp/doppler/releases/download/v$v/doppler-$v-windows-x86_64.zip"
Expand-Archive doppler.zip -DestinationPath "$HOME\doppler"
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang-cl `
  -DCMAKE_PREFIX_PATH="$HOME\doppler"

find_package(doppler) works exactly as below; pkg-config is not the Windows path. doppler::doppler links the DLL — put $HOME\doppler\bin on PATH, or copy doppler.dll beside your executable — and doppler::doppler-static links doppler_static.lib with no DLL at all. The NATS stream layer is doppler::stream (doppler_stream.dll) and doppler::stream-static (doppler_stream_static.lib); a static consumer also links ws2_32, which the static target carries for you.

With vcpkg

The repository carries its own vcpkg port and a clang-cl triplet, under packaging/vcpkg/. The port is an overlay: it builds the checkout it lives in, so you get the version you cloned, and it is not in the upstream microsoft/vcpkg registry — whose default x64-windows triplet compiles with cl.exe and therefore cannot build doppler.

$v = "0.81.0"   # the release to install
git clone --depth 1 --branch "v$v" https://github.com/doppler-dsp/doppler
vcpkg install doppler:x64-windows-clangcl `
  --overlay-ports=doppler/packaging/vcpkg/ports `
  --overlay-triplets=doppler/packaging/vcpkg/triplets
cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang-cl `
  -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake" `
  -DVCPKG_TARGET_TRIPLET=x64-windows-clangcl

vcpkg's toolchain finds the package and copies doppler.dll beside your executable, so there is no CMAKE_PREFIX_PATH and no PATH edit. vcpkg builds in a scrubbed environment and looks for clang-cl under %LLVMInstallDir%, then %ProgramFiles%\LLVM, then Visual Studio's Clang component. Three things to know:

  • Use a full vcpkg checkout, not Visual Studio's. A Developer PowerShell sets VCPKG_ROOT to the copy bundled with Visual Studio, which is manifest-only and answers vcpkg install <port> with "does not have a classic mode instance". Clone microsoft/vcpkg, run its bootstrap-vcpkg.bat, and point VCPKG_ROOT there. (doppler's own CI hit exactly this.)

  • Dynamic triplets only. The exported targets carry both linkages and refuse to load with one missing, so a static triplet (which must ship no DLL) is not expressible yet (#1405). doppler::doppler-static is still there to link against.

  • Rebuild after updating the clone. vcpkg's binary cache is keyed on the port files, not on the sources an overlay port points at — after a git pull, run vcpkg remove doppler first or pass --binarysource=clear.

The same port installs on Linux and macOS with the stock *-dynamic community triplets; CI exercises the Windows one on every pull request (make vcpkg-smoke).

System packages (.deb / .rpm)

Every release carries native packages for x86_64 and aarch64, attached to the GitHub Release. Download the ones for your format and install them with your package manager, which resolves the dependencies. The commands are for x86_64; on aarch64 swap the architecture in the file names (amd64 → arm64 for .deb, x86_64 → aarch64 for .rpm).

Debian / Ubuntu:

base=https://github.com/doppler-dsp/doppler/releases/download/v0.81.0
curl -fL -O "$base/libdoppler-dsp0.81_0.81.0_amd64.deb" -O "$base/libdoppler-dsp-dev_0.81.0_amd64.deb"
sudo apt install ./libdoppler-dsp0.81_0.81.0_amd64.deb ./libdoppler-dsp-dev_0.81.0_amd64.deb

Fedora / RHEL / AlmaLinux:

base=https://github.com/doppler-dsp/doppler/releases/download/v0.81.0
curl -fL -O "$base/libdoppler-dsp-0.81.0-1.x86_64.rpm" -O "$base/libdoppler-dsp-devel-0.81.0-1.x86_64.rpm"
sudo dnf install ./libdoppler-dsp-0.81.0-1.x86_64.rpm ./libdoppler-dsp-devel-0.81.0-1.x86_64.rpm
Debian / Ubuntu Fedora / RHEL
shared libraries libdoppler-dsp<X.Y> libdoppler-dsp
headers, .a, CMake + pkg-config files libdoppler-dsp-dev libdoppler-dsp-devel
the wfmgen CLI doppler-dsp-tools doppler-dsp-tools

After that find_package(doppler) and pkg-config doppler resolve with no path of yours on the command line. Three things to know:

  • The name is doppler-dsp, as on PyPI — an unrelated doppler package (a secrets-manager CLI) is distributed from its vendor's own repository. The library itself is still libdoppler.so.
  • One package per architecture, for every distro at or above glibc 2.28 (Debian 10, Ubuntu 20.04, RHEL 8): they are built in the same manylinux_2_28 image as the tarballs, and CI installs them on AlmaLinux 8, the floor, on every pull request. The x86_64 build targets x86-64-v2 (SSE4.2), which is above the oldest CPUs those distros themselves still run on.
  • The Debian runtime package carries the ABI version in its name (libdoppler-dsp0.53 for the 0.53 series), so two minors install side by side and a program built against one keeps running after you install the next.

Build something against it

A fresh machine also needs a toolchain to build your own code. One line:

Debian / Ubuntu:

sudo apt install cmake gcc make pkg-config

Fedora / RHEL:

sudo dnf install cmake gcc make pkgconf-pkg-config

Try it: the consumer example is the smallest project that links doppler. Its page has a one-line fetch of the folder, and with the packages installed you build and run it with the folder's own Makefile (its run goal), shared and static.

On Debian and Ubuntu, find_package(doppler) depends on three things, each of which fails to configure when it is missing (reproduced on Ubuntu 24.04 with the arm64 packages):

  • The -dev package. The CMake config lives in libdoppler-dsp-dev; with only the runtime package installed, CMake does not find doppler.
  • project() enables C before find_package. CMake searches /usr/lib/<triplet>/cmake only once it knows the compiler's triplet, so project(x NONE), or a find_package(doppler) placed above project(), does not find it.
  • A version the install satisfies. find_package(doppler X.Y.Z) is refused when the installed release is older than the one you ask for.

These are release assets, not an apt or yum repository: upgrading means downloading the next release's files.

System install

Install headers and libraries to a system prefix (default /usr/local) so find_package/pkg-config resolve with no per-project paths.

From a release tarball (no toolchain, no build) — point jbx get-doppler at the prefix. The sudo env "PATH=$PATH" form keeps jbx findable under sudo's restricted PATH:

sudo env "PATH=$PATH" jbx get-doppler --prefix /usr/local
sudo ldconfig

From a source build — only if you already built from source (a build/ tree exists):

cmake --install build
sudo ldconfig

Fedora, RHEL and AlmaLinux

/usr/local/lib is not on their loader path by default (Debian's and Ubuntu's is), so ldconfig alone does not make the library findable there. Add the directory the install used once (pkg-config --variable=libdir doppler prints it), then refresh the cache:

echo /usr/local/lib | sudo tee /etc/ld.so.conf.d/usr-local.conf
sudo ldconfig

Or install the .rpm packages below, which need neither.

Either way, verify the install is visible to your toolchain:

pkg-config --modversion doppler

CMake — find_package

The package provides two link targets — pick the one matching your linking policy:

find_package(doppler REQUIRED)

# shared: links -ldoppler; smallest binary
target_link_libraries(my_app PRIVATE doppler::doppler)

# static: pure C, self-contained — links -lm and -lpthread
target_link_libraries(my_app PRIVATE doppler::doppler-static)

# optional: the NATS stream layer (dp_pub_*/dp_sub_*, wfmgen --output nats://).
# Pure C (vendored nats.c, folded in). Link only if needed.
target_link_libraries(my_app PRIVATE doppler::stream-static)

A complete, buildable consumer that exercises both targets lives in example-projects/consumer/.

pkg-config

# shared
gcc -o app main.c $(pkg-config --cflags --libs doppler)

# static — pure C, self-contained: -lm and -lpthread
gcc -o app main.c $(pkg-config --cflags doppler) \
    "$(pkg-config --variable=libdir doppler)/libdoppler.a" -lm -lpthread

# the optional NATS stream layer has its own module — one name gives
# the whole line (shared; Requires: doppler)
gcc -o app main.c $(pkg-config --cflags --libs doppler_stream) -lm

# static + stream: name the archives explicitly
gcc -o app main.c $(pkg-config --cflags doppler) \
    "$(pkg-config --variable=libdir doppler)/libdoppler_stream.a" \
    "$(pkg-config --variable=libdir doppler)/libdoppler.a" \
    -lpthread -lm

pkg-config --cflags doppler includes a feature-test macro on its own: -D_GNU_SOURCE on Linux (-D_DARWIN_C_SOURCE on macOS). That is deliberate. The installed headers call syscall() and ftruncate() and name MAP_ANONYMOUS, which a strict -std=c99 hides on older glibc unless the macro is defined before libc is first included; a #define inside a header is already too late, so only the compile line can carry it. The CMake targets carry the same definition, so every route compiles the same headers.

These recipes assume the library is on the loader path, which it is after an apt or dnf install. From a prefix that is not (the release tarball, jbx get-doppler, ~/.local), also say where it is, once, on the link line:

gcc -o app main.c $(pkg-config --cflags --libs doppler) \
    -Wl,-rpath,"$(pkg-config --variable=libdir doppler)"

The .pc does not add that for you: an rpath written there would be invisible to you, and wrong for a cross build. If you move the binary off the machine, or delete the prefix, set LD_LIBRARY_PATH or link statically.

Custom install prefix

cmake --install build --prefix ~/.local
export PKG_CONFIG_PATH=~/.local/lib/pkgconfig

Useful during development — no cmake --install required.

Shared library:

gcc -o app main.c \
    -Inative/inc \
    -Lbuild -ldoppler \
    -Wl,-rpath,"$(pwd)/build"

rpath on Linux

The -Wl,-rpath flag bakes the build path into the binary. If you move the binary or the build tree, set LD_LIBRARY_PATH or re-link.

Static library (no runtime .so dependency):

gcc -o app main.c \
    -Inative/inc \
    build/libdoppler.a \
    -lm

CMake — add_subdirectory:

add_subdirectory(path/to/doppler)
target_link_libraries(my_app PRIVATE doppler::doppler)

Embedding the wfmgen generator

The wfmgen composer CLI is archived into the library as a plain callable, so a C program can drive the full generator in-process — same flags, same output as the command line — without spawning a subprocess. Include doppler/wfm/wfmgen.h and call dp_doppler_wfmgen(argc, argv):

#include <stddef.h>
#include "doppler/wfm/wfmgen.h"

int main(void)
{
    /* identical to: wfmgen --type qpsk --count 4096 --output out.cf32 */
    char *av[] = { "wfmgen", "--type", "qpsk", "--count", "4096",
                   "--output", "out.cf32", NULL };
    return dp_doppler_wfmgen(7, av);   /* 0 on success; writes out.cf32 */
}

It is the exact code path the wfmgen binary runs (which is itself a one-line main shim over dp_doppler_wfmgen), so the output is byte-identical. wfmgen lives in the pure-C core, so the file/raw/csv/BLUE/SigMF output paths link with just libdoppler.a -lm -lpthread:

gcc -o app app.c -I "$PREFIX/include" "$PREFIX/lib/libdoppler.a" \
    -lm -lpthread

The --output nats://… PUB-sink path additionally needs the optional stream component (it carries the vendored nats.c, pure C). Link libdoppler_stream.a alongside the core, whole: the core ships weak no-op stubs of the sink, and a plain archive link keeps them, because nothing pulls the real definitions out of the archive. Wrap it in -Wl,--whole-archive (-Wl,-force_load,<path> on macOS), or link the shared libdoppler_stream instead. nats.c is statically embedded, so there is still no runtime client-library dependency — just a running nats-server to connect to:

gcc -o app app.c -I "$PREFIX/include" \
    "$PREFIX/lib/libdoppler.a" \
    -Wl,--whole-archive "$PREFIX/lib/libdoppler_stream.a" \
    -Wl,--no-whole-archive -lpthread -lm

Without the stream component, dp_doppler_wfmgen() still builds and runs; only the nats:// path is unavailable (it reports a clear "requires the stream component" error via the weak dp_wfm_stream_sink_* seam).

The shared library is even simpler — -ldoppler alone is sufficient.

The NATS output

dp_doppler_wfmgen is the same generator as the wfmgen binary. Its --output nats:// needs the stream sink from doppler::stream: without it, that one output reports that the sink is not linked, and every other output works from the core library alone.