My Alternative Package Tool and Ports System for FreeBSD

This is a new package tool and ports system I’m working on. I started building it because I needed a specific set of packages that aren’t available in the FreeBSD ports tree, and one of the core libraries I rely on, skia is packaged incorrectly. I also require a different version than what’s currently provided. When I tried to package the libraries I needed using Makefiles, I realized it was taking more time and effort than simply building a new system from scratch.

Whats really nice about this system is you can have as many versions of a port installed at the same time, it also reserves all past and future ports and ports configurations.

The design is inspired by Nix. It uses TOML for port definitions and tar/zstd for packaging.

Used like this.
Code:
pkg install gnome

  Commands
  - list — list available ports
  - show <name> [ver] — show a port definition
  - deps <name> [ver] — show port dependencies
  - plan <name> [ver] — show build order
  - fetch <name> [ver] — fetch sources
  - build [--group] [--install] [--force] <name> [ver] — build a port or group
  - install [--rebuild] [--group] <name> [ver] — create profile tree
  - package all — package current profile
  - root — show ports root


  Flags
  - --group — treat <name> as a group (in build/install)
  - --install — link each built port into the profile as it finishes (build)
  - --force — override active lockfile (build)
  - --rebuild — rebuild missing deps before install (install)


This is the ports root dir.
Screenshot From 2026-02-05 09-34-37.png

This is the build/ directory, when you build a port its built in this dir.

Screenshot From 2026-02-05 09-36-44.png


build/logs/ When we build a port from src these are the logs generated, a log is
what you would see in your terminal when building src code.

Screenshot From 2026-02-05 09-39-03.png


build/distfiles/ When you build a port from src its src package is downloaded here.

group/ A group allows you to assign a group of packages to a single option. So in
this case we would do ...
Build: ./pkg build --group development
Build + install each: ./pkg build --group --install development
Install ./pkg install --group development

Screenshot From 2026-02-05 09-49-38.png


group/development.toml
Code:
ports = [
  "libepoll-shim",
  "libffi",
  "expat",
  "libglvnd",
  "hwdata",
  "libpciaccess",
  "libdrm",
  "mesa-libs",
  "pixman",
  "wayland",
  "wayland-protocols",
  "wlr-protocols",
  "wlroots",
  "yoga"
]
 
pkgs/ This is where the actual ports files are stored.
Screenshot From 2026-02-05 10-00-36.png


pkgs/expat/ Each version of a port gets its own version dir.

Screenshot From 2026-02-05 10-02-11.png


pkgs/expat/version.toml current is built by default, future is the next os version
and last is the previous os version 13 would be freebsd version 13.
Code:
future = "2.8.0"
current = "2.7.3"
last = "2.7.1"
13 = "2.6.8"
12 = "2.6.3"

pkgs/expat/2.7.3/port.toml
Code:
name = "expat"
version = "2.7.3"
summary = "XML 1.0 parser"
license = "MIT"

deps = []

[src]
type = "url"
url = "https://github.com/libexpat/libexpat/releases/download/R_2_7_3/expat-2.7.3.tar.xz"
sha256 = ""

[build]
system = "configure"
make = "make"
args = ["--prefix=/usr", "--without-docbook", "--without-examples"]

[install]
args = ["DESTDIR=$out", "install"]

Here is a more complex port.toml file pkgs/skia/m145/port.toml
Code:
name = "skia"
version = "m145"
summary = "Skia 2D graphics library"
license = "BSD-3-Clause"

deps = []

[src]
type = "path"
path = "../sdk/contrib/skia"

[build]
system = "custom"
commands = [
  "mkdir -p $WORK/build",
  '''sh -c 'cat > "$WORK/build/args.gn" << "EOF"
is_official_build=true
is_component_build=false
skia_use_vulkan=true
skia_use_gl=false
skia_use_egl=false
skia_use_x11=false
skia_use_fontconfig=false
skia_use_freetype=true
skia_use_system_freetype2=false
skia_use_harfbuzz=true
skia_use_system_harfbuzz=false
skia_use_expat=true
skia_use_system_expat=false
skia_use_icu=true
skia_use_system_icu=false
skia_use_dng_sdk=false
skia_use_system_libjpeg_turbo=false
skia_use_system_libpng=false
skia_use_system_libwebp=false
skia_enable_fontmgr_android=false
skia_enable_fontmgr_fontconfig=false
skia_enable_fontmgr_FontConfigInterface=false
skia_compile_modules=false
skia_compile_sksl_tests=false
skia_enable_skottie=true
skia_enable_svg=true
skia_enable_skshaper=true
skia_enable_skparagraph=true
EOF' ''',
  "GIT_SYNC_DEPS_SKIP_GN=1 GIT_SYNC_DEPS_SKIP_EMSDK=1 python3 \"$SRC/tools/git-sync-deps\"",
  "gn gen \"$WORK/build\" --root=\"$SRC\"",
  "ninja -C \"$WORK/build\" -v",
]

