Install
pip install pkgforge
pip install 'pkgforge[yaml]' # adds the YAML DB backend
pip install 'pkgforge[zstd]' # adds zstd compression below Python 3.14
pkgforge requires Python 3.9+. Its one runtime dependency, as declared in
pyproject.toml, is duho>=0.7.8,<0.8
(the CLI framework). The yaml extra adds
PyYAML>=6.0,<7, which the yaml DB
backend needs; without it, --db-format yaml (or a .yaml --db) exits 1 with
a message naming the extra. JSON Lines, the default backend, and SQLite need
no third-party parser. The zstd extra adds
zstandard>=0.25,<0.26, which
pkgforge package needs for zstd compression below
Python 3.14 (Python 3.14 has it built in): -f arch, whose default is zstd, and
--compression zst for deb and tar. Without it those exit 2 with a message
naming the extra; --compression xz needs nothing extra.
Platform support
pkgforge is a Linux tool: the install, scan, and metadata-apply paths
use POSIX facilities — os.chmod, os.chown (with follow_symlinks=False),
and symlinks. The CLI itself (parsing, --help, the dump formats)
imports and runs on any platform, so you can develop and unit-test on Windows or
macOS; the file-staging operations expect a POSIX filesystem.
install --method link/move (see Commands) use
os.link/os.rename, which work the same way anywhere Python runs; crossing
a filesystem boundary falls back to a copy (link) or a copy-then-delete
(move) rather than failing.
install --record-tree (see Commands) is a plain
filesystem walk over the already-staged tree -- it reads each child's mode
and type from disk (os.lstat), no extra POSIX facility beyond what
staging itself already needs.
Archive extraction prefers stdlib tarfile for the tar family
(.tar, .tar.gz/.tgz, .tar.bz2, .tar.xz) and falls back to the
bsdtar binary (needs libarchive 3.3+) for stdin sources -- even a tar
stream, since tarfile needs a real path -- for any other format
(.zip, .iso, .cpio, ...), and for an interpreter whose tarfile has
no extraction filter (PEP 706; below 3.9.17/3.10.12/3.11.4); so a plain
tar-based build from a real file on a current Python needs no external
archiver installed. Both paths apply the same extraction policy: an
archive's ownership, setuid/setgid, group/other write, xattrs, ACLs and
file flags are never restored, and a device node, FIFO or socket is
refused. On the tarfile path, a symlink member's target is kept exactly
as stored (absolute or climbing targets included), but a write through any
symlink ancestor -- even one resolving back inside the destination -- is
refused, and a hardlink member's target must resolve inside the
destination; re-extracting the same archive over an existing tree replaces
a stale entry at each member's path instead of failing. install -X
prunes matched members (files, symlinks, and whole directory subtrees)
from the extracted tree before anything reaches DESTINATION, the same as
it filters a directory source's copy.
From source
git clone https://github.com/jose-pr/pkgforge
cd pkgforge
python -m venv .venv/3.14-posix-$(uname -m) && . .venv/3.14-posix-$(uname -m)/bin/activate
pip install -e ".[dev]"
pytest -q
Name the venv <version>-<os>-<arch> (<os> is posix/nt/darwin) if you
keep more than one interpreter around, e.g. to also test the >=3.9 floor.
Invocation
pkgforge installs a pkgforge console script and is also runnable as a
module:
pkgforge --help
python -m pkgforge --help