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:
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
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_ROOTto the copy bundled with Visual Studio, which is manifest-only and answersvcpkg install <port>with "does not have a classic mode instance". Clonemicrosoft/vcpkg, run itsbootstrap-vcpkg.bat, and pointVCPKG_ROOTthere. (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-staticis 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, runvcpkg remove dopplerfirst 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 unrelateddopplerpackage (a secrets-manager CLI) is distributed from its vendor's own repository. The library itself is stilllibdoppler.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_28image as the tarballs, and CI installs them on AlmaLinux 8, the floor, on every pull request. The x86_64 build targetsx86-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.53for 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:
Fedora / RHEL:
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
-devpackage. The CMake config lives inlibdoppler-dsp-dev; with only the runtime package installed, CMake does not find doppler. project()enables C beforefind_package. CMake searches/usr/lib/<triplet>/cmakeonly once it knows the compiler's triplet, soproject(x NONE), or afind_package(doppler)placed aboveproject(), 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:
From a source build — only if you already
built from source (a build/ tree exists):
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:
Or install the .rpm packages below, which need neither.
Either way, verify the install is visible to your toolchain:
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
Link from the build tree (no install)¶
Useful during development — no cmake --install required.
Shared library:
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):
CMake — add_subdirectory:
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:
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.