[install]
commands = [
  "rm -rf \"$out/usr\"",
  "mkdir -p \"$out/usr/lib\"",
  "mkdir -p \"$out/usr/include/skia/include\"",
  "mkdir -p \"$out/usr/include/skia/modules\"",
  "find \"$WORK/build\" -type f \\( -name 'lib*.a' -o -name 'lib*.so*' \\) -exec install -m 644 {} \"$out/usr/lib\" \\;",
  "find \"$out/usr/lib\" -type f -name '*.o' -delete",
  "cp -a \"$SRC/include/.\" \"$out/usr/include/skia/include\"",
  "cp -a \"$SRC/modules/.\" \"$out/usr/include/skia/modules\"",
]


ports.lock/ This file locks the ports build system so we can't build other packages when
one is currently build it also tells us what we are building and what failed, this file is auto generated

Code:
# Generated by pkg
state = "failed"
pid = 92377
started = 1770312618
command = "build --install wlroots"
current = "mesa-libs@24.1.7"
completed = [ "hwdata@0.399", "libpciaccess@0.18.1", "libdrm@2.4.129", "libglvnd@1.7.0", "expat@2.7.3", "spirv-tools@2026.1", "glslang@16.2.0", "py-markupsafe@3.0.2", "py-mako@1.3.5", "vulkan-headers@1.4.336", "libffi@3.5.1", "libepoll-shim@0.0.20240608", "wayland@1.24.0", "wayland-protocols@1.47" ]

[[package]]
name = "hwdata"
version = "0.399"
store = "/home/stevenstarr/Projects/ports/store/e2942dfed0c7-hwdata-0.399"
hash = "e2942dfed0c7479a459430c69a0e5ae672ce9ad2610e58f9b1af68256d57be28"
src_type = "url"
src_url = "https://github.com/vcrhonek/hwdata/archive/refs/tags/v0.399.tar.gz"

store/ This is where all ports packages are installed after they are built. Notice the hash's that's because
specific configuration should reproduce specific hashes, this is for reproducible
builds example say you have a program that needs a very specific version and configuration
if all the settings and correct patches etc are used you should get the same hash.

When packaging binary's we simply package the following directory's individually, when we install a binary port package
we would have pkg download and extract the package here, and uninstall we simply delete it.

Screenshot From 2026-02-05 10-17-49.png


store/0ff43b52f3b3-wayland-1.24.0
Code:
.
└── usr
    ├── bin
    │   └── wayland-scanner
    ├── include
    │   ├── wayland-client-core.h
    │   ├── wayland-client-protocol.h
    │   ├── wayland-client.h
    │   ├── wayland-cursor.h
    │   ├── wayland-egl-backend.h
    │   ├── wayland-egl-core.h
    │   ├── wayland-egl.h
    │   ├── wayland-server-core.h
    │   ├── wayland-server-protocol.h
    │   ├── wayland-server.h
    │   ├── wayland-util.h
    │   └── wayland-version.h
    ├── lib
    │   ├── libwayland-client.so -> libwayland-client.so.0
    │   ├── libwayland-client.so.0 -> libwayland-client.so.0.24.0
    │   ├── libwayland-client.so.0.24.0
    │   ├── libwayland-cursor.so -> libwayland-cursor.so.0
    │   ├── libwayland-cursor.so.0 -> libwayland-cursor.so.0.24.0
    │   ├── libwayland-cursor.so.0.24.0
    │   ├── libwayland-egl.so -> libwayland-egl.so.1
    │   ├── libwayland-egl.so.1 -> libwayland-egl.so.1.24.0
    │   ├── libwayland-egl.so.1.24.0
    │   ├── libwayland-server.so -> libwayland-server.so.0
    │   ├── libwayland-server.so.0 -> libwayland-server.so.0.24.0
    │   └── libwayland-server.so.0.24.0
    ├── libdata
    │   └── pkgconfig
    │       ├── wayland-client.pc
    │       ├── wayland-cursor.pc
    │       ├── wayland-egl-backend.pc
    │       ├── wayland-egl.pc
    │       ├── wayland-scanner.pc
    │       └── wayland-server.pc
    └── share
        ├── aclocal
        │   └── wayland-scanner.m4
        └── wayland
            ├── wayland-scanner.mk
            ├── wayland.dtd
            └── wayland.xml

