Wong's Cafe
2024-12-23
FreeBSD has a very sophisticated infrastructure built around compiling ports from source, which inspired Gentoo' portage[1]. But unlike Gentoo, it's configuration management is mostly still manual (you are recommended to use the manual TUI config). In this article I will record how I set up and manage my poudriere build system, and use it as a backend(?) for my package manager.
As a long time Gentoo user, I found the FreeBSD way of configuring packages kind of overly complex, and there's no userland software that let you quickly query packages and options, like Gentoo's euse
. So I have developed a compromise solution, that is configure selected packages manually, and use global options to set options for all dependencies.
The following snippet is my current (2024-12-23) configuration for optimizing speed and minimizing unused dependencies for a headless server.
# /etc/make.conf
#
# Global options
#
# Headless server settings
OPTIONS_UNSET+=X11 WAYLAND DBUS XCB LIBWACOM
# Performance
OPTIONS_SET+=OPTIMIZED_CFLAGS AVX AVX2 AVX512 LTO
OPTIONS_SET+=XXHASH
GOAMD64=v4
The variables are documented in /usr/ports/Mk/bsd.options.mk
Install and setup poudriere according to the official website and the manual page (It's really helpful), then we can configure poudriere
make.conf
in jailsSince poudriere use jails, it makes more sense to use the poudriere wrapper of make config
, namely poudriere-options
to manage them.
To use /etc/make.conf
as the single source of truth, link it to /usr/local/etc/poudriere.d/make.conf
. Poudriere will then use that file for jails.
After adding options in make.conf
, if you have configured your options by hand , make sure to run poudriere options -r
to clean up the existing config. Since if you don't use force, it will only be applied to new packages.
This one is simple, follow the official documentation:
poudriere jail -c -j default -v 14.2-RELEASE -a amd64
This sets up a jail with version 14.2-RELEASE
, if switching version, I prefer yanking it and making a new one, which can be optimized with ccache
FreeBSD Port have mainly two branches: quarterly and latest (git main). Latest is much easier to maintain and upgrade (just git pull), and quarterly is easier to build (less updates) but harder to maintain (you need to manually switch branches, but there's a bug report to fix that [2]). Currently I have chosen latest, not sure if it'll backfire though.
Currently I only need one port collection for my headless server, so to keep on single source of truth, I use local clone of /usr/ports
, and configure poudriere to create a local port tree with it:
# man poudriere-ports
poudriere ports -c -p local -m null -M /usr/ports
poudriere ports -l
yields:
PORTSTREE METHOD TIMESTAMP PATH
local null 2024-11-17 13:22:24 /usr/ports
I update it with a script:
#!/bin/sh
# To update the ports repo and update the jail with binary pkg
JAIL=default
git -C /usr/ports pull --rebase
git -C /usr/ports gc --auto
git -C /usr/ports pull --depth=3 # Trim the log
poudriere jail -u -j $JAIL
The resulting binary performance and building time for poudriere can be optimized in several ways:
Poudriere is conservative about dependency updates, which means sometimes a dependency update will trigger update of the reverse dependency i.e. the package that depends on it[3]. On Gentoo, reverse dependency is not rebuilt by default, but you can do it manually with revdep-rebuild
.
Since the package itself is not changed, we can use ccache
to skip these compilations:
# /etc/make.conf
# CCACHE
WITH_CCACHE_BUILD=yes
# /usr/local/etc/poudriere.conf
# Un-comment line 170:
# ccache support. Supply the path to your ccache cache directory.
# It will be mounted into the jail and be shared among all jails.
# It is recommended that extra ccache configuration be done with
# ccache -o rather than from the environment.
CCACHE_DIR=/var/cache/ccache
Some other optimizations for poudriere, customized through /usr/local/etc/poudriere.conf
, they are clearly documented in /usr/local/etc/poudriere.conf.sample
# Use tmpfs(5)
# This can be a space-separated list of options:
# wrkdir - Use tmpfs(5) for port building WRKDIRPREFIX
# data - Use tmpfs(5) for poudriere cache/temp build data
# localbase - Use tmpfs(5) for LOCALBASE (installing ports for packaging/testing)
# all - Run the entire build in memory, including builder jails.
# yes - Enables tmpfs(5) for wrkdir and data
# no - Disable use of tmpfs(5)
# EXAMPLE: USE_TMPFS="wrkdir data"
USE_TMPFS=yes
don't use tmpfs for rust:
# List of package globs that are not allowed to use tmpfs for their WRKDIR
# Note that you *must* set TMPFS_BLACKLIST_TMPDIR
# EXAMPLE: TMPFS_BLACKLIST="rust"
TMPFS_BLACKLIST="rust"
On /usr/local/etc/poudriere.conf
ALLOW_MAKEJOBS=yes
PARALLEL_JOBS=2
and on /etc/make.conf
, set MAKE_JOBS_NUMBER=8
, this yields 2x8 = 16
threads which matches my 16 CPU threads
There are several packages that just takes way too much time and power to build (cough cough lang/rust
), since running an optimized compiler won't outweigh the heavy compile time it costs (especially with lto
), we can fetch binary packages for them instead.
On Gentoo, this is handled really nicely with slot system, --getbinpkg
and -rust
suffix with virtual/
category (Gentoo's package manager is definitely superior, there's no doubt about that).
We can have kind of the same thing for FreeBSD by using PACKAGE_FETCH_WHITELIST
by simply edit the last line in /usr/local/etc/poudriere.conf
:
PACKAGE_FETCH_WHITELIST="gcc* rust llvm* node*"
This make poudriere try to use binary if the options match, to make sure of that, I have lang_rust_UNSET+=LTO
set in /etc/make.conf
also, you can append -v
flag when running poudriere bulk
to see why it's rejected