10 directories, 35 files

profile/ This is the profile dir this is where all the installed files from store are reassembled
via symlinks. We use current by default. /usr/local would point to one of these.

Screenshot From 2026-02-05 10-26-26.png


profile/current/ This is what the inside looks like. On your file system /usr/local
points to this.
Screenshot From 2026-02-05 10-31-26.png
 
profile/current/ This is what the inside looks like. On your file system /usr/local
points to this.
You want to change the installed software based upon some profile? If you plan to do this on a per-user basis we need some explaination how this shall fly in a Multi-User system.
 
You want to change the installed software based upon some profile? If you plan to do this on a per-user basis we need some explaination how this shall fly in a Multi-User system.
Right now by default there is one shared profile for the whole system, you can run a single global profile or separate per‑user profiles. A shared
base + per‑user overlay is something I can add later if needed, but it’s not required to make multi‑user work. And for clarification I am not suggesting replacing freebsd pkg tool or ports system, I am just saying this is what I am working on and felt like sharing because I like what I built, its a sound proven design.
 
Well, this seems really interesting. The problem is that, in general, you can't come out of nowhere with something revolutionary and expect a close-knit community to do anything other than fiercely reject your ideas or simply ignore them. Maybe this will be the exception to the rule, but that is the rule of the human condition. I wonder, what has changed in your life, after 14 years (you said this in another thread) of solitary work? Also, it's obvious you are a very resourceful person. Why don't you create a repack of FreeBSD? Like the guy who controls GhostBSD did. ZestBSD.

My main concern, though, is that I see a person of great intelligence and imagination, you zester, who I suspect is having some kind of crisis, and I wouldn't like him to feel defeated or bad. I may be wrong again. It's very common to be wrong.
 
Well, this seems really interesting. The problem is that, in general, you can't come out of nowhere with something revolutionary and expect a close-knit community to do anything other than fiercely reject your ideas or simply ignore them. Maybe this will be the exception to the rule, but that is the rule of the human condition. I wonder, what has changed in your life, after 14 years (you said this in another thread) of solitary work? Also, it's obvious you are a very resourceful person. Why don't you create a repack of FreeBSD? Like the guy who controls GhostBSD did. ZestBSD.

My main concern, though, is that I see a person of great intelligence and imagination, you zester, who I suspect is having some kind of crisis, and I wouldn't like him to feel defeated or bad. I may be wrong again. It's very common to be wrong.

I don’t see a problem here, and I fully understand that this is a close-knit community I’ve been a member since 2011. What I don’t understand is the notion that someone isn’t allowed to discuss a new or unconventional idea unless they’ve been granted some kind of special permission. That’s not how open source works, and I’m fairly certain that’s also not in line with the rules of this forum.

We shouldn’t assume that simply because I share something I’m working on, it somehow affects the community, or that I’m trying to change minds or force ideas on anyone. I was very explicit in the thread title: I used the words “my” and “alternative.” I never claimed otherwise.

I also never said I was working in isolation. What I said was that it took me 14 years to go from rendering a single image to building something functional. And thank you for the compliment“it’s obvious you are a very resourceful person.”

To be absolutely clear, I have zero interest in creating a distribution.

Finally, it’s extremely inappropriate to insinuate that sharing a personal technical project on these forums implies some sort of mental health issue. That kind of assumption is both rude and unwarranted.
 
I don’t see a problem here, and I fully understand that this is a close-knit community I’ve been a member since 2011. What I don’t understand is the notion [...]
Sorry, it's just my suspicion. I might be wrong! I was only talking about my suspicion! It was ME talking! I don't represent the community. I hardly represent me.
 
I think I should resign after this great mistake. I'm very sorry zester. I apologize again. You are right in everything you say. I'm infinitely sorry.
Relax you’re fine. If I could offer some advice: embrace diversity in all its forms, lead with kindness, and reject gatekeeping and elitism.
 
Relax you’re fine. If I could offer some advice: embrace diversity in all its forms, lead with kindness, and reject gatekeeping and elitism.
Again, I was not talking about myself. I was talking about what I feared could happen. Anyway, I'm still very sorry and I'll be very sorry for a long time. I apologize again, zester.
 
Again, I was not talking about myself. I was talking about what I feared could happen. Anyway, I'm still very sorry and I'll be very sorry for a long time. I apologize again, zester.
Thanks for trying to be my protector ;) but I’ve been around a long time and I’m more than capable of handling anyone who wants to give me a hard time.
 
I'm wondering if someome familiar with synth could compare the features to zester's work.
They are completely different designs.

1. I use a toml format because I found the ports tree makefile to complex.
Mine ports.toml
Code:
name = "expat"
version = "2.7.3"
summary = "XML 1.0 parser"
license = "MIT"

deps = []

[src]
type = "url"
url = "https://github.com/libexpat/libexpat/releases/download/R_2_7_3/expat-2.7.3.tar.xz"
sha256 = ""

[build]
system = "configure"
make = "make"
args = ["--prefix=/usr", "--without-docbook", "--without-examples"]

[install]
args = ["DESTDIR=$out", "install"]

FreeBSD ports makefile
Code:
PORTNAME=    expat
DISTVERSION=    2.7.3
CATEGORIES=    textproc
MASTER_SITES=    https://github.com/libexpat/libexpat/releases/download/R_${DISTVERSION:S|.|_|g}/

MAINTAINER=    desktop@FreeBSD.org
COMMENT=    XML 1.0 parser written in C
WWW=        https://github.com/libexpat/libexpat

LICENSE=    MIT
LICENSE_FILE=    ${WRKSRC}/COPYING

TEST_DEPENDS=    bash:shells/bash

USES=        cpe libtool pathfix python:test tar:xz
CPE_VENDOR=    libexpat_project
CPE_PRODUCT=    libexpat
USE_LDCONFIG=    yes

GNU_CONFIGURE=    yes

INSTALL_TARGET=    install-strip
TEST_TARGET=    check

PLIST_SUB=    EXPAT_VERSION=${DISTVERSION}

CONFIGURE_ARGS=    --without-docbook \
        --without-examples

OPTIONS_DEFINE=    DOCS STATIC TEST
OPTIONS_SUB=    yes

STATIC_CONFIGURE_ENABLE=    static

TEST_USES=    compiler:c++11-lang shebangfix
SHEBANG_FILES=    test-driver-wrapper.sh tests/udiffer.py tests/xmltest.sh
TEST_CONFIGURE_WITH=    tests

.include <bsd.port.mk>

2. My system builds into a content‑addressed store, so every output path is a hash
of its inputs (sources, patches, and build settings). That gives an immutable,
verifiable fingerprint for what was built. For regulated environments like DoD,
you can pin the exact inputs and only accept binaries whose store hash matches
the certified signature so the build is repeatable and auditable, and any
deviation is immediately detectable.

3. Everything we build lives in a read‑only, versioned store. The ‘installed’
system is just a profile: a directory of symlinks pointing into that store.
Switching or rolling back is just swapping the profile.”

4. Synth I think builds multiple packages at a time, mine doesn't do that as that would compromise safety and reliability
in this type of design.
 
Note to future me: By default, all installations are global. A “local” install is simply a global package whose accessibility is restricted to a specific user.
 
Very cool project! It would be interesting to see a FreeBSD using that kind of package management. In particular I like the feature of having multiple releases of the same program or library installed at the same time.
 
Thanks! You’re welcome to use it, it’s designed to be dropped right into a fresh install. It’s not currently ready for release; I only have about 25 ports working. It’s a constant fight to add more, because it feels like everything is built for Linux, and there are lots of little gotchas here and there.
 
I started building it because I needed a specific set of packages that aren’t available in the FreeBSD ports tree, and one of the core libraries I rely on, skia is packaged incorrectly. I also require a different version than what’s currently provided. When I tried to package the libraries I needed using Makefiles, I realized it was taking more time and effort than simply building a new system from scratch.

Dare I say you started building it because you wanted to build one ;)
Because all the stuff you mention, would be solved by using current systems in place.

I use a toml format because I found the ports tree makefile to complex.

Your toml seems to do not much more than automate the easiest, default build instructions for a build system that already supports FreeBSD.
What ports do is patch the sources for FreeBSD, and translate between features present on the system (global flags, compilation options, flavours) and the internal build system of the software to be ported.

They are much much more than "./configure && make && make install", in any syntax shape or form those three steps may come.

For example you've used expat, a simple port, yet the toml doesn't reflect its dependencies.

I don't know, you do you but I don't see any point in revamping the whole system just to use another syntax. These things are totally oblivious, if you intent this to live and people to use it, target group of "prefer toml so much I will use a less featured build system" is almost nonexistent.

Whats really nice about this system is you can have as many versions of a port installed at the same time, it also reserves all past and future ports and ports configurations.

How is that not achievable with current systems? You can have multiple ports trees. Or switch between git branches. Every tree has its own persisted configuration. When you build the port you don't install it, but build the package. Then you can install that package in another prefix. It is exactly what wine does to have separate 32 bit system "installed".

Also ports and packages as system feature are to be used with other features such as chroots and jails. Need a whole ecosystem around a previous version, need a cutting edge version thats too unstable to be bumped in the ports tree? Just isolate it all.

I'm not trying to burst your bubble. It is simply my opinion, problems you solve are already solved, in another style.
 
I get what you’re saying, but I don’t really agree with the conclusion.

Yes, the existing FreeBSD tooling can solve these problems, but in practice I found it more complex than what I needed. This isn’t about raw capability, it’s about ergonomics and time. I was spending more effort understanding ports internals, Makefile conventions, OPTIONS, flavors, and patch logic than actually building the software I care about. For me, a simpler system was the faster path.

The TOML comparison wasn’t meant to replicate everything ports does. It was a direct comparison of expressing the same port logic in TOML versus Makefiles. The goal is clarity and determinism, not mirroring ports 1:1. Any missing features are a conscious scope decision, not a limitation of the format.

As I’ve said earlier, I’m not trying to replace ports or pkg, and I’m not suggesting anyone change how they work. This is something I built to solve a specific set of problems in my own workflow, and I’m sharing it because others might find the approach interesting or useful.

FreeBSD has always been about giving people options rather than enforcing a single “right” way. If this doesn’t fit someone’s needs, that’s perfectly fine the existing tools already work well for a lot of people. This is just an optional alternative, not a prescription.
 
I moved commands to shell scripts, that portion of toml was to verbos for my liking and prone to syntax errors.

There is also Sqlite FTS5 support so you can do full text search on the ports port.toml file, case you wanted every port that required a certain dep, particular patch, license, buildsystem, config option, ... anything in the toml file below.

There is also support for cache builds.

Also added support for more build system right now it supports autotools, make, cmake, cmake-ninja, meson, ninja, gn, qmake, scons, waf, cargo, go, python, pep517, b2 and custom in addition it also supports patching and has a partial ports Makefile to TOML converter. Below is a working example of that.

Ohhh an I also added libgit2 support, because we can check out past ports version for conversion from previous version of FreeBSD from the git repo.

port.toml
Code:
name = "mesa-libs"
version = "24.1.7"
summary = "OpenGL libraries that support EGL/GBM clients"
license = "MIT"

deps = ["bison", "expat", "glslang", "libdrm", "libglvnd", "py-mako", "vulkan-headers", "wayland", "wayland-protocols"]

[src]
type = "url"
url = "https://archive.mesa3d.org/mesa-24.1.7.tar.xz"
sha256 = ""

[build]
system = "meson"
env = ["YACC=bison"]
commands = [
  "@fix-wayland-protocols-pc.sh",
  "@fix-libdrm-pc.sh"
]
args = [
  "-Degl=enabled",
  "-Dgbm=enabled",
  "-Dglx=disabled",
  "-Dplatforms=wayland",
  "-Dgallium-drivers=swrast",
  "-Dvulkan-drivers=",
  "-Dllvm=disabled",
  "-Dshared-glapi=enabled",
  "-Dopengl=true",
  "-Dgles1=enabled",
  "-Dgles2=enabled",
  "-Dzstd=disabled"
]

[install]
args = []

fix-wayland-protocols-pc.sh

sh:
#!/bin/sh -e
pc="$profile/share/pkgconfig/wayland-protocols.pc"
if [ ! -e "$pc" ]; then
  echo "wayland-protocols.pc not found: $pc" >&2
  exit 1
fi
real="$(realpath "$pc")"
sed -i '' -e 's|^prefix=.*|prefix=$profile' "$real"
sed -i '' -e 's|${pc_sysrootdir}||' "$real"
 
This isn’t about raw capability, it’s about ergonomics and time. I was spending more effort understanding ports internals, Makefile conventions, OPTIONS, flavors, and patch logic than actually building the software I care about. For me, a simpler system was the faster path.

FreeBSD has always been about giving people options rather than enforcing a single “right” way. If this doesn’t fit someone’s needs, that’s perfectly fine the existing tools already work well for a lot of people. This is just an optional alternative, not a prescription.

No, I cannot agree with any of that but I'm not going to dissect and reply piecewise. My issue is calling the tool "pkg" and somewhat advertise it as the pkg alternative. That's just semantics, not important, and I support your efforts.

Here I see a project of internal build help tool. One that has nothing FreeBSD specific in it, therefore cannot be "a new package tool and ports system". It doesn't apply FreeBSD patches, doesn't rely on anything FreeBSD, it seems like completely cross platform POSIX. Which is very good in itself, in my opinion we want software developed on FreeBSD with cross platform in mind, not tying itself if it doesn't need to (unlike Linux ppl).

Plus porters can reuse the fix scripts and patches you'll have to do in order to bring non-ported FOSS to your build system.

There's a ton of usable software, libs and applications on github that compile under FreeBSD with no or slight intervention but nobody uses them or ports them. Only sporadic ./configure make make install users around. And then those projects suddenly move to Linuxism, because they don't know of usage outside Linux.

So more ports porters and maintainers are always cool one way or the other. Somebody to get back to those guys and say hey you broke our port, even if they don't know it exists...
 
In particular I like the feature of having multiple releases of the same program or library installed at the same time.

FreeBSD allows that feature and as policy FreeBSD never makes fat tools, it uses Unix ways, right tool for the right job.

Yes the package manager could solve the link environment and manage symlinks ala "update-alternatives" so you can seamlessly run various versions of same program, that is always linked to correct version of dependencies.

But it doesn't because that's not its job. What pkg can do, is install packages and dependencies to any prefix (--rootdir) using alternate repository configuration (-R), as limited user (INSTALL_AS_USER).

If normal pkg provides app v2.0 linked to lib v2.0, you use above to install v1.0 versions from alternate repos as user to some directory.
Now you have the directory with all the dependencies in proper layout. This is like the insides of a flatpak.
All you need to do is LD_LIBRARY_PATH=thatdir/usr/local/lib thatdir/usr/local/bin/appv1 to run the program.
 
  • Like
Reactions: ptx
No, I cannot agree with any of that but I'm not going to dissect and reply piecewise. My issue is calling the tool "pkg" and somewhat advertise it as the pkg alternative. That's just semantics, not important, and I support your efforts.

Here I see a project of internal build help tool. One that has nothing FreeBSD specific in it, therefore cannot be "a new package tool and ports system". It doesn't apply FreeBSD patches, doesn't rely on anything FreeBSD, it seems like completely cross platform POSIX. Which is very good in itself, in my opinion we want software developed on FreeBSD with cross platform in mind, not tying itself if it doesn't need to (unlike Linux ppl).

Plus porters can reuse the fix scripts and patches you'll have to do in order to bring non-ported FOSS to your build system.

There's a ton of usable software, libs and applications on github that compile under FreeBSD with no or slight intervention but nobody uses them or ports them. Only sporadic ./configure make make install users around. And then those projects suddenly move to Linuxism, because they don't know of usage outside Linux.

So more ports porters and maintainers are always cool one way or the other. Somebody to get back to those guys and say hey you broke our port, even if they don't know it exists...

“BSD has always been about research, experimentation, and pushing systems forward.”
Marshall Kirk McKusick, BSD architect and longtime FreeBSD contributor
 
Back
